Implementation Plan: Ratchet, baseline & census gate remediation

Branch: claude/spec-kitty-remediation-wfje22 (planning base = merge target) | Date: 2026-09-26 | Spec: spec.md Input: Mission specification from kitty-specs/ratchet-baseline-census-gate-remediation-01M3EW3Z/spec.md (rev 2 + errata) Design source: research.md (Phase 0 design research, architect lens, HEAD 3717c7ea; section letters A–G below refer to it)

Summary

Make every ratchet, allowlist and census gate in epic #5104's scope either measure what it claims (content-identified, non-vacuous, drift-tolerant) or be retired with its residue. Technical approach:

1. Widen the positional-anchor ban (research §A) with a predicate owned by the ban's test module, covering Python allowlists and _exemptions/*.txt; it finds 94 line-pinned entries on the planning base. 2. Migrate hand-curated allowlists (join, kernel, os-detect) to ContentDescriptor entries via a new shared matcher _content_identity.py, where one entry suppresses at most one finding (§B). 3. Re-key the 80 census entries to CensusKey(rel, qualname, token_line, op, op_ordinal) built on _ratchet_keys.composite_key, deleting the census helper's duplicate qualname algorithm; equivalence proven by a mapping CSV and a base-vs-head script (§C). 4. Derive enforced baseline leaves from a single hoisted comparison table; fail on non-enforcing leaves; retire the dead inert-slot machinery (§D). 5. Retire the surface-resolution converter directory (keeping the scanner functions in _surface_resolution_scan.py) and resolution_gate_allowlist.yaml (§E). 6. Parity remediation per file (§F): non-vacuous runtime-parity ban, delete status/test_parity.py after relocating its live invariants, convert context parity to an on-disk packs fixture, split bridge-parity P0 tests from the oracle.

Engineering Alignment

The operator instructed an end-to-end autonomous run ("drive mission e2e"), so planning questions were resolved from the confirmed spec, the Decision Moments and the design research rather than by further interrogation. Plan-level decisions (recorded in tracer-design-decisions and research §8):

IDDecisionRationale
D-OP-1Census identity = composite key + op + op_ordinal (renamed from occurrence to avoid clashing with ContentDescriptor.occurrence)Only 3/80 need occurrence > 0; rejects both widening and arg drift (§C1)
D-OP-2Remove category_1 and skip_marker_blocks leaves rather than enforce themNeither is a size ceiling; category_1's equality pin shows the "toll drain" never worked (§D3)
D-OP-3Comment-only src/ edits allowed under C-005 with AST-equality proofNeeded for SC-003 (status/aggregate.py:543); behaviour provably unchanged
D-OP-4WP07 and WP09, the two deletion-only WPs, run under a recorded charter exception to ATDD-First (C-011). WP09's evidence substitute is the committed, reviewer-re-runnable mutation script research/wp09_mutation_matrix.py, proving every invariant of tests/status/test_parity.py is caught by a surviving or relocated test. WP07's evidence substitute is the base defect recorded via spec-kitty agent tracer-append (audit.py exit 1, 8 missing + 8 ghost rows; rekey_inventory.py --check STALE), a reviewer-re-runnable reproduction block, and the survivor test_single_mission_surface_resolver.py (8 tests) GREEN from _surface_resolution_scan (the earlier import-error RED is withdrawn: it proved only that a module name is new). WP05 gets no exception: it lands the honest failing-first test_load_baseline_rejects_retired_keys. Tombstone tests are never usedOperator-approved (stijn-dejongh): WP09 on 2026-09-26 (analysis finding D1); extended to WP07 by Decision Moment DM-01M3F3T1G2RYW7P0ZVWQS41GEZ, resolved 2026-09-26. See Complexity Tracking. Avoids the class #3285 removed
D-OP-5FR-015 proceeds via SPEC_KITTY_PACKS_ROOT mirror fixturePrototyped with zero private patches; defer only if Windows-symlink or cache-order checks fail
D-OP-6The text-file arm makes path:line exemption shapes unusable at zero entries (clock, lock-ban)Intended: those gates are shrink-only at zero
D-OP-7Interim 94 per-site exemptions in WP01, pinned empty in WP13Lets the ban land green and the migrations run in parallel; the interim state never reaches main because the mission ships as one PR
D-OP-9_content_identity.py is the single matching authority (one entry suppresses at most one finding, path always part of the key); census diff_against_allowlist adopts it in WP04; other matchers follow upPaula post-plan: it would otherwise be the 8th matcher (DIRECTIVE_044)
D-OP-10Mirror packs fixture is always copied, never symlinkedCopy costs 0.05 s; Windows safety (Debbie post-plan)
D-OP-8Stale policy split: hand-curated allowlists fail, census allowlists warnEpic "no re-pin toll on unrelated changes" (post-spec divergence adjudicated)

Technical Context

Language/Version: Python 3.11+ (repo .venv, uv sync --frozen --all-extras) Primary Dependencies: pytest, pytest-xdist, ast (stdlib), existing substrate tests/architectural/_ratchet_keys.py (composite_key, ContentDescriptor, resolve_descriptor) Storage: Files only — Python allowlist literals, tests/architectural/_baselines.yaml, _inert_slots_baseline.yaml, _exemptions/*.txt, mission CSV artefact Testing: pytest; per-WP blast radius (research §G3) + make test-fast; full tests/architectural/ for WPs touching shared helpers, _baselines.yaml or pyproject.toml Target Platform: CI Linux + Windows CI lane (ci-windows.yml) for WP10 symlink behaviour Project Type: single (test infrastructure inside the existing repo) Performance Goals: widened ban < 10 s over tests/architectural/ (prototype 1.4 s); bridge P0 module < 60 s (oracle ~389 s untouched) Constraints: C-001 red-first (two recorded exceptions: WP07, WP09), C-002 oracle untouched, C-004 one identity mechanism, C-005 no src/ behaviour change, complexity ≤ 15, zero new suppressions Scale/Scope: 13 WPs; ~94 line-pinned entries migrated; 45 parity modules cataloged; estimated +1.7k / −3.3k lines

Charter Check

Gate evaluated before Phase 0 and re-checked after design.

Charter ruleStatusEvidence
Single canonical authority (DIRECTIVE_044)PASSC-004 deletes the census helper's second qualname algorithm; one comparison table for baselines (§D2)
ATDD-first (C-011)PASS with 2 recorded exceptions (WP07, WP09)Both operator-approved (DM-01M3F3T1G2RYW7P0ZVWQS41GEZ for WP07; analysis finding D1 for WP09; see Complexity Tracking). Every other WP names a RED-on-base acceptance test (table below), including deletion-heavy WP05
Architectural gate discipline (SO #5, DIRECTIVE_043)PASSNFR-002 floors + real-path self-mutation per ban; shrink-only baselines (NFR-003)
Test remediation discipline (SO #4, DIRECTIVE_041)PASSParity verdicts per discriminator; NFR-006 requires a surviving test or mutation proof per retirement
Campsite cleaning (SO #2)PASS#2972 findings cleaned only in touched test files (FR-020)
Pack tiersN/ANo doctrine pack edits
Terminology canonPASSMission terminology; test_no_legacy_terminology.py in WP12 blast radius
Git workflow (DIRECTIVE_045/046)PASSTopic branch, one PR to main, compressed history, operator merges
Pre-existing failure reportingWATCHAny base-red test encountered is classified and, if pre-existing, filed as an issue before continuing

Post-design re-check: PASS, with the two operator-approved ATDD exceptions (WP07, WP09) recorded in Complexity Tracking.

Mission-global constraints. C-001 (ATDD red-first), C-005 (no production behaviour change), C-006 (branch and publication) and NFR-005 (quality gates) bind every WP, whether or not a WP's requirement_refs lists them.

Project Structure

Documentation (this mission)

kitty-specs/ratchet-baseline-census-gate-remediation-01M3EW3Z/
├── spec.md
├── plan.md                  # this file
├── research.md              # Phase 0 design (sections A–G)
├── data-model.md            # Phase 1 entities
├── quickstart.md            # verification commands
├── research/                # grounding, post-spec squad, census equivalence script (WP04)
├── census-rekey-map.csv     # WP04 artefact
├── checklists/requirements.md
└── tasks.md, tasks/         # /spec-kitty.tasks output

Source Code (repository root)

tests/architectural/
├── test_ratchet_positional_anchor_ban.py   # WP01 (widen), WP13 (pin empty)
├── _content_identity.py                    # NEW (WP02) shared matcher
├── test_content_identity.py                # NEW (WP02)
├── test_built_in_location_authority.py     # WP02 join allowlist
├── test_kernel_no_doctrine_import.py       # WP03
├── _os_detection_exemptions.py, _exemptions/os-detect-ban-*.txt, test_os_detection_ban.py  # WP03
├── _destructive_op_census.py               # WP04 (CensusKey, unify qualname)
├── test_destructive_op_routing.py, test_overwrite_ownership_routing.py, test_mutation_ownership_routing.py  # WP04
├── _inert_slots*.py/.yaml, test_no_inert_schema_slots.py  # WP05
├── test_ratchet_baselines.py, _baselines.yaml  # WP06 (WP05 sequenced inert-leaf edit)
├── surface_resolution_audit/ → _surface_resolution_scan.py  # WP07 (directory retired)
├── resolution_gate_allowlist.yaml          # WP13 (retired)
└── test_execution_context_parity.py, test_docs_cli_reference_parity.py  # WP08
tests/next/test_internal_runtime_parity.py, tests/contract/test_next_no_unknown_state.py  # WP08
tests/missions/test_surface_resolution_equivalence.py, tests/review/test_transition_gate_parity.py  # WP08
tests/status/test_parity.py (deleted), test_reducer.py, test_transitions.py  # WP09
tests/charter/test_context_parity.py → test_context_bootstrap_markers.py  # WP10
tests/runtime/test_bridge_parity.py, test_next_board_authority.py (NEW), _next_mission_scaffold.py (NEW)  # WP11
pyproject.toml (format-exclude lines)       # WP07, WP09, then WP13
kitty-specs/<mission>/research/parity-verdicts.md  # WP12 (catalog)

Structure Decision: all changes live in the existing test tree; no new packages. File ownership is exclusive per WP except the sequenced hotspots below.

Parallel Work Analysis

Dependency Graph (revision 2, post-plan squad folded)

graph LR
  WP01 --> WP13
  WP02 --> WP03 --> WP13
  WP02 --> WP04 --> WP13
  WP05 --> WP06 --> WP13
  WP07 --> WP13
  WP08 --> WP13
  WP09 --> WP13
  WP10 --> WP13
  WP11 --> WP13
  WP12 --> WP13

Critical path: WP02 → WP04 → WP13.

Work packages

WPTitleFRsDepsSizeRed-first acceptance test
WP01Widen positional-anchor ban + interim per-site exemptionsFR-001, FR-002, FR-003 (interim), NFR-002, NFR-004—Mwidened arms report 94 findings on base (one exemption row per line); 4 inverted tests
WP02Content-identity matching authority + join allowlistFR-004, FR-007, NFR-001, NFR-003—Stest_join_allowlist_entries_each_suppress_a_live_join (RED: 2 dead) + drift test (3 files)
WP03Kernel + os-detect exemptions; retarget lock-ban/clock loadersFR-005, FR-007, NFR-001WP02Skernel + os-detect drift tests; path:line loader shape rejected
WP04Census re-key (all 80) on _content_identity + composite keyFR-006, C-004, NFR-001WP02L (split commits: helper → destructive+overwrite → mutation)census drift tests (15/21/2 files) + test_second_identical_op_in_exempted_function_fails; equivalence 80/80
WP05Inert-slot retirement (#3026, #3962)FR-010— (#5117 filed)Mtest_load_baseline_rejects_retired_keys (RED: base parser accepts owner:/provisional:/mission:/code_only_suppressions:); baseline 38→36; survivor suite green
WP06Baseline leaf enforcementFR-011, NFR-003WP05Mtest_every_baseline_leaf_is_enforced_by_a_size_ratchet (RED: 4 leaves) + lower-below-live mutations; hand-kept key lists derived
WP07Surface-resolution converter retirementFR-008, FR-009—SCharter exception (D-OP-4, DM-01M3F3T1G2RYW7P0ZVWQS41GEZ): tracer-recorded base evidence (audit.py exit 1, 8 missing + 8 ghost; rekey_inventory.py --check STALE) with a reviewer reproduction block; survivor test_single_mission_surface_resolver.py 8/8 GREEN from _surface_resolution_scan; path-qualified token search 0
WP08Runtime-parity bans + parity residue + test_next_no_unknown_stateFR-013, FR-016—S/Mtest_rich_typer_ban_inspects_live_runtime_package (RED: 0 files) + planted violation
WP09Status parity retirementFR-014, NFR-006—MCharter exception (D-OP-4, Complexity Tracking): committed mutation script research/wp09_mutation_matrix.py M1–M10 reds a surviving or relocated test for every invariant; L407/L660 dispositions
WP10Context parity conversionFR-015—Mtest_context_markers_use_no_src_patch_targets (RED: 6 patch sites + 2 private calls); copy mirror, no symlinks
WP11Bridge parity splitFR-017, C-002, NFR-004—M (separate move/format/rename commits)test_board_authority_module_does_not_import_the_oracle + node-ID set equality (16; oracle keeps 8)
WP12Parity verdict catalogFR-018, NFR-006—S (planning_artifact)catalog covers all 45 swept modules
WP13Closeout: pin exemptions empty, retire orphan gate data, format-exclude removals, follow-ups, matrixFR-003 (final), FR-012, FR-019(d), FR-020allMtest_positional_anchor_exemptions_are_pinned_empty (RED: 94 rows)

Coordination points (shared hotspots)

HotspotOwnersResolution
test_ratchet_positional_anchor_ban.pyWP01, WP13WP13 depends on WP01 (sequenced out-of-map edit)
pyproject.toml format-excludeWP07, WP09 (deletions), WP13 (rewritten files)Deletion WPs remove their own exclude lines (hunks ~1,600 lines apart); WPs that rewrite an excluded file leave the line and do not run ruff format on it; WP13 formats those files and removes their lines in one commit
_destructive_op_census.py + all three census gatesWP04 onlymerged WP (the enclosing_qualname signature change would otherwise break another lane)
_content_identity.pyWP02matching authority; WP03, WP04 depend on WP02; remaining hand-rolled matchers → follow-up (FR-019d)
_baselines.yamlWP06 (owner); WP05 edits its three inert leaves as a sequenced out-of-map editWP06 depends on WP05

External coordination

  • #4506 (repo-wide reformat, re-claimed 2026-09-22): conflicts with five WPs if it lands mid-mission; the operator should sequence it relative to this PR.
  • #4727 owns an os-detect exemption that WP03 migrates; #3206 kernel exemptions (FR-005); #2633 gates the oracle (#5116); #2560, #5061, #5068, #1979 adjacent, no action.

Integration tests

After lane consolidation: full tests/architectural/ (-n auto --dist loadfile), make test-fast, the WP-specific blast radius from research §G3, and research/census_rekey_equivalence.py --base 3717c7ea.

Complexity Tracking

ViolationWhy NeededSimpler Alternative Rejected Because
ATDD-First (charter C-011, spec C-001) for WP09. Operator-approved exception (stijn-dejongh, 2026-09-26, analysis finding D1). Evidence substitute: the committed, reviewer-re-runnable mutation script research/wp09_mutation_matrix.py (M1–M10), proving every invariant of tests/status/test_parity.py is caught by a surviving or relocated testWP09 is a pure deletion of duplicated invariants. Every test it keeps or relocates is already GREEN on base, so no honest RED-on-base acceptance test existsA tombstone or base-green "failing-first" test would be fake (the class #3285 removed). Dropping WP09 would leave a known 740-line scaffold of retired-subsystem tests in place
ATDD-First (charter C-011, spec C-001) for WP07. Operator-approved exception, Decision Moment DM-01M3F3T1G2RYW7P0ZVWQS41GEZ (resolved 2026-09-26). Evidence substitute: base defect recorded via spec-kitty agent tracer-append (audit.py exit 1, 8 missing + 8 ghost rows; rekey_inventory.py --check STALE), a reviewer-re-runnable reproduction block (planning-base checkout, exact commands, expected output), and the survivor test_single_mission_surface_resolver.py (8 tests) GREEN from _surface_resolution_scanWP07 is a pure deletion. #3285 deleted the gate that detected the defect, and the survivor is GREEN on base, so no honest RED-on-base acceptance test existsThe earlier import-error RED (ModuleNotFoundError for the new module name) proves only that a file was renamed, not the defect; a tombstone test is the class #3285 removed

Follow-ups outside this mission (tracked, not in scope)

  • Consolidate tests/cross_branch/test_parity.py.
  • Stale test_resolution_authority_gates.py references in docs/ADR beyond those FR-012 touches.