Implementation Plan: Contract-ownership boundary — MVP
Branch: feat/contract-ownership-boundary | Mission: contract-ownership-boundary-01KWYRE5 Spec: kitty-specs/contract-ownership-boundary-01KWYRE5/spec.md | Design: kitty-specs/contract-ownership-boundary-01KWYRE5/design.md
Summary
Model shared contracts + their declared consumer set + retirement as one owned artifact — the conservative MVP. Generalize the proven shim-registry chain (schema→manifest→loader→doctor→scanner) into a Contract Registry; add the static-arm absence-sweep driver (advisory); adopt only the genuine retired-literal sweeps behind it (coverage proven first). No enforcement change, nothing force-folded.
Technical Context
Language/Version: Python 3.11 (repo pinned) Primary Dependencies: ruamel.yaml/PyYAML (manifest + schema); src/specify_cli/compat/registry.py (ShimEntry/load_registry/validate_registry — the generalization template); tests/architectural/_ratchet_keys.py::composite_key (content-anchoring, promoted to a shared lib); src/specify_cli/compat/doctor.py + cli/commands/doctor.py (the doctor <sub> pattern); test_no_dead_symbols's import resolver (consumer-set discovery); pytest (architectural marker) Storage: N/A — declarative YAML manifest docs/contracts/contract-registry.yaml + schema …-schema.yaml; no DB Testing: unit tests for the loader/validator (well-formed vs malformed vs file:line-anchored → reject); the static-sweep driver with a mandatory anti-vacuity negative control; parity tests for each adopted literal sweep (driver catches exactly what the old sweep caught) BEFORE removal Target Platform: local + CI (arch pole; docs/contracts/ docs-scoped like shim-registry.yaml) Project Type: single project — a governance/architecture boundary (registry + validator + one sweep driver + adoption) Performance Goals: N/A — validation + a content-anchored static sweep Constraints: no file:line anchoring (DIR-041 — the validator rejects it, NFR-003); the absence sweep is advisory in v1 (NFR-002); no coverage regression — parity proven before any removal (NFR-001); consumer sets discovered-then-frozen, never hand-typed blind (C-003); generalize the shim chain, don't reinvent (C-001); ruff+mypy --strict clean; no suppression Scale/Scope: new docs/contracts/ manifest+schema + src/specify_cli/contracts/registry.py + spec-kitty doctor contracts + a promoted anchoring lib + tests/architectural/test_retired_contracts_absent.py + adoption of test_no_legacy_terminology.py (+ the path-literal grep half)
Charter Check
Canonical-source discipline (generalize the shim chain), DIR-041 one-source-of-truth (the mission's raison d'être), advisory-first / delete-the-assertion-not-the-test, no ratchet/suppression — charter-aligned.
Implementation Concerns
IC-01 — The Contract Registry model (FR-001..004) — WP1 (MVP, standalone)
- Schema
docs/contracts/contract-registry-schema.yaml+ manifestdocs/contracts/contract-registry.yaml(docs-scoped sibling toshim-registry.yaml). Record:id,kind∈{fallback_name, retired_literal}, content-anchoredanchor(dotted symbol OR fixed literal — neverfile:line),status,owner,replaced_by,retirement,consumers{scan_roots,exemptions,test_shards?,call_sites?},verification{enforcement: advisory}. - Loader/validator
src/specify_cli/contracts/registry.py(ContractRecord,load_registry,validate_registry) — generalized fromcompat/registry.py; the validator rejects any positionalfile:linefield (NFR-003). spec-kitty doctor contracts— enforcing well-formedness (schema, resolvable anchors, nofile:line). Structural validation is the only enforcing gate in v1.- Promote
_ratchet_keys.py::composite_keyto a shared lib the loader + driver depend on (no behavior change to existing callers). - Seed one record (FR-004): a
retired_literalfromtest_no_legacy_terminology.pywith its discovered-then-frozen consumer set (scan_roots + exemptions), proving the model end-to-end.
IC-02 — Static-arm absence-sweep driver (FR-005) — WP2 (advisory)
tests/architectural/test_retired_contracts_absent.py: content-anchored sweep overstatus=retiredrecords, usingcomposite_key/literal anchoring acrossscan_rootsminusexemptions. Mandatory anti-vacuity negative control: a planted reappearance of a retired anchor MUST be flagged (else the sweep is vacuously green). Advisory/report-only — never blocks.
IC-03 — Model + parity-prove the literal sweeps, WITHOUT retiring the enforcing gates (FR-006) — WP3 (additive)
- Model the literal-sweep subset (
test_no_legacy_terminology.py+ the CLI-tree literal-grep half oftest_no_legacy_path_literals.py) asretired_literalrecords with declared consumers + exemptions. - Prove parity: the advisory driver flags exactly what
test_no_legacy_terminology.py(pytest.fail, merge-blocking) + the path-literal grep (assert, blocking) flag — but leave those enforcing gates in place (removing them behind an advisory driver silently downgrades a blocking gate → report-only; NFR-004). The delete-the-assertion retirement awaits the enforcing-driver follow-up. - Carve out (do NOT fold): the
path_literalsbehavioral nudge tests,status_emit_callers,agent_profiles_path.
Project Structure (files touched)
docs/contracts/contract-registry-schema.yaml # IC-01 NEW schema
docs/contracts/contract-registry.yaml # IC-01 NEW manifest (seeded with 1 record)
src/specify_cli/contracts/registry.py # IC-01 NEW loader/validator
src/specify_cli/contracts/__init__.py # IC-01 NEW
src/specify_cli/cli/commands/doctor.py # IC-01 add `doctor contracts` subcommand
tests/architectural/test_retired_contracts_absent.py # IC-02 NEW sweep driver + anti-vacuity control
tests/architectural/test_no_legacy_terminology.py # IC-03 parity-prove only — KEEP the enforcing gate in place (no retire in v1)
tests/audit/test_no_legacy_path_literals.py # IC-03 parity-prove the literal-grep half; keep all its assertions
Key Decisions
- Generalize, don't reinvent (C-001): new sibling registry under
docs/contracts/, reusing the shim schema/loader shape; fold shim-registry in a later follow-up. - Advisory + anti-vacuity (NFR-002): the sweep reports only; a planted-reappearance control proves it isn't vacuously green.
- Discovered-then-frozen consumers (C-003): seed from the import/call graph, freeze + review — never hand-type blind.
- Carve-outs (post-spec fold): only the literal sweeps fold; behavioral/AST/directory checks stay put or defer to the signature-kind follow-up.