Data Model: Operator Config & Install Ergonomics
These are configuration/infrastructure entities (no persistent DB). Invariants are the testable heart of the mission.
KittyEnvFile (two-tier)
- Locations: home-tier
${SPEC_KITTY_HOME}/.kitty.env; per-repo<repo>/.kittify/.kitty.env. - Shape: newline-delimited
KEY=VALUE;exportprefix tolerated; surrounding quotes stripped; full-line#comments; values literal (no in-value interpolation). - Contents: path vars (excl.
SPEC_KITTY_HOME) incl.SPEC_KITTY_PACKS_ROOT; sync vars (SPEC_KITTY_ENABLE_SAAS_SYNC,SPEC_KITTY_SAAS_URL,SPEC_KITTY_SAAS_TOKEN,SPEC_KITTY_ORG_TOKEN,SPEC_KITTY_TEAM_SLUG); beta varSPEC_KITTY_PRERELEASE. - Invariants:
- I1: precedence real-env > per-repo > home (tiers merged
{home, repo}then a singlesetdefault). - I2: MUST NOT define
SPEC_KITTY_HOME(locator) — such a line is ignored-with-warning. - I3: seeded into
os.environbefore any spec-kitty import. - I4: gitignored + claudeignored; never committed.
- I5: malformed line → skipped + debug log; absent file → warn-continue; present-but-unreadable → fail loud.
ConfigPointer (.kittify/config.yaml env_file)
- Field:
env_file: ${SPEC_KITTY_HOME}/.kitty.env(the sole expansion this key introduces). - Invariants: resolved once at bootstrap (not by the ~30 config readers); lives outside any
extra="forbid"pydantic block; committed (no secret — a path only).
EnvExpansionSeam (kernel/env_expand.py)
- API:
expand_env_template(raw, *, inject_defaults: bool, environ=None) -> str;UnresolvedEnvTokenError;_DEFAULT_INJECTORSregistry;get_packs_root_default() -> Path(kernel/paths). - Invariants:
- E1:
inject_defaults=False→ surviving token raises (resolution fields). - E2:
inject_defaults=True→ surviving${SPEC_KITTY_PACKS_ROOT}substitutesget_packs_root_default()=get_built_in_pack_root().parent. - E3:
org_pack_config._expand_path_templatedelegates withinject_defaults=False— fail-loud contract byte-preserved.
ProvenanceToken
- Form:
${SPEC_KITTY_PACKS_ROOT}/built-in/<kind>/<file>incharter.yamlcatalogsource_pathandagent_profiles_manifest.jsonsource_path. - Producer: one shared path→token normalizer consumed by both carriers.
- Invariants: P1 no absolute path ever serialized (incl. when
SPEC_KITTY_PACKS_ROOT=<abs>is exported); P2 byte-identical across editable/wheel.
SecretAllowlist
- Shape: set of printable var names (fail-closed).
- Invariant: any var not on the allowlist is never rendered by value in doctor/
sync status/logs.
ReleaseChannelPreference
- Var:
SPEC_KITTY_PRERELEASE(default off). - Invariants: off → 0 rc advisories; on → newest PEP 440 pre-release surfaced + pinned
==<rc>install command.
Migrations (two, independent)
- HealProvenanceMigration (WP1):
detect= any absolute built-insource_path;applyrewrites to tokens via the charter-yaml safe round-trip + manifest save; idempotent. - ProvisionKittyEnvMigration (WP2): create
.kitty.env(seed only already-set values; neverSPEC_KITTY_PACKS_ROOT), register pointer, add ignore rules; idempotent; ordering coordinated with #3381. - Invariant M1: re-run of either produces zero changes.