Contracts
adversarial-evidence-contract.md
Contract: Adversarial Evidence
Every contested finding from an adversarial-squad pass carries a recorded disposition — accepted, changed, or deferred_with_rationale. No contested finding may be silently dropped.
- Post-spec squad (reviewer-renata / architect-alphonso / planner-priti): dispositions recorded in ../research.md §Adversarial Evidence. All HIGH/MEDIUM findings
changed; twodeferred_with_rationale(extracted-pack layout → #3022; Windows-CI matrix → out of scope, covered by parametrized unit tests). - Post-plan squad: dispositions to be appended to research.md before
/spec-kitty.tasks. - No new dependency is introduced (hand-rolled parser), so the supply-chain adversarial pass reduces to confirming the null-change + the D6 pin-exact-rc mitigation.
env-expander.md
Contract: Kernel env-expansion seam
src/kernel/env_expand.py — one primitive, two policies. Consumed by charter (below specify_cli) and specify_cli.
API
def expand_env_template(raw: str, *, inject_defaults: bool, environ: Mapping[str,str] | None = None) -> str
class UnresolvedEnvTokenError(ValueError): ...
# kernel/paths.py
def get_packs_root_default() -> Path # == get_built_in_pack_root().parent
Behavioral guarantees (tests)
- C-EXP-1:
expand_env_template("${SPEC_KITTY_PACKS_ROOT}/built-in/x", inject_defaults=True)with the var UNSET → resolves tostr(get_packs_root_default()) + "/built-in/x"(no literal token, no raise). - C-EXP-2: same call with
inject_defaults=Falseand var unset → raisesUnresolvedEnvTokenErrornaming the token. - C-EXP-3:
get_packs_root_default()==get_built_in_pack_root().parent(token names…/packs; resolver returns…/packs/built-in; no double-join). - C-EXP-4:
org_pack_config._expand_path_templatedelegates withinject_defaults=False; an org-packlocal_pathwith an unset${VAR}still raises the existing fail-loud error (behavior byte-preserved). - C-EXP-5: kernel module imports nothing from
specify_cli/doctrine(arch-gated bytest_kernel_no_doctrine_import/test_layer_rules).
kitty-env-loader.md
Contract: .kitty.env pre-import loader
src/specify_cli/bootstrap/env_file.py — invoked before any spec-kitty import.
Behavioral guarantees (tests)
- C-LDR-1 (precedence): var set in real env, per-repo
.kittify/.kitty.env, and home-tier${SPEC_KITTY_HOME}/.kitty.env→ resolved value is the real-env one; with real-env unset, the per-repo one; with both unset, the home one. (Tiers merged{home, repo}, then oneos.environ.setdefault.) - C-LDR-2 (pre-import):
SPEC_KITTY_SYNC_MINIMAL_IMPORTset only in.kitty.env→ the import-time-gated behavior (sync/__init__.py:455) observes it; proves seed precedesimport specify_cli(__init__.py:36). - C-LDR-3 (fail policy): absent file → warn+continue (exit 0); present-but-unreadable
env_file→ non-zero exit naming the file; malformed line → skipped + debug log, bootstrap survives. - C-LDR-4 (locator recursion): a
SPEC_KITTY_HOME=line inside.kitty.env→ ignored with a warning. - C-LDR-5 (single pointer):
config.yamlenv_fileresolved once at bootstrap; a raw config reader never receives an unexpanded${…}token; the key is outsideextra="forbid"blocks. - C-LDR-6 (budget): loader is stdlib-only; the completion benchmark shows no regression beyond its noise floor.
- C-LDR-7 (cross-platform, state-root home): home-tier resolves to the STATE root —
%LOCALAPPDATA%\spec-kittyon Windows,~/.spec-kittyon POSIX (NOT~/.kittify). The shim reuses ONE stdlib-safe kernel-floor state-root primitive (no 4th resolver); a test asserts the shim's unset-SPEC_KITTY_HOMEdefault equalsget_runtime_root().baseon both platforms.
provenance-and-channel.md
Contract: Provenance tokens, secret redaction, release channel
Provenance (both carriers, one normalizer)
- C-PRV-1: fresh charter compile → every built-in catalog
source_pathis${SPEC_KITTY_PACKS_ROOT}/built-in/...; 0 absolute paths. Same foragent_profiles_manifest.json. - C-PRV-2 (re-bake gate): with
SPEC_KITTY_PACKS_ROOT=/some/absexported, emitted artifacts are byte-identical to the unset case (token stored, never the resolved path). - C-PRV-3 (invariance):
charter.yamlbyte-identical across editable checkout and installed wheel. - C-PRV-4 (heal): an existing absolute
source_path→ rewritten to a token by the heal migration; re-run = 0 changes. - C-PRV-5 (leak-check):
spec-kitty doctor(dedicated_provenance_doctor.pysibling undercli/commands/doctor.py, NOTruntime/doctor.py) flags any committed absolute built-in path with a heal hint. - C-PRV-6 (surgical normalizer, 3 classes): the shared path→token normalizer emits (a) token for built-in-pack paths, (b) repo-relative for in-tree project/org paths, (c) absolute for out-of-tree non-pack paths — replacing ONLY
compiler.py:1424/1447+projection.py:56; a regression asserts mission-template callers (compiler.py:1482/1494) and manifestoutput_path(manifest.py:112) are byte-unchanged.
Secret redaction (fail-closed allowlist)
- C-SEC-1: a var NOT on the printable-var allowlist (e.g.
SPEC_KITTY_SAAS_TOKEN) never appears by value indoctor/sync status/logs — only its presence. - C-SEC-2:
.kitty.envmatches an ignore rule in both.gitignoreand.claudeignore(asserted by an architectural test).
Release channel (consumer slice; interface with #3047)
- C-CHN-1 (default off): with
SPEC_KITTY_PRERELEASEunset and a newer rc on the index,upgrade --agent-checkreports the latest stable; no rc advisory. - C-CHN-2 (opt-in): with it truthy, the newest PEP 440 pre-release on the same PyPI index the CLI already probes is surfaced;
upgrade_commandisspec-kitty-cli==<rc>(pinned, no--pre). - C-CHN-3 (doctor):
doctorreports the active channel. - Interface with #3047: producer must publish rc's as PEP 440 pre-releases on that index/scheme; this mission owns only the consumer read.
Migration idempotency
- C-MIG-1: re-running the heal migration and the provision migration each yields 0 changes.
- C-MIG-2 (no PACKS_ROOT seed): the provision scaffold never writes
SPEC_KITTY_PACKS_ROOT; a regression assertsSPEC_KITTY_TEMPLATE_ROOTstill governs asset resolution when the scaffold is present.