Implementation Plan: Mission Status contract, health, drift and ops reads
Branch: issue-5776-mission-status-contract-health-drift-ops | Date: 2026-10-06 | Spec: spec.md Input: Mission specification from kitty-specs/mission-status-health-drift-ops-01M464D3/spec.md (issue #5776, part of epic #5528)
> No new contract version (spec PR-1, C-002). info.version of contracts/mission-status/openapi.yaml stays 1.0.0-SNAPSHOT; the CHANGELOG additions merge into the existing 1.0.0-SNAPSHOT section; no tag is needed and none exists. The one existing response that changes is Project, which gains five properties; the tooling reports four of them (the non-provisional ones) as breaking, which is allowed only because no contract-mission-status-v* tag exists (OR-1). The proof is additive against the contract tree on main (FR-022), measured in research R-5.
Note: Scaffolded by spec-kitty agent mission setup-plan --mission mission-status-health-drift-ops-01M464D3 --json (scaffold_only: true, run once) and filled by hand; the command is not run again (research R-13). Companion planning artefacts in this directory: research.md (the evidence, R-1 to R-14), data-model.md, quickstart.md, contracts/operations-and-schemas.md, contracts/tool-extension-and-reader.md and the three tracer files (tracer-*.md). The section layout follows the previous slice (a) to (j); its content is derived from this spec, not copied.
Branch contract
- Current branch at plan start:
issue-5776-mission-status-contract-health-drift-ops. Planning/base branch and merge target recorded by the tooling: the same branch (branch_matches_target: true). Topology inmeta.json:lanes. - Merge-base of the branch with
origin/mainat plan start:b61d1fa336478dae023a2d82808fef0d811816c3, an ancestor oforigin/main(checked).origin/mainhas since moved: a dry-run fetch shows a newer tip and the GitHub compare API lists 133 commits beyond the local ref (research R-1: none undercontracts/ortests/contract/; several in files the reference reader imports). The ref was not fetched by this phase (fetching moves the base ref the cut-over guard reads). D-0 (orchestrator, Step 0, before any work package is dispatched; plan-round ruling 3): fetch, bring the branch level with the new tip by a merge oforigin/maininto the Mission branch (history is compacted and rebased at W-5 and W-6), re-run the symbol check of the quickstart, re-run the gate files, classify the upstream delta on the files of research R-1, and take the baseline of the Baseline section in a synced environment. Evidence recorded before D-0 is labelled "plan-time". - Integration branch for the finished work:
main, through one pull request, opened as a draft after the wrap-up sequence. Work packages never push and never open a pull request; implementers and the orchestrator never merge; nothing is pushed tomain(charter, Agent Push Authorization). - Lanes are computed, not chosen:
compute_lanes(src/specify_cli/lanes/compute.py) unions every pair of code work packages whoseowned_filesoverlap into one lane; a dependency only becomes an edge between lanes. The plan states dependencies and write scopes and the expected computed outcome (section "Lanes"); it names no manual merge point. - Wrap-up order and which evidence is final (details in "Wrap-up checklist"): accept, local lane consolidation, registration and scope checks, JVM replay confirmation, dev-assist cleanup, issue-verdict evidence, aggregate adversarial squad, the terminal #5776 verdict (after the squad), history compaction, rebase onto upstream
main, final evidence, draft PR. The tree changes at compaction and at the rebase, so only the evidence of W-7 is cited as final; earlier records are labelled pre-rebase.
Operator decisions
None open. The rulings OR-1 to OR-10, round 2 to round 5, the precedent rulings PR-1 to PR-5 and the plan-round operator rulings of 2026-10-06 (spec Clarifications, "Operator rulings (2026-10-06, plan round; binding)", rulings 1 to 7) bind (C-001) and are not re-opened; the plan applies each as the chosen option. The places where the spec leaves a call to the plan, and the two points that go beyond the spec's file set, are decided below with their reasons so the operator can contest them (none is a fork with two defensible answers; each is the precedent applied):
| ID | Call | Decision | Reason and cost |
|---|---|---|---|
| PD-1 | Number and home of the new test modules | Three new test modules (test_mission_status_project.py, test_mission_status_drift.py, test_mission_status_ops.py), five new helper modules (_mission_status_memo.py, _mission_status_project.py, _mission_status_drift.py, _mission_status_ops.py, _mission_status_oracles.py); the reality check stays in its module. Contract-level proofs extend the existing proof module of the previous slice. | The same layout the previous slice's operator ruling chose (three focused modules over growing modules of 1,000 to 2,100 lines). Each new module needs a registration pair (PD-2) and selects the heavy architectural battery on this PR (PD-2). The fallback (put every test in existing modules) is rejected: test_mission_status_reality.py is already 2,099 lines and would run these tests serially in the 10-minute corpus job instead of the -n 4 tool job. |
| PD-2 | Registrations outside the spec's slice file set | Each new test module adds one --deselect entry to the one-line built-in-corpus-suite command of .github/workflows/packs.yml and one row to _CORPUS_MARKED_MODULES of tests/architectural/test_ci_corpus_trigger_completeness.py (three pairs). .github/workflows/ci-router.yml gains **/... entries in its contract_tools group for the new src/ imports. .github/ci-module-registry.yml and .github/workflows/contracts.yml need no edit. | The spec's item (5) names ci-router.yml, ci-module-registry.yml and contracts.yml only; the previous slice proved the two registrations necessary and sufficient (the corpus-trigger completeness test and the same-tier uniqueness test go red without them) and the operator acknowledged them as a scope extension. Cost, stated once: editing either registration file selects the architectural-heavy battery on this PR through the router groups ci_config and architectural (CI-owned; research R-7). Operator-acknowledged (plan-round ruling 7, 2026-10-06): the widened registration file set is approved, not merely requested; SLICE_ALLOWED lists every such file (packs.yml, test_ci_corpus_trigger_completeness.py, ci-router.yml; see IC-06). No alternative keeps the slice file set and the new modules together. |
| PD-3 | Where Project change, memo and Project builder land | One vertical work package (IC-03): the Project schema change, the memo helper, the new Project builder and the ratchet edits of the v1 payload and reality tests. | The Project change turns two v1 tests red (the derive_project equality of two keys and the reality check's Project build); a contract-only package would leave a red tip. |
| PD-4 | New root tags | Drift and Ops are added to the tags list of openapi.yaml (FR-008, FR-016 name them; the file-set item (2) allows "path map, tags"). The scratch baseline of the additive proof removes the two tag entries together with the two path keys. | The spec's bundle comparison (FR-022 item 2) removes "the new operations and schemas"; a new root tag is neither, so without this the comparison would be unequal. See "Departs from the spec" 2. |
Summary
Deliver the last read slice of the Mission Status contract: three Projects-screen, Drift-screen and Ops-screen reads, read-only, in the same contract tree and release as the previous slices. GET /project gains five properties (specKittyVersion, schemaVersion, health, currentBranch, lastActivityAt); a new GET /drift (getDriftReport) reports three kinds of finding (snapshot_disagrees_with_event_log, snapshot_or_event_log_missing, lane_branch_missing; the fourth, derived_view_stale, is decided not shipped, FR-013); a new GET /ops/invocations (listOpsInvocations) pages the Op invocation records with a profile filter, an honest totalCount, a provisional skippedCount, closure by the spine, and redacted or withheld evidence. The proof has four legs: a test-tree reference reader (five helper modules) that implements every rule over this repository's own Missions and Ops and over synthetic repositories; the reality check extended with independent oracles, named lists, floors and a read-only fingerprint; the additive proof against the contract tree on main; and the JVM replay (J-1) once the shape is final. No file under src/ changes (C-006, NFR-007); the service is #5528.
Technical approach in one paragraph. The contract lands first, in the order of risk (the Project vertical, which is the only change to an existing response; then the Drift and Ops contract slices; then the proof edits), so that J-1 runs before any reader is built on the shapes. The readers then follow in order of risk (drift, ops, oracles and the reality extension). The coordination resolver is wrapped once, in a neutral memo helper keyed by (resolved repository root, Mission directory name), so the project-wide scan, every per-Mission report, the Project build and the oracles share one resolver pass and the subprocess count is observable; nothing in the plan or the tests reasons about the resolver's internals beyond the outcomes the spec fixes (R-9 of the spec; research R-9). The new tests run in the router job tests (contract tools); the reality extension runs in tests (corpus-blocking). No new CI job.
Technical Context
Language/Version: Python 3.11+ for contracts/tools/ and tests/ (CI runs 3.12; the checkout .venv is 3.11.15; ruff target py311); YAML (OpenAPI 3.1). Primary Dependencies: Locked Python dependencies only (pyyaml, jsonschema with referencing, pytest; none new; pyproject.toml and uv.lock untouched, C-007). The pinned oasdiff and vacuum binaries of contracts/tools/pins.json are CI-only and used locally through install_tools.py into a scratch directory. Storage: Files only. Committed: new contract files, examples, the CHANGELOG additions, three frozen pre-slice copies of the Project files, one pinned fixture of named corpus anomalies. Never committed: the bundle, any scratch baseline. Testing: pytest in tests/contract/ (router jobs tests (corpus-blocking) for the reality check and tests (contract tools) for everything else); planted-negative registry contracts/tools/negative_cases.json run by the Contracts workflow job negative-tests. Target Platform: Linux CI (ubuntu-24.04); contributors on Linux, macOS, Windows for the Python tools. Project Type: Single repository; contract files and tooling under contracts/, tests under tests/contract/; three CI files for registrations. Performance Goals: NFR-001 (corpus job gains at most 120 seconds; one scan at most 120 seconds), NFR-002 (bounded reads; 5 seconds for the real 480-file directory, 30 seconds for a 10,000-file one), NFR-008 (256 KiB redaction in 0.1 second), NFR-009 (one Project build at most 120 seconds), all with the remotes reachable. Constraints: C-001 to C-009; the slice file set of the spec's Terminology plus the two registration paths of PD-2; public repository: no absolute path, user name, e-mail address or private reference in any file. Scale/Scope (re-measured 2026-10-06, research R-2): 569 directories under kitty-specs/ (536 with both status files, 31 with a log and no snapshot, 2 with neither), 489 lanes.json (1 legacy-shaped), 51 Missions declaring a coordination branch (14 merged, 37 not merged with the branch absent locally), 480 Op files (473 served: 467 closed in their own file and 6 closed by the spine; 7 legacy completions skipped), 6 spine records, 12 distinct profiles.
Charter Check
GATE: passed before research; re-checked after design (second column).
| Charter rule | Pre-design | Post-design | Plan element |
|---|---|---|---|
Single canonical authority (DIRECTIVE_044) | pass | pass | One resolver wrapper (the memo, D-P2) shared by every consumer; the strict sibling of the v1 helper lives in one module and the v1 helper stays the control; one predicate for the artifact-path rule (the scanner's malformed_artifact_path, imported not copied); the leak patterns stay in leak_patterns.py; the closure rule is implemented once per reader and checked by an independent oracle. |
Architectural alignment (DIRECTIVE_001) | pass | pass | Seam: no src/ change, no new import direction; the reader composes public product readers from the test side. See "Seam". |
| Tiered rigour | pass | pass | The contract, the redaction/withholding rules, the 500/refuse rules and the readers are core and get planted-violation proof and mutation tests; registrations and fixtures are glue. |
| ATDD-first (C-011) | pass | pass | The first commit of every work package is its failing test (with a stub entry point that raises, so a red is a behaviour failure and not an import error); red on the work package's base, green on its tip; the reviewer verifies red then green. Declared exceptions: IC-01 (measurement and a behaviour-preserving campsite). |
| Terminology (Mission, never feature; status lane versus code lane) | pass | pass | No new field, parameter, route or flag holds feature; the one accepted literal feature_slug (the legacy-shape test) is pinned by a scan test (AC-VERSION). Descriptions say which sense of lane they use. |
| Standing Order 1 (adversarial squad) | n/a | requested after this point-cut | Advisory; the orchestrator runs it; wrap-up W-4 runs it again over the aggregate diff. |
| Standing Order 2 (campsite first) | pass | pass | One distinct behaviour-preserving opening commit on a counted duplicate in the file the slice will rewrite (section Campsite-clean). |
| Standing Order 3 (tracer files) | pass | pass | The three tracer files are seeded now; the work packages report, the orchestrator appends, IC-10 assesses (Record-keeping). |
| Standing Order 4 (test remediation, red-first) | pass | pass | Red-first through the new entry points; the three kinds of revert proof (base-red, planted pair, named mutation) are in "Test strategy"; no retry-to-green anywhere (a red run is classified first; the bins are in the Baseline section). |
| Standing Order 5 (non-vacuous gates, priced debt) | pass | pass | Every new check has a floor, an independent count and a planted negative; no new allowlist and no ratchet entry (the empty-allowlist invariant holds for everything added); the three registrations are the only data lists extended, each with its module. |
| Standing Order 6 (canonical sources) | pass | pass | Canonical plan template; no generated file is hand-patched (section Generated artefacts); the frozen Project copies are produced by git show, never typed. |
| Standing Order 7 (git and workflow) | pass | pass | One PR, draft, the operator merges; no version number in scope (PR-1). |
| Standing Order 8 (mission hygiene) and Tracker Ticket Assignment Rule | pass | pass, with named owners | Reviewer and implementer are distinct roles. Issue-matrix rows, claim and tracker comment are an orchestrator duty, not a work package: at D-0 the orchestrator assigns #5776 to the Human-in-Charge and posts the claim comment naming the Mission slug and mid8; the rows of issue-matrix.json are set with spec-kitty agent issue-verdict before the first approval and #5776 is moved to its terminal verdict after the squad (W-4), not before it; the closing tracker comment follows the draft PR. Ownership-map leeway: write scopes overlap only where declared, every overlap is a dependency chain. |
| Standing Order 9 (red-main and release discipline) | n/a | n/a, watched | No product red was found on the base (research R-6): the only reds are environmental (a stale venv; class 4 of the baseline-red rule). The Mission never reverts or green-washes an honest red; the draft PR is held, never merged red. |
| Pre-existing Failure Reporting Rule | pass | pass | The 17 errors seen at plan time are ModuleNotFoundError for a declared and locked package in the hand-built .venv (stale-venv false red). The orchestrator's Step 0 re-takes the baseline in a synced environment before any dispatch; if any of the 17 survives a full sync it is reported in a tracker issue (command, summary, why pre-existing) before work continues. |
NO_FULL_HEAVY_SUITES_IN_MISSION | pass | pass | Only named gate files run in mission work (the gate set lists them); tests/architectural is never swept locally; the heavy battery and architectural-fast are CI-owned. An explicit operator request can extend that for the registry-touching work packages, as in the previous slice; the plan does not assume it. |
| Privacy of the public repository (C-005) | pass | pass | Every artefact in this directory is scanned line by line with the repository's own human-pattern scanner before commit (section Upgrade chain, item 3); scratch paths and commands use placeholders. |
Drift flagged (charter wins). CLAUDE.md says branch protection and review requirements enforce the workflow; the charter says nothing on GitHub enforces it and a failing run blocks nothing by itself. The plan uses the tiers of the previous plan: tier 1 means the job is a needs of the terminal router-gate, tier 2 is fleet-reported, tier 3 is local discipline.
Seam: where the change lands (a)
None of kernel, doctrine, CLI or sync. The change lands in the contract tree, the contract tooling, the test tree and three CI files:
| Surface | New or edited | What goes there |
|---|---|---|
contracts/mission-status/ | 49 new files (2 paths, 2 query parameters, 21 schemas, 3 responses, 21 examples); edited: openapi.yaml (two path keys, two tags), the four _index.yaml, CHANGELOG.md, schemas/Project.yaml, examples/Project.example.yaml, paths/project.yaml | The slice (contracts/operations-and-schemas.md). |
contracts/tools/ | fixture_builder.py (strict names, plants), negative_cases.json, enum_pins.json; leak_scan.py only if a red-first plant proves the scanner blind (not expected, see Departs 7) | FR-027, FR-023. |
tests/contract/ | five helper modules and three test modules new; the payload helper and eight existing test modules edited (test_mission_status_contract_1_1.py, test_mission_status_examples.py, test_mission_status_payloads.py, test_mission_status_reality.py, test_enum_pin_check.py, test_leak_scan.py, test_fixture_builder.py, test_run_negative_cases.py); fixtures: three frozen Project files and one pinned anomaly fixture | Readers, oracles, proof edits (contracts/tool-extension-and-reader.md). |
.github/workflows/packs.yml, tests/architectural/test_ci_corpus_trigger_completeness.py | three entries each (PD-2) | One --deselect and one registry row per new test module; chain of writers (Lanes). |
.github/workflows/ci-router.yml | contract_tools group gains **/... entries | The src/ files the new helpers import (D-P8). |
Boundary rules:
1. No src/ change (C-006, NFR-007). The reader composes public read-only seams from the test side: ProjectMetadata, get_project_schema_version, MissionStatus.load, materialize_snapshot, materialize_to_json, classify_status_json (oracle only), read_lanes_json, load_meta_fail_closed, is_mission_completed (oracle) and derive_mission_lifecycle, parse_op_event, read_op_closures, closed_invocation_ids, normalise_ref (classification input), redact_emails, email_matches, SECRET_PATTERNS. It never calls the writing materialize (a severity-5 defect; the tests make it raise). A seam that would need a src/ change is escalated, not edited (C-006). The coordination resolver is used through its outcomes only (spec R-9); the plan raises no task about its internals. 2. contracts/tools/ never imports pytest or anything under tests/. tests/contract/ imports the tools by file path through the existing loader. 3. Primary and coordination surfaces. Status files are read from the resolver's directory (the read directory); meta.json, lanes.json and the merge marker are read in the Mission's own directory (FR-015). The fallback Mission (the declared coordination branch is gone) is read from its own directory and named on the reader's result. 4. Name collision to avoid. src/specify_cli/contracts/ is the CLI's shared-contract registry; unrelated to the top-level contracts/ tree and to this directory's contracts/ planning notes. 5. Helper naming. Every new helper module has mission_status in its file name: tests/ci/test_contracts_workflows.py derives the src/ import list of the router group from tests/contract/mission_status.py; a helper named otherwise would escape it (research R-7).
Generated artefacts: what is generated, by which command (b)
Hand-patching a generated file is forbidden. This Mission touches or creates:
| Artefact | Generated by | Committed? | Handling |
|---|---|---|---|
The bundle openapi.yaml and its checksum | contracts/tools/bundle.py (Python resolver; two builds compared) | No (the layout check fails on a tracked bundle) | Never edited; written under a scratch directory for the lint, replay and comparison runs. |
kitty-specs/<mission>/status.events.jsonl, meta.json, lanes.json, tasks/, tasks.md, wps.yaml-derived files, the acceptance and issue matrices | The spec-kitty CLI | Yes | Written only by CLI commands (setup-plan appended a lifecycle event to the status log; finalize-tasks, issue-verdict, acceptance-verdict later). setup-plan is not run again (R-13). Never hand-edited. |
Frozen pre-slice copies of schemas/Project.yaml, examples/Project.example.yaml, paths/project.yaml | git show <baseline sha>:<path> into the same relative path under tests/contract/fixtures/mission_status_pre_slice/ (schemas/Project.yaml, examples/Project.example.yaml, paths/project.yaml) | Yes | The fixture directory mirrors the contract layout, so no two names in one directory differ only by letter case (a flat layout would hold Project.yaml beside project.yaml, which a case-insensitive checkout on macOS or Windows cannot materialise). Produced by git show <baseline sha>:<path> (the quickstart's byte-identity step), never typed. The committed test uses them as the pre-slice side of the scratch pair; that each copy equals the baseline commit's bytes is a recorded wrap-up command (git show <baseline sha>:<path> compared with the copy), because a committed test cannot name a baseline commit that exists on every clone. |
contracts/tools/negative_cases.json, enum_pins.json | Authored (one case per new strict-name kind; one pin per new enum) | Yes | Plants are built at run time; no leaking text is committed. The pin is the contract's enum value set. |
Derived inventories (tests/release/pinning_rule_inventory.json, docs/development/docs-retrieval-index.yaml, the page inventory), agent command copies, glossary and contextive files, DRG, pack manifest | their own scripts | n/a | Not touched: no template, skill, doctrine artefact, glossary entry, workflow script or documentation heading changes. spec-kitty regen --check is unaffected. |
tests/contract/fixtures/mission_status_health_drift_ops_expected.json | Authored (named corpus anomalies: the 7 legacy Op completions, the one legacy manifest, the Missions of the independently derived fallback list are NOT pinned because they are clone-dependent) | Yes | Beside the existing pinned fixtures; subset check, never an exact list. |
.kittify/derived/ | spec-kitty materialize and the status refresh hook on an operator machine | never (gitignored) | The scan never reads it (FR-013); no work package creates it except a scratch fixture under a temporary directory. |
Contracts: what moves and what is preserved (c)
| Contract | Verdict | Reason |
|---|---|---|
Every existing path, schema, parameter and response file of contracts/mission-status/ except schemas/Project.yaml, examples/Project.example.yaml and paths/project.yaml (description only), the four _index.yaml, openapi.yaml (path map and tags) and CHANGELOG.md | Preserved, byte-identical | FR-022 item 1; proved by the committed pure test over the extended scratch pair and by the recorded wrap-up diff against the baseline commit. |
contracts/_shared/ | Preserved, untouched | Nothing is admitted to it (the new pieces describe one module's resources). |
Project (GET /project, getProject) | Changed, additive under 1.0.0-SNAPSHOT: five required properties, closed schema kept; health provisional | OR-1. The tooling counts four of the five as breaking (spike, research R-5: lowered-major baseline exits 0 with breaking=4 provisional_changes=1; a same-major baseline fails BREAKING_WITHOUT_MAJOR naming specKittyVersion, schemaVersion, currentBranch, lastActivityAt). The previous slice's breaking=0 does not carry over. |
New: getDriftReport, listOpsInvocations and their schemas, parameters, responses and examples | New, additive | Same unreleased release; Drift and Ops tags are new (PD-4). Provisional elements per FR-023's table. |
contracts/tools/ scripts | Extended (three files) | STRICT_FIELDS, plants, pins; the scanner and the other tools are unchanged. |
Doctrine, mission step contracts, orchestrator-api, the events packages, the status event schema, the CLI's own Ops reader (OR-5: its closure gap is fixed separately, out of scope) | Preserved | Nothing under src/ or packs/ changes. |
| CI workflows, router groups, module registry | ci-router.yml gains contract_tools globs and, by the operator ruling after the pre-merge squad (2026-10-07), kitty-ops/** in the corpus group (with _CORPUS_DATA_ROOTS and the scrub copy), and the corpus-blocking command gains -rs; packs.yml and the corpus-trigger registry gain three entries each (PD-2); contracts.yml, ci-module-registry.yml unchanged | No new job; costs in PD-2. |
Upgrade chain, migration impact and reflexivity (d)
Upgrade and migration chain impact: none. No file under src/ changes, so no migration, upgrade step, template, agent command copy, skill or schema version moves; nothing is installed into a consumer project (contracts/ is outside the wheel). src/specify_cli/migration/schema_version.py is read, never written; Project.health reflects the serving build's schema range (FR-004).
Reflexivity: Missions in flight when this lands.
1. Runtime. Unchanged; the contract describes a service that does not exist yet (#5528). 2. CI coupling, now with the network. The reality check already reads every Mission on every corpus-selected PR. After this lands it also builds Project, the project-wide drift report and every per-Mission report through the coordination resolver, which for the 37 fallback Missions runs git ls-remote --heads probes (spec FRESH3-001). A required CI job therefore depends on the remote being reachable: an unreachable remote is detected by the check itself and gives the named skip (never a silent pass); the scan itself stays a 200 with no fallback entry when a remote is unreachable (operator ruling at WP07, 2026-10-06), so an intermittent failure mid-scan may show as a fallback list that disagrees with the independent derivation, and that is a red job. The rule for such a red: classify first (environment, contract gap, reader defect or data defect), name the Mission and the resolver outcome, at most one diagnosed rerun; never retry to green. A data defect in an unrelated non-completed Mission (a corrupt meta.json, a malformed lanes.json) reddens a stranger's PR by design (AD-10, R-2); a completed Mission's frozen manifest never does (FRESH-007). 3. This Mission is part of its own corpus, in flight and once merged. While in flight its directory holds status.events.jsonl and, until the status is bootstrapped, no status.json: it is itself a snapshot_or_event_log_missing finding; its non-completed lanes.json (written by finalize-tasks) must be valid, because a malformed one is a project-wide 500. The plan therefore keeps this directory free of anything the readers refuse: valid UTF-8, no NUL, no file over 256 KiB, no symlink, no credential, no yaml-fenced block in contracts/*.md (the round-trip gate collects them), and no text the human leak patterns flag. That is checked, not assumed: leak_patterns.leak_codes(line, HUMAN) over every line of every file of this directory (the reviews/ directory and the status log excluded) returns zero hits at this revision (research R-13 gives the command), and the scan is repeated by IC-10 and at W-7. No check asserts "zero findings" or an exact count, only floors and equality with an independent oracle (AC-REALITY). 4. Readers that write. The corpus fingerprint of kitty-specs/ and kitty-ops/ (bytes plus directory names) is equal before and after the run; writers (materialize, InvocationWriter, append_to_index, file creation) are replaced by ones that raise (NFR-003). 5. Reader drift is not selected before merge (accepted, as v1): a PR that edits a status reader selects module shards and, through the new contract_tools globs, the tool job, but not the corpus job; the push-to-main run catches it. 6. History and a moving upstream. The branch is merged with origin/main at D-0; the wrap-up compacts and rebases (W-5, W-6); W-7 re-verifies every cited symbol (the quickstart script), re-measures the floors and repeats the comparison against the main tree. Counts that move with the tree (Missions, Ops, fallbacks) have floors set below them and no pinned totals. 7. kitty-specs/ is archive-frozen. This Mission modifies no file already on main (C-004); its own files are add-only on the branch (the add-only check is a recorded wrap-up command). tests/architectural/test_archive_root_byte_identical.py (job archive-freeze) is a named gate file. 8. The v1 pass stays. The unchanged v1 reality test keeps its own resolver pass over the corpus beside the new memoised pass (FR-015 Memo); the corpus job therefore pays two passes, which NFR-001's 120-second gain bound accounts for.
The gate set (e)
Verified on this checkout from .github/workflows/ and the Makefile (research R-6, R-7). Tier as in the Drift note under the Charter Check.
Existing gates, re-verified
| Gate | Where | Applies? | Reason or how |
|---|---|---|---|
make lint | Makefile: ruff check src/ | No | It covers src/ only and this slice changes no src/ file; it cannot see the diff. The real lint is the whole-repo pair below. |
ruff check . and ruff format --check . (hard-enforced; the formatter is a separate gate) | ci-router.yml job ruff | Yes | New Python in contracts/tools/ and tests/. Rules in force: E F W C90 C4 ARG B SIM UP ASYNC S TID251 and the PT set; line length 164; complexity ceiling 15; tests/ relaxes ARG, S, E402, contracts/tools/ does not. Per-file: ruff format --check --force-exclude <files>. Not runnable in the checkout .venv (no ruff, research R-6)**: the orchestrator's Step 0 obtains the locked version (0.15.12) in a scratch environment before any dispatch; until then CI is the first signal, which the baseline step must not accept. |
| mypy | no workflow job (make typecheck is two src/ files); two architectural-fast files shell out to python -m mypy --strict over their own exempt modules | No (for new code) | New code is annotated to pass --strict as local discipline only. The home-pin files need mypy installed, which the checkout .venv lacks (research R-6): a stale-venv red, not this slice's. |
| TID251 import boundaries | ci-router.yml job import-linter | Yes | ruff check --select TID251 .; hashlib.sha256 is banned (the fingerprint uses blake2b, as v1). New helpers import specify_cli and kernel symbols from the test side only. |
| Terminology guard | job terminology (tests/architectural/test_no_legacy_terminology.py) | Yes | Cheap; new prose lands (two retired terms only; Mission versus feature is review-enforced, backed here by a new scan, AC-VERSION). |
| Router derivation and corpus-trigger gates | tests/architectural/test_ci_corpus_trigger_completeness.py, test_same_tier_uniqueness.py, test_no_duplicate_suite_execution.py, test_module_shard_registry.py, test_workflow_coherence.py; tests/ci/test_contracts_workflows.py, test_contracts_routing.py, test_corpus_blocking_home.py | Yes, named files | The first two go red for a new corpus-marked module without its two registrations; test_contracts_workflows.py pins both the contract_tools globs and "every tests/contract module has exactly one home". Plan-time: 352 passed, 1 skipped, 17 errors (all stale-venv: respx missing breaks the universe collection of test_same_tier_uniqueness.py and test_corpus_blocking_home.py), so the two gates that guard the registrations cannot run in this checkout until the environment is synced (risk P-2). |
architectural-fast | job of the same name (10-minute timeout, -n 4, every pull request); roster fast_gate of .github/ci-module-registry.yml | Yes | Of its roster, three files read the whole test tree and can fail on a new test module: test_no_manual_global_state_mutation.py (manual global state), test_no_tmp_paths_in_tests.py (a shared-temp literal anywhere under tests/; the evidence-class plants for absolute-path values are the natural place to type one) and test_issue_named_test_census.py (issue-named file names; none of the new modules is issue-named). Real-git fixtures, a subprocess stub and a counting wrapper in the new modules are what the first scan inspects. Plan-time baseline: the eight files of the first quickstart run: 167 passed, 4 failed (every failure is No module named mypy: the checkout .venv lacks mypy, a stale-venv red). No allowlist or baseline entry may be added to make a new module pass; tests use monkeypatch and a scratch HOME. |
Census files owned by architectural-heavy that read tests/ | architectural-heavy battery (CI-owned, 30-minute timeout); not in the fast_gate roster: the registry excludes test_no_dead_symbols.py and test_dead_symbol_allowlist_contract.py from it on purpose, and lists no home-pin file | Yes, as single named files | The census of the home pin (test_spec_kitty_home_pin_census.py, _guard.py, _budget.py, test_home_pin_gate_verdict.py), the sealed global-state allowlist (test_global_state_allowlist_sealed.py) and the two clock bans (test_clock_import_ban.py, test_clock_call_ban.py, which scan src/, tests/ and scripts/: a default of the wall-clock call in a reader that takes an injected clock would be red) are heavy-battery files, so on this PR CI would otherwise be their first signal. They run locally as named files, never a directory sweep (the NO_FULL_HEAVY_SUITES_IN_MISSION rule holds). The two dead-symbol files walk src/ (tests are not counted as callers) and the slice adds no src/ symbol: they stay in the baseline run, not in the per-module list. The named-file list is derived, not remembered: at the orchestrator's Step 0 and again at W-1 the list is re-derived from the fast_gate roster (the scanners rooted at the tests directory) plus the heavy files above, and every path is checked to exist (quickstart, "Architectural files that read tests/"). |
layer-rules | job of the same name | Yes | test_layer_rules.py and test_pyproject_shape.py; the slice adds no src/ import, but the helpers import specify_cli and kernel symbols. Plan-time baseline: 83 passed in 25 s. |
archive-freeze | job of the same name (test_archive_root_byte_identical.py) | Yes | The Mission directory is add-only; run at IC-01 and at W-1 and W-7. |
architectural-heavy battery | job of the same name (two shards, 30-minute timeout) | CI-owned | Selected on this PR by the two registration edits (router groups ci_config through packs.yml and architectural through the registry file). A red there is compared with the latest main run first (R-7). Not run locally in mission work. |
| Tool tests and corpus job | tests (contract tools) (15-minute timeout, -n 4, uv sync --frozen --no-install-project), tests (corpus-blocking) (10-minute timeout) | Yes | The homes of the new tests (D-P5). Plan-time baselines: tool selection 1,778 passed, 37 skipped in 176 s locally; reality plus payload helper 819 passed in 108 s locally. |
Module test shards (ci-modules.yml and module-tests.yml, the per-module matrix, coverage artefacts) | ci-router.yml module lanes, selected by src/ path groups | No | A diff with no src/ file selects no module shard; the module matrix deselects the corpus marker, so the tests/contract modules run once, in the two router jobs above. |
| Contracts workflow gates (verify-pins, python-checks, validate-bundle, resolver-parity, lint, breaking-change, release-dry-run, negative-tests, contracts-gate) | contracts.yml, triggered by contracts/ and tests/contract/ | Yes | The slice changes their input. The workflow runs only on pull_request and push to main (no workflow_dispatch), so no job runs before the draft PR: the JVM-only jobs are replayed by the orchestrator once the shape is final (J-1, FR-028). The breaking-change job finds no tag and prints NO_BASELINE_INITIAL_VERSION; it proves nothing about additivity (the recorded comparisons do). |
| Commit-message lint | commit-msg job | No (cannot fail) | The job only prints the subjects (` |
| markdownlint | markdownlint job | No (cannot fail) | Ends in ` |
docs-lint (spelling, changelog style) | docs-lint job | No | Its roots are docs and packs/built-in; no file there changes (the module CHANGELOG is under contracts/; no root changelog entry, Departs 9). |
uv lock --check, wheel build, clean-install-verification, regen --check, packaging safety | CI | No | No dependency, pyproject.toml, src/, packs/ or lock change; C-007 is proved by a committed scope test (AC-CROSS). |
| Contextive glossary traceability | tests/cross_cutting/encoding/test_contextive_traceability.py | No | No docs/context/*.md or contextive map change; the contract descriptions use the repository glossary terms (lane, mission, work package, op), and the terms the glossary lacks (invocation, drift, health) are defined in the spec and in each schema description, not added to the glossary (a glossary change would be outside the slice file set). |
Architecture and documentation consistency (test_docs_cli_reference_parity.py, the lifted-docs gates, regen --check, the single-seam gates such as test_cli_error_surface_seam.py and the load_meta call-site census) | tests/architectural/ (architectural-fast, regen-check) | No (except through the CI-owned census) | They scan src/, docs/ and the CLI surface; none of those changes. The census scans that read tests/ are the global-state and home-pin files listed above, which are named. |
Schema freshness (test_completion_manifest_freshness.py: the CLI completion manifest) | tests/architectural/ | No | The slice adds no CLI command or option, so the manifest is unchanged; the contract's own freshness is the pin and citation checks, which are in the gate set. |
Typer JSON error surface (tests/agent/test_json_group_typer_surface.py and the agent-command JSON error tests) | tests/agent/ | No | The slice adds no CLI command; the service errors it specifies are the contract's Problem shapes, proved by the examples and the reader tests. |
patch() target validation | none found as a named gate (searched tests/architectural, tests/ci, scripts/ci) | No | The new tests patch with pytest.MonkeyPatch on objects and module attributes (raising=True is the default, so a stale target fails at run time) and plant no string-targeted patch of a src/ symbol. |
| Bandit and pip-audit | none (they are declared dev dependencies; no workflow runs them) | No | No dependency change; ruff's S rules are the only security lint in force, and they apply to the new code. |
| SonarCloud | sonar-pr job of ci-aggregate.yml (reported, continue-on-error) | Informational | Complexity ceiling 15, repeated literals (three or more) become constants, no empty handlers, every new branch has a test in the same change. Remaining Sonar UI work, if any, is named in the PR body. |
| Coverage floors (kernel, mission loader, per-module) | none found: the only fail-under in .github/workflows/ is the diff-cover line of ci-aggregate.yml (90), and the module registry names no floor | No | No coverage floor applies to this slice. The module shards that would carry coverage artefacts are selected by src/ path groups, and a diff with no src/ file selects none; the new Python lives in tests/ and contracts/tools/, outside every floor. Said here as a finding of the search, the way the patch(), Bandit and pip-audit rows say "none found". |
| diff-cover at least 90 percent | ci-aggregate.yml | Vacuous | The scope is the hard-coded CRITICAL_PATHS of scripts/ci/aggregate_source.py; no path of this diff is in it. Coverage of the new Python is held by behaviour: every helper function has a direct test, every branch has a synthetic case, and the mutation tests prove the tests can fail. |
Release readiness cutover-guard | release-readiness.yml; locally spec-kitty cutover-guard --base-ref origin/main | Yes | This Mission's directory is in its scope; plan-time: 1 Mission touched, 0 un-cut-over. |
make test-fast | Makefile ((fast or unit) over tests/unit tests/status tests/cli tests/specify_cli/runtime) | No | The diff has no src/ change and those four directories import none of it; the target also calls uv run --frozen, which re-syncs the environment. The blast radius of this diff is tests/contract, the registry files and the named gate files above. |
New gates this Mission adds, and where each runs
| New gate | Runs in | Tier | When it cannot do its job (FR-024) |
|---|---|---|---|
Reality extension: Project, project-wide and per-Mission drift, the whole Ops walk, independent oracles with an AST independence test, named lists with stale-entry checks both ways, floors, controls, leak scan, fingerprint of kitty-specs/ and kitty-ops/ | tests (corpus-blocking) | 1 | Fails on a shortfall, a 500 outside a named list, a stale entry, zero examined, a floor miss, a generated case that did not execute; offline it takes the named skip and says so. |
| Reader tests with mutations (project, drift, ops) | tests (contract tools) | 1 | A fault that did not fire, a surviving mutation, zero cases built. |
Contract-level proofs in the extended test_mission_status_contract_1_1.py: frozen-copy pair, extended scratch pair (path keys and tags), the CHANGELOG qualified names, scope data, dependency check | tests (contract tools) | 1 | A surviving planted mutation; an empty change list is itself a problem (no_changes). |
Six strict-name plants (specKittyVersion, currentBranch, profileId, action, invocationId, sourceCode) | tests (contract tools) and the negative-tests job | 1 and 2 | A blind scanner fails its own self test; a control that is flagged fails (CONTROL_FLAGGED). |
| Enum pins for 13 new enums | python-checks job, tests (contract tools) | 2 and 1 | A changed value set fails; a pinned title absent from the module exits 2. |
| J-1 record check | recorded wrap-up commands (AC-VERSION) | 3 | The cited commit must exist, be an ancestor of HEAD and be the last commit touching contracts/mission-status; one line per job of contracts.yml, none missing and none invented. |
Where this plan departs from, or adds to, the spec's gate analysis
- Added gates the spec's gate analysis does not list:
architectural-fast,layer-rules,archive-freezeand the separateimport-linterjob, each a router-gate input on every pull request (rows above). make lintis not a gate for this slice (it readssrc/only); the spec's phrase "lint" means the whole-repo ruff pair.- The two gates that guard the registrations are blind in this checkout until the venv is synced (R-6): not a departure from the spec, a fact the baseline step must close before the first module is registered.
Baseline: pre-existing red versus introduced red (f)
Rule. The charter's Pre-existing Failure Reporting Rule: a pre-existing failure is reported in a tracker issue (command, failure summary, why it is believed pre-existing) before it is treated as accepted baseline; it is never absorbed or blamed on this Mission without a base measurement. Every red is binned first: (1) pre-existing known-P0 (nightly p0-repro lane only), (2) CI-environment, (3) stale install, (4) stale venv, (5) introduced. Only a failure red on the branch and green on the base is this Mission's.
Taken during planning (tree identical to origin/main at b61d1fa33 for every file under test; results in research R-6): the ten contracts/tools checks all exit 0 (counts: lines recorded); the nine named gate files 352 passed, 1 skipped, 17 errors (bin 4, stale venv: respx is declared and locked but not installed in the hand-built .venv); the tool-job selection 1,778 passed, 37 skipped; the reality module and payload helper 819 passed; the eight census files of the plan-time selection (global state, sealed allowlist, home pin, verdict, dead symbols; most are heavy-battery files, see the gate set) 167 passed, 4 failed (bin 4: mypy is not installed in the .venv); layer-rules 83 passed; archive-freeze (the archive-root byte-identity file) 25 passed in 8 s; cutover-guard 0 un-cut-over; ruff, TID251 and the formatter could not run (ruff absent, bin 4). No product red was found.
To be taken by the orchestrator as Step 0, before any work package is dispatched (plan-round ruling 3), and recorded by the orchestrator in tracer-approach.md. No work package, including IC-02 beside IC-01, starts before this record exists, so every red a work package meets can be binned against a measured base:
1. D-0 sync of the branch (Branch contract) and a recorded git rev-parse HEAD. 2. A synced environment that has ruff and the test extras (uv sync --frozen --all-extras into a scratch environment or by the operator; never uv run in the hand-built .venv), then the same commands as the plan-time baseline, plus ruff check ., ruff format --check ., ruff check --select TID251 ., the named census files (see the gate set), the layer-rules files and the archive-freeze file. The 17 errors must be gone; if any survives, issue first. 3. The latest main run of architectural-fast and architectural-heavy (job conclusions and durations), so a red there on this PR is compared with it before it is attributed; and the median duration of the last five successful main runs of tests (corpus-blocking) and of tests (contract tools) (with their timeouts, 10 and 15 minutes), the baseline of NFR-001's gain bound (D-P5, W-7). 4. The named census files of the gate set (the roster-derived list and the heavy files), run as single named files, so a red there is binned before the first module is written.
IC-01 starts from this record. It keeps the campsite re-count and the re-measurements (research R-2 to R-4, kind 3).
Campsite-clean (g)
Opening commit (distinct, behaviour-preserving, tidy-first): refactor(tests): name the repeated scope-data literals of the 1.1 proof module once (#5776).
- Surface and debt (counted, not from a Sonar report).
tests/contract/test_mission_status_contract_1_1.py(1,073 lines) holds 15 string literals of twelve characters or more used three or more times (the SonarS1192class), among them"openapi.yaml"(7 uses),"byte_identity: "(5),"leak_patterns"(5),"out_of_scope: "(4),"contracts/tools/leak_scan.py"(4) and".github/workflows/packs.yml"(4). FR-029 rewrites exactly this file's scope data (SLICE_ALLOWED,EXTRA_ALLOWED_REGISTRATIONS,IN_SCOPE_CHANGES,REGISTRATION_CHANGES,NEW_PATH_KEYS,CHANGED_ALLOWED), so the duplicates are domain-matched debt in the file about to change. - Change. Hoist the repeated literals that sit in the scope, pair and proof-message data into named module constants, and replace their uses. Nothing else is tidied.
- Behaviour-preserving proof. The same test ids pass before and after with identical counts (the module's own count is recorded at IC-01); every assertion is unchanged.
- Write scope. That one test file; the re-measurements and the baseline are reported to the orchestrator, which appends them to the tracer files.
- Drop rule. After the D-0 sync the literals are re-counted; below three uses each, the opening commit is none, and the PR body and the tracer say "no domain-matched debt found in the edit surface".
- Not a grab-bag. The 1,365-line payload helper and the 2,099-line reality module are not split here (the new builders live in new modules); no
src/debt is folded (NFR-007).
Project structure
Documentation (this mission)
kitty-specs/mission-status-health-drift-ops-01M464D3/
├── spec.md # specify output (done; two nits fixed by the plan phase)
├── plan.md # this file
├── research.md # evidence ledger R-1 .. R-14
├── data-model.md # entities, derivation rules, decision orders, invariants, floors
├── quickstart.md # commands to run every check locally
├── contracts/
│ ├── operations-and-schemas.md # the planned contract file set, schema sketches, examples
│ └── tool-extension-and-reader.md # tool edits, reader modules, mutation catalogue, test map, floors
├── tracer-tooling-friction.md tracer-approach.md tracer-design-decisions.md
├── reviews/ # spec-phase review trail (existing)
└── tasks.md # NOT created by plan (/spec-kitty.tasks)
Source code (repository root)
contracts/mission-status/
├── openapi.yaml # version stays 1.0.0-SNAPSHOT; +2 path keys, +2 tags (Drift, Ops)
├── CHANGELOG.md # additions merged into the existing ## 1.0.0-SNAPSHOT section
├── paths/ drift.yaml ops_invocations.yaml (new) project.yaml (description only)
├── parameters/ DriftMissionId.yaml OpsProfile.yaml (new)
├── schemas/ ProjectHealth DriftReport DriftFinding LaneComparison DriftKind DriftSeverity DriftAuthority DriftSide
│ DriftRemedy DriftRefusal DriftRefusalCode OpsInvocationPage OpsInvocation OpsModeOfWork
│ OpsInvocationStatus OpsOutcome OpsClosedBy OpsEvidence OpsEvidenceKind OpsRefusal OpsRefusalCode (21 new)
│ Project.yaml (edited)
├── responses/ DriftMissionNotFound DriftScanUnreadable OpsUnreadable (3 new)
└── examples/ 21 new files (contracts/operations-and-schemas.md) Project.example.yaml (edited)
contracts/tools/ # fixture_builder.py negative_cases.json enum_pins.json (leak_scan.py only if proven blind)
tests/contract/
├── _mission_status_memo.py # new: the resolver memo, subprocess count
├── _mission_status_project.py # new: the Project builder, HEAD reader, metadata reader
├── _mission_status_drift.py # new: the drift reader, strict sibling, fallback list
├── _mission_status_ops.py # new: the Ops reader, closure, evidence pipeline
├── _mission_status_oracles.py # new: independent oracles (never import the readers)
├── _mission_status_payloads.py # edited (derive_project, FLOORS)
├── test_mission_status_project.py test_mission_status_drift.py test_mission_status_ops.py # new
├── test_mission_status_reality.py test_mission_status_payloads.py test_mission_status_contract_1_1.py
│ test_mission_status_examples.py test_enum_pin_check.py test_leak_scan.py test_fixture_builder.py
│ test_run_negative_cases.py # edited
└── fixtures/ mission_status_pre_slice/{schemas/Project.yaml, examples/Project.example.yaml, paths/project.yaml} mission_status_health_drift_ops_expected.json # new (the pre-slice tree mirrors the source layout: no two names in one directory differ only by case)
.github/workflows/packs.yml # +3 deselect entries (one physical command line, one writer at a time)
tests/architectural/test_ci_corpus_trigger_completeness.py # +3 registry rows
.github/workflows/ci-router.yml # contract_tools group: **/ globs for the new src imports
Structure Decision. Reader code beside the existing projector in five small helper modules instead of growing the 1,365-line payload helper; three focused test modules; the minimum edits to shared tooling. Rejected: placing the readers under contracts/tools/ (the tools are Node-free scripts that never import src/), one large test module (PD-1), and a new CI job (D-P5).
The one pull request: size and reading order
Added lines by group. These are planning estimates, not measures: contract files use the measured v1 averages (schema about 54 lines, example about 41, path file about 37), a negative case about 23 lines, and code and tests the previous slice's ratio of test lines to helper lines; the real figures are taken with git diff --shortstat per group at W-5 and written into the PR body.
| Group | Estimated added lines | Nature |
|---|---|---|
Contract: 49 new files and the edits of Project, openapi.yaml, four _index.yaml and CHANGELOG.md | about 2,700 | mechanical apart from the two path files, DriftFinding, OpsInvocation, OpsEvidence and the CHANGELOG entry |
Tooling: fixture_builder.py, enum_pins.json, negative_cases.json (six cases) and their tests | about 450 | mechanical |
| Reference readers: five helper modules and the payload-helper edit | about 2,400 | authored logic |
| Tests: three new modules, edits of eight existing modules, the reality extension, two fixtures | about 6,000 | authored logic |
| Total | about 11,500, of which about 3,200 are mechanical | about 8,300 lines of authored logic |
Compaction shape (W-5): one commit per concern in dependency order, each commit carrying the tests of its concern, the registration pair riding in the commit that creates its module: (1) mission planning artifacts and status log (by path); (2) campsite (or none); (3) tooling extension and its tests; (4) Project: contract, memo, builder, ratchet edits, registration pair and router globs; (5) Drift contract; (6) Ops contract; (7) contract proof edits (CHANGELOG final statements, frozen copies, scope data); (8) drift reader with its pair; (9) ops reader with its pair; (10) oracles and the reality extension with its fixture; (11) close-out records. Under squash consolidation this shape is rebuilt from one tree by path splitting (W-5).
PR body, reading order and what to skip. Read in this order: the CHANGELOG entry and the two path files (the public surface); the Project schema diff and the additive-proof evidence; the tooling commit; the readers; the reality extension. Skippable as mechanical: the 21 schemas' enums and refusal schemas, the three responses, the 21 examples and the negative_cases.json block (the layout_check, citation_check, example_check, enum_pin_check and lint runs in the PR body vouch for their shape). If the measured total at W-5 exceeds 14,000 added lines, the orchestrator raises it with the operator before the PR (the repository default stays one PR; the estimate above is already larger than the previous slice because this spec has three read families).
Architecture and design decisions
Design decisions that bind the work packages
D-P1 Reader architecture. Five helper modules (PD-1). Entry points return an outcome object (an HTTP status and a body, or a typed refusal), never raise for a refusal, so each of the two operations is testable as the service answers it. An injectable file-system seam (scandir, lstat, open_binary) with counting and fault-injecting wrappers serves the bounded-read assertions (NFR-002); the reader's own code opens only the files the spec lists. No private import except the one _reset_remote_branch_lookup_cache of the offline fixture (FR-026) and none in the oracles.
D-P2 The resolver memo (FR-015, FRESH2-003, FRESH3-004). One neutral helper module, _mission_status_memo.py: an object memo created per run (a reality run or a fixture build), keyed by (resolved repository root, Mission directory name), holding the raw resolver outcome (the directory or the exception) and the number of subprocesses the call started. Consumers: the strict drift sibling (D-P3), the new Project builder (D-P4) and the oracles. A test builds two repositories with the same Mission name and different outcomes and asserts each gets its own result; a control reads one repository twice and asserts one resolver run. Counting: the subprocess count wraps subprocess.Popen.__init__ through a pytest MonkeyPatch context (never a manual global mutation, which the architectural scans flag); IC-03's first task runs the two named scan files against a stub module holding this wrapper before the helper is written (risk P-14). The FR-007 stub of subprocess.run that raises unless the memo helper's resolver call is on the stack composes with it.
D-P3 The strict drift sibling (FRESH2-004). In _mission_status_drift.py: catches CoordinationBranchDeleted only and returns the Mission's own directory with the reason coordination_branch_deleted; every other resolver exception propagates as a typed unreadable outcome (500 drift_scan_unreadable); a read directory outside the repository root raises. v1's resolve_read_dir is not edited (a ratchet file) and is the control of the plant: one test asserts, on the same three fixtures (CoordAuthorityUnavailable, MissionMetadataUnavailable, outside the root), that the strict sibling raises while the v1 helper returns the own directory with a reason, and that for CoordinationBranchDeleted both return the own directory.
D-P4 The Project builder (FR-001 to FR-007, FRESH2-002). In _mission_status_project.py: reads .kittify/metadata.yaml through ProjectMetadata.load and get_project_schema_version (never the CLI wrapper), behind a shape guard (plan-round ruling 2, PQ-7): ProjectMetadata.load applies .get to unvalidated YAML and raises AttributeError for a list or scalar at the top level, for spec_kitty or environment a list or scalar, and TypeError for a non-mapping migrations entry (only OSError and the YAML error are caught inside it). The reader therefore pre-checks the parsed shape (top level a mapping, spec_kitty a mapping), calls load only when the shape is safe, and treats a residual AttributeError or TypeError from load as null (never as a failure of the read, never a 500); load stays the reader of the version, so the spec's "read as load reads it" holds and no src/ change is needed (C-006). The guard is proved by the raising shapes planted in AC-PROJECT row 1. It applies the FR-002 table, computes health from MIN_SUPPORTED_SCHEMA/MAX_SUPPORTED_SCHEMA, reads HEAD as a file (the .git file case follows the gitdir; no subprocess) with the FR-005 name rules, and takes lastActivityAt as the latest instant over the Missions GET /missions lists. It reaches the resolver only through the memo and reproduces all four v1 fallbacks (the three exceptions and a read directory outside the checkout) on the raw outcome; it never calls v1's load_source. Equality control: on every fixture the new builder equals the v1 build (including the outside-root fixture); that is how the fallbacks are proved the same.
D-P5 Placement; no new job. The reality extension stays in test_mission_status_reality.py (router job tests (corpus-blocking), serial, 10-minute timeout); the three new reader modules and the proof edits run in tests (contract tools) (selected automatically: the job takes the directory minus three --ignore modules). Corpus-reading cases live in the reality module only (plan-round ruling 4): AC-DRIFT row 19's equality with is_mission_completed over the real corpus, and the corpus-sized cases of AC-CROSS 4 (the real kitty-ops/ listing at most 5 s; one Project build and one project-wide scan at most 120 s each), run there with a discovered-count guard (D-P13); docs/development/reference/ci-gate-mechanics.md documents the tool job as reading no corpus, and its budget carries no measured corpus time. The three new modules are fixture-built only (a temporary repository per case, each plant with its control); no docs edit is needed. Budget (NFR-001 is a gain bound): the plan-time local figure of the unchanged corpus job is 108 s and its CI duration about 3 minutes (a remembered figure of the previous slice, not a baseline), a local-to-CI factor of about 1.7. The added work is the second, memoised resolver pass (the v1 pass stays; about 37 s local), the scan's remaining reduction (about 3 to 15 s local) and the Project reduction (about 7 s local): about 45 to 60 s local before the items priced below (about 75 to 100 s in CI by that factor, a derived estimate, not a measure). Execution sharing (NFR-001 stays honest): the Project-build bound, the scan bound and the combined case are ONE execution: the combined case builds the Project and runs the memoised pass and the per-Mission reductions once and records each duration, and the two separately named bounds assert on those same measured durations (no second Project build, no second scan, so no scan-reduction or Project-reduction time is added twice). The other added work is priced separately, in local seconds: the AC-DRIFT 19 completion equality reads about 518 own directories (an estimate of about 5 to 10 s local, measured by the IC-01 proxy), and the real-directory listing is repeated at least 5 times at its expected cost of well under 1 s each, about 5 s local in all (the 5 s bound is an assertion that a failing run would trip, not a budget input, so a 25 s worst case is not priced). The job-gain-only items therefore total about 10 to 15 s local, and the local estimate is the 45 to 60 s base plus those 10 to 15 s, about 55 to 75 s, which is about 94 to 128 s in CI at the 1.7 factor (a derived estimate, not a measure). The top of that range exceeds the 120 s gain bound and the bottom is not safely inside it, so the arithmetic does not prove the bound; the IC-01 proxy and the W-7 pass rule govern. The control is not this arithmetic: IC-01 measures the proxy first, the first PR run is judged against the recorded baseline, and the 6-minute re-plan threshold below stays the explicit limit. Offline the Project, scan and combined cases are skipped and cost nothing. Baseline: the orchestrator's Step 0 records the median duration of the last five successful main runs of both tests (corpus-blocking) and tests (contract tools) (Baseline section). Pass rule at W-7: the first PR run's tests (corpus-blocking) duration minus the recorded median is at most 120 s, and tests (contract tools) stays under its 15-minute timeout with the margin recorded; re-plan if the first run shows more than 6 minutes of corpus job time (about 3 minutes baseline plus the 2 minute bound is 5 minutes; 6 leaves one minute of runner noise). In-test: no case over 120 s, and one case in test_mission_status_reality.py times the memoised pass, the Project build and the per-Mission reductions together, so the in-test bound and the job gain are different quantities: the combined case is the largest component of the gain, not the whole (the v1 pass is not part of either; the AC-DRIFT 19 completion equality and the repeated real-directory listing sit in the job gain only, about 10 to 15 s local, 17 to 25 s in CI, the same figure as above), and a combined case passing under 120 s does not imply the job gain is; the W-7 pass rule above is the arbiter of the job gain (plan-round ruling 10). IC-01 measures the proxy first, and a local proxy of the job gain above about 70 s (the 120 s bound divided by the 1.7 local-to-CI factor, stated in local seconds) goes to the operator before the bound is set (research R-12; plan-round rulings 8 and 11). The 70 s line is stated against the SUM: the combined case plus the 10 to 15 s of completion equality and listing repeats, so the combined case alone escalates at about 55 s local (70 s minus the upper 15 s); the W-7 pass rule stays the final arbiter. Markers: the drift and project modules build real git repositories and the resolver starts git, so they carry contract, corpus and git_repo and not fast (as the detail module of the previous slice); the ops module carries contract and corpus and not fast (it holds the timed 10,000-file and 256 KiB cases, which are not sub-second); every new module declares a module-level pytestmark (the class of defect of an unmarked corpus reader that is silently never run), written as one single-line list that holds pytest.mark.corpus on the same physical line as pytestmark =, in the form of tests/contract/test_mission_status_detail.py (pytestmark = [pytest.mark.contract, pytest.mark.corpus, pytest.mark.git_repo]): _CORPUS_MARK_APPLICATION_RE of tests/architectural/test_ci_corpus_trigger_completeness.py matches pytestmark\s=.\bpytest\.mark\.corpus\b within one line, so a mark spelled through a shared list or wrapped across lines escapes the registry gate (the registry row is then reported as in the registry but no longer marked) while the home gate, which collects for real, still sees it. The registry row and the --deselect go in the commit that creates the module, in sorted position. The advisory packs corpus suite must not gain these tests: each new module is deselected there (PD-2).
D-P6 Kind 3 is provisional and honest-rule gated, not floored (FR-012, R-1). The reader implements the rule exactly as written (completion first; three-way lanes.json classification for a non-completed Mission; expected lane and expected Mission-level branch; one local-ref listing, once; one finding per Mission, no branch name). The oracle shares the product's is_mission_completed and materialize_snapshot for a Mission whose read directory is its own directory and reads the two arms directly otherwise (COMPLETE-012), reads meta.json and the manifest as raw JSON, and tests each expected name with its own git rev-parse --verify --quiet. No corpus floor and no assertion rests on the old 93: the corpus is asserted equal to the oracle, and the synthetic fixtures carry the named plants (a merged marker, an all-terminal Mission, a reopened Mission, an expected and a planned-only lane, the coordination pair). IC-01's first task re-measures the counts under the final rule (research R-3); if the finding population is dominated by a class the oracle shares with the reader by construction, the honest-rule criterion cannot be met and the point goes to the operator, not to a self-ruled change. Shared product function (added after the pre-merge squad, 2026-10-07): the expected lane branch name comes from code_lane_branch_name, which the reader, the oracle and the fixtures share by construction; the product's own branch-naming tests pin the literal names, so a naming regression goes red outside this slice.
D-P7 The lanes.json classification and the 500 line. One raw JSON pre-read before read_lanes_json, only after the completion test says not completed; legacy shape (feature_slug and no mission_slug) is counted on the named list for kind 3 only; every other shape is 500 drift_scan_unreadable. The literal feature_slug appears once, in the reader's legacy-shape test, and the terminology scan pins it (AC-VERSION). The principle of the round-2 rulings is the test: a broken derived file is a finding, a broken authority a 500, an older valid shape "not evaluated" and counted.
D-P8 Router globs for the new imports. The contract_tools group of ci-router.yml names the src/ files the helpers import, spelled **/<path> so the group stays non-src. Each reader work package adds its own entries in sorted position in the same commit as the helper; test_the_contract_tools_filter_group_names_every_src_file_the_mission_status_reader_and_its_tests_import derives the required list from the imports and goes red until they are present. The expected new entries (to be recomputed by that test, never by hand): status/lifecycle.py, lanes/models.py, status/validate.py (one-way drift check only), upgrade/metadata.py, migration/schema_version.py, core/paths.py, invocation/record.py, invocation/writer.py, invocation/errors.py, git/remote_probes.py and the __init__ files the import scan adds. No edit of docs/development/reference/ci-gate-mechanics.md is needed: its paragraph on the group already says "the src/ modules the mission-status reference reader imports".
D-P9 Contract authoring decisions. All new enums are snake_case and pinned (PR-2; the vacuum enum-case rule is outside every Python gate, hence J-1). Every new property carries exactly one x-source or x-derived (field catalogue of the spec); every new schema is closed; every new operation keeps the shared Problem for default. Refusal schemas follow ArtifactRefusal (a Problem plus code and one if/then per code pinning the status). DriftFinding.sourceCode is a string matching ^[A-Z][A-Z0-9_]*$ or null; OpsEvidence.value has maxLength: 512; OpsInvocation.profileId and action use ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$. The two query parameter files are new (DriftMissionId, OpsProfile); PageSize and PageCursor are the shared ones. The unicode-escape hazard applies to every pattern with a NUL or backslash (write \x00, build chr(0) and chr(92) in tests).
D-P10 The additive proof (FR-022). (a) A committed pure test extends _make_pair: the candidate tree with the two new path keys and the two new tags removed and the three frozen Project files swapped in is the scratch baseline; CHANGED_ALLOWED gains the three Project files; byte identity holds for everything else; the bundle comparison removes the new operations and schemas and the two tags and equals the baseline bundle except Project, under this masking rule: remove components.schemas.Project from both trees, and in the /project path item drop only the description of the operation (and its summary only if the final edit changes it); everything else, including the whole of operation getProject (operationId, parameters, response codes, response schema references), must be equal. A mask that excludes the whole /project path item is wrong: it would hide a change to the one existing operation this slice touches. Planted (each must fail the comparison): a byte change in an existing schema, a 409 on an existing operation, a changed info.version, an extra response code on getProject, a changed 200 schema reference on getProject, and a change to another existing schema (control: the unmutated candidate passes with only the two permitted masks). (b) Recorded wrap-up commands (they need oasdiff, full history and scratch copies the pure-Python jobs lack): the git diff --exit-code <baseline sha> -- contracts/mission-status limited to paths outside the slice file set with its planted byte change; the lowered-major breaking_check.py --baseline-root run (exit 0, counts: ... breaking=4, provisional_changes pinned on the final tree; plan-time spike 1) with the control (an extra property on WorkPackage raises breaking to 5); the same-major run (exit 1, BREAKING_WITHOUT_MAJOR naming exactly the four, control: with the Project change reverted and the /drift and /ops/invocations operations and their two tags removed it exits 0; with the Project change reverted alone it exits 1 with BUNDLE_CHANGED_VERSION_SAME). The baseline is the full SHA of the merge-base at the run, asserted an ancestor of origin/main. (c) The existing permanent test that info.version equals the version on main stays green. The module docstring says which proofs are tests and which are wrap-up commands.
D-P11 CHANGELOG and provisional tests are section-scoped. One test reads the existing ## 1.0.0-SNAPSHOT section only: its headings, the three remaining deferred gaps (and the planted entry that lacks "lane weights" fails), the not-shipped decision of derived_view_stale with its reason, that info is not carried, that missionId and artifactPath of DriftFinding are non-null, the cap of 1000, and every FR-023 token inside the section's own ### Provisional heading. The four qualified names (DriftFinding.kind, DriftReport.truncated, DriftRefusal.code, OpsRefusal.code) are asserted literally, each with a plant that removes it, because provisional_check is word-satisfied by the previous slice's kind, code and truncated (the previous slice's tracer friction F-4).
D-P12 Read-only proof. tree_fingerprint (it already takes a sub-path and hashes bytes and directory names) runs over kitty-specs and over kitty-ops before and after the run; writers are replaced by ones that raise; a control that writes shows the probe sees writes; the reader's seam records no write mode; the existing autouse fingerprint fixture of the reality module covers the new cases.
D-P13 Floors (FR-025, spec D-P15; the completion-equality floor is added by plan-round ruling 4). Fixed here, never re-pinned, below the plan-time measurement by one stated rule (95 percent rounded down to two significant figures; counts under 100 at 90 percent rounded down; named anomaly classes by a subset check): Missions examined 540 (of 569); Ops served 440 (of 473); spine-closed Ops 5 (of 6); evidence of each kind own-file: none 330, absolute path 28, free text 50, relative reference 25, url 0 (fixture-only floors: url, a redacted address, a withheld credential); kind-1 findings 39 (of 44: 43 terminal, 1 provenance, 0 plain), kind-2 findings 27 (of 31); completion-equality guard of AC-DRIFT 19: Missions declaring no coordination branch compared 490 (of 518, which is 569 less the 51 declaring one; 95 percent rounded down to two significant figures); no kind-3 floor and no fallback floor. IC-01 verifies each floor sits below the re-measure; a floor above a re-measure is an escalation, not a re-pin. A floor loosened to one fails (a test), and floor_failures keeps the v1 shape.
D-P14 The oracles (FR-025, spec D-P14). _mission_status_oracles.py holds the independent oracles: kinds 1 and 2 (file presence plus classify_status_json filtered to the four kind-1 codes and the reader's population, with the UnicodeDecodeError wrapper unit-tested), kind 3 (D-P6), the fallback derivation (an agreement check on this corpus, not a mirror of the resolver; each resolver arm it does not mirror has a fixture), and the Ops direct scan of every Op file and of the spine. An AST test asserts that the oracle module and the oracle functions of the reality module neither call nor import any reader module (textual guards are bypassable by design and accepted at severity 1 to 2, as in the previous slice's tracer friction F-42). Shared product function (added after the pre-merge squad, 2026-10-07): the expected lane branch name comes from code_lane_branch_name, which the reader, the oracle and the fixtures share by construction; the product's own branch-naming tests pin the literal names, so a naming regression goes red outside this slice.
D-P16 Timing method for absolute bounds (plan-round ruling 6; NFR-002, NFR-008; not the spec's D-P15, which is the floors rule). The 0.1 s redaction bound (three 256 KiB shapes), the 5 s real-directory bound and the 30 s 10,000-file bound are absolute wall-clock assertions in a blocking -n 4 --dist loadfile job. Each shape is therefore timed as the minimum of at least 5 repeated runs inside the test (the minimum is robust to contention and still falsified by a quadratic matcher, whose long shape stays far over the bound on every repeat); the short-value control is timed the same way; the planted quadratic mutation (the pre-fix pattern) must still fail under this method, which the mutation table proves. A fixed repeat count of 5 is the floor, not a retry: no test reruns to green. Every repeat is cold: a fresh call of the production entry point on an identical input with no state shared between repeats (new reader state, the per-run memo and the remote-branch lookup cache reset, a fresh opener, and the directory handle rebuilt or re-opened per repeat for the 5 s and 30 s cases); the test asserts that no cache was hit (a counter at zero) or that the call count equals the repeat count, so the minimum is never a cache hit. A second planted mutation, cache-the-result (the entry point memoises its result across calls), must turn AC-OPS 14 and the timing cases red, and is also applied as a context manager in the reality module against the real 5 s listing case (plan-round ruling 9); it joins the Ops mutation catalogue beside the quadratic matcher. The work package records the measured minimum and the margin to the bound in its hand-off report (the orchestrator appends it to the tracer file), and a margin under a factor of 2 goes to the operator rather than loosening the bound. The 5 s real-directory case belongs to the reality module (D-P5); the 30 s synthetic case and the 0.1 s cases to the ops module.
Decision register: questions resolved by the plan
| ID | Question | Decision | Reason |
|---|---|---|---|
| PQ-1 | Where do the memo and the strict sibling live? | A neutral memo module and the drift module (D-P2, D-P3) | The oracles must share the memo without importing the reader (FR-015, FR-025). |
| PQ-2 | Subprocess counting without a global patch? | pytest.MonkeyPatch context around subprocess.Popen.__init__ (D-P2) | The architectural scans flag manual global mutation; the stub test composes. |
| PQ-3 | Which module owns the CHANGELOG proofs? | The existing 1.1 proof module (D-P11) | Its section-scoped helpers and its registration exist; a fourth new module would add a registration pair for no new capability. |
| PQ-4 | Does leak_scan.py change? | No (Departs 7) | STRICT_FIELDS lives in fixture_builder.py; the scanner derives from it; edit only if a red-first plant proves the scanner blind. |
| PQ-5 | One J-1 or several? | One after IC-06 (contract shape final), repeated for any later change to contracts/mission-status/ | FR-028; a reader finding a contract defect returns by a rejection transition on the owning contract work package. |
| PQ-6 | Does the offline check cover Ops and the fingerprint? | The named skip covers the resolver-dependent assertions only (Departs 5; operator ruling 1, spec edited) | A skip hides a defect for no reason where no network is involved. |
| PQ-7 | How does the Project reader serve null for metadata shapes on which ProjectMetadata.load raises? | Pre-check the parsed shape, call load only when safe, treat a residual AttributeError or TypeError as null (D-P4; operator ruling 2) | Keeps the spec's "read as load reads it"; no src/ change (C-006); the alternative of reading spec_kitty.version directly was not chosen. |
| PQ-8 | How are the absolute timing bounds made stable in a blocking -n 4 job? | The minimum of at least 5 in-test repeats, control timed the same way, quadratic mutation still failing (D-P16; operator ruling 6) | The minimum resists contention; CPU time and a scaling ratio were not chosen. |
| PQ-9 | Where do the corpus-sized cases (AC-DRIFT 19, AC-CROSS 4) live? | In test_mission_status_reality.py with a discovered-count guard; the three new modules are fixture-built only (D-P5; operator ruling 4) | The tool job reads no corpus (ci-gate-mechanics.md) and has no measured corpus budget. |
Departs from the spec
Each item sharpens or departs from the spec text; none is invented beyond it. Items 1 to 4 and 6 are inconsistencies found while planning and the spec text is left as it is for them. Item 5 is a real departure from the spec as first fixed: the operator ruled for the plan's behaviour (plan-round ruling 1) and the spec is edited to match (AC-REALITY bullets 2 and 3 and the FR-025 "Corpus outcome" sentence; add-only record in tracer-approach.md A-7). The other spec edits of this phase are the two severity-1 nits.
1. Registrations outside the slice file set. The spec's item (5) lists ci-router.yml, ci-module-registry.yml and contracts.yml. A new corpus-marked module is also red in two gates the file set does not name (test_ci_corpus_trigger_completeness.py registry row; a --deselect in the built-in-corpus-suite command of packs.yml for test_same_tier_uniqueness.py). The plan adds those two paths (PD-2) and neither of the two spec-named files that need no edit. FR-027's sentence "registered only in the three files of the slice file set" is read as "registered only in the files the plan lists". 2. Two new root tags break the literal bundle comparison. FR-022 item 2 compares the bundle "with the new operations and schemas removed"; Drift and Ops are new entries of the root tags list (FR-008, FR-016, file-set item (2)). The extended scratch pair removes them too (PD-4, D-P10). The previous slice added no tag. 3. NFR-001 and NFR-009 "measured on the CI runner and recorded in the plan". The code does not exist at plan time, so the plan records the local proxy (research R-12) and the spec's own figures; the CI-runner figure is read from the first CI run of the PR and recorded at W-7 against the 120-second bounds. 4. The J-1 commit citation is rewritten by compaction. FR-028 and AC-VERSION cite the commit of the final contracts/ change and check it is an ancestor of HEAD; W-5 rewrites every SHA. The plan records the J-1 result at the time (pre-compaction commit) and re-cites at W-7: the last commit touching contracts/mission-status in the compacted, rebased history, with git diff --exit-code <J-1 commit> <re-cited commit> -- contracts/mission-status clean. J-1 is repeated only if that diff is not clean. 5. Offline scope of the AC-REALITY named skip (an explicit departure, resolved by operator ruling). The spec as first fixed said that offline the whole of AC-REALITY bullets 2 and 3 takes a named skip (examined plus skipped, floors, oracles, per-Mission equality, the Ops walk against its oracle, skippedCount equality and the kitty-specs/ and kitty-ops/ fingerprint). The plan departs from that: it runs the Ops walk, the skippedCount equality, the Ops floors and the read-only fingerprint offline, because they need no network, and skips by name only the assertions that depend on the coordination resolver (the project-wide report equality with the oracles, the Missions examined-plus-skipped identity and Missions floors, the fallback list, the kind 1 to 3 oracles, per-Mission equality). A skip would hide a defect for no reason where no network is involved. The operator ruled for the plan's behaviour and spec.md is edited so offline skips only those named assertions; the named-skip rule and "an offline run is not evidence for SC-002 or SC-007" stay. Offline classification of the corpus-sized cases (applies ruling 1; derived from whether a case calls the coordination resolver or the network): (a) the AC-DRIFT 19 completion equality with its 490 floor and discovered-count guard RUNS offline (it reads each Mission's own directory for Missions declaring no coordination branch and never calls the resolver); (b) the real kitty-ops/ listing bound (at most 5 s, minimum of at least 5 cold repeats) RUNS offline (a directory listing, no resolver, no network); (c) the one-Project-build bound, (d) the one-project-wide-scan bound and (e) the combined timing case TAKE the named resolver-dependent skip offline (the Project build and the scan reach the resolver, which offline answers each Mission's own directory with no fallback entry (operator ruling at WP07, 2026-10-06), so a bound measured offline does not measure the online path) and count as skipped, not as executed, in the "every generated case executed" tally. The spec carries the same classification (add-only record in tracer-approach.md A-9). 6. FR-027 "negative_cases.json gains the plants that need the JVM or lint toolchain". The slice adds no lint rule; the existing lint plants (including enum-case) cover the new enums, and a committed lint plant would need a fixture under contracts/tools/fixtures/, outside the slice file set. The camel-case enum plant is run at J-1 on a scratch copy and recorded. negative_cases.json gains the six strict-name leak cases only. 7. leak_scan.py is in the file set but is not expected to change (PQ-4). It is listed in the allowed data only if a red-first plant makes the edit necessary, in the same commit. 8. The frozen pre-slice copies' location and generation (the spec says "under tests/contract/"): tests/contract/fixtures/mission_status_pre_slice/ in the source layout (schemas/, examples/, paths/, so that no two names in a directory differ only by case), generated by git show of the same relative path. 9. No root docs/changelog entry. The module CHANGELOG is the record (the root changelog is outside the slice file set; the previous slice's squad raised the same note). 10. FR-029 item 1 lands with IC-03, items 2 to 4 with IC-06. The deferral edit is paired with the Project change that moves the line; the pair and scope data are re-pointed once both new operations exist. 11. The orchestrator re-orders the canonical wrap-up sequence once: consolidation before the squad. The procedure lists the aggregate review before the local consolidation; the plan consolidates first so the squad reads one tree (the previous slice's order). If the orchestrator prefers the procedure order, the squad reads the union of the lane diffs. Every other step keeps the procedure's relative order: accept, dev-assist cleanup, issue-verdict resolution, squad (plan-round ruling 5); the terminal #5776 verdict is set after the squad's LAND, not before it. The consolidate-time verdict-terminality warning for the in-mission #5776 row is the expected result of that order (W-1). 12. Hygiene scan over this directory (Reflexivity item 3) is a plan commitment the spec does not state; it protects the corpus the new reads run over.
Test strategy per acceptance criterion (i)
Rule for every row: the test fails when the change is reverted. Three kinds of revert proof, named in each row: B base-red (the test is red on the work package's base and green on its tip; the first commit carries the test and a stub entry point that raises, so a red is a behaviour failure); P planted pair (a control that passes and a plant that fails on the same fixture, so the probe can see the thing); M named mutation (a table of reader mutations applied by the test, each of which must turn its rows red: the mutation was not killed: <name>; the catalogue is in contracts/tool-extension-and-reader.md). Tests call the reference reader's production entry point, never only a helper. Row numbers are the order of the spec's tables.
AC-PROJECT (module test_mission_status_project.py, IC-03; contract rows in the examples and proof modules)
| Row | Red-first test (proof kind) |
|---|---|
| 1 non-mapping and list metadata is null (the shape guard of D-P4; the reader never raises) | the shapes on which ProjectMetadata.load raises: a YAML list at the top level, a scalar at the top level, spec_kitty a list, spec_kitty a scalar, environment a list, a non-mapping migrations entry (each: specKittyVersion null, no exception; the unguarded load call is shown to raise AttributeError or TypeError on the same file, so the plant is real); control a mapping (B, P, M: call load unguarded) |
| 2 sentinel never escapes; non-string and non-matching versions null | no version; numeric; path-shaped; sentence-shaped; control 4.0.0 (B, P) |
| 3 non-integer schema null and unhealthy; CLI coercion is the contract's | three, "3" gives 3, 3.9 gives 3, true gives 1, compared with get_project_schema_version (B, P, M: wrap the CLI call) |
| 4 range boundaries; range not equality | MIN-1, MIN, MAX, MAX+1, null; constants patched to 2 and 4 with schema 3; differential test against check_compatibility (B, P, M: one-sided test) |
| 5 branch states | attached, unborn (the name), detached, no repository, unreadable HEAD, bad name, strict host-path name, linked worktree; parity with get_current_branch wherever a name is served (B, P, M: null on unborn) |
| 6 activity as instants | offsets where text and instant order differ; zero Missions; all-null; control one active Mission (B, P, M: string maximum) |
| 7 no process of its own, no write | subprocess.run raising unless the memo's resolver call is on the stack; writers raising; a plant that starts a subprocess from the reader (fails); builder equals the v1 build on every fixture including outside-root (B, P) |
| 8 description and required list; cost sentence | frozen old schema, short required, description without the cost sentence; real file passes (B, P); in test_mission_status_examples.py for the three examples |
AC-DRIFT (module test_mission_status_drift.py, IC-07, fixture-built only; row 19's corpus half in test_mission_status_reality.py, IC-09; contract halves in the examples, pin and proof modules, IC-04)
| Row | Red-first test (proof kind) |
|---|---|
| 1 differing snapshot; variant and order | WP01 planned versus approved; provenance-only; all-done; unmodified control (B, P, M: confuse variants) |
| 2 corrupt snapshot a finding; unreadable and raising reducer 500 | not JSON; a list; OSError; corrupt event log; control before corruption (B, P) |
3 laneComparison rows and nulls | one-sided work package; genesis lane; plain difference control (B, P) |
| 4 exactly one file missing; path; remedy | log without snapshot (remedy), snapshot without log (null), neither (none), both (control) (B, P) |
| 5 remedy null on a merged Mission | merged_at in the own meta.json versus an unmerged twin (B, P) |
| 6 expected lane branch absent | active Mission, in_progress lane, no branch; control with the branch (B, P) |
| 7 coordination expects its coordination branch only | absent gives a finding, present gives none although mission_branch is absent; a non-coordination control expects mission_branch (B, P, M: expect both) |
| 8 local branches only | remote-tracking ref only; control local (B, P) |
| 9 not expected | planned-only lane; completed Mission; absent manifest, each beside a flagged Mission (B, P) |
10 lanes.json three-way; completion first | non-completed: legacy-shaped (no finding, listed), not UTF-8, not JSON, not an object, neither key, current-shaped missing a key (each 500); completed: the same files are never read; control current manifest (B, P, M: read before completion) |
11 meta.json fail-closed | [] and invalid JSON are 500; absent meta.json is no coordination branch (B, P) |
| 12 git failure is never kind 3 | no .git with an expected lane (500); scan with no expected lane not failed (B, P) |
| 13 at most one listing, once | many Missions with expected lanes: the reader's own subprocess count exactly one; none expected: zero (B, P, M: per-branch call) |
| 14 a remote that cannot be asked | real-git fixture, remote URL a non-existent local path, declared branch absent locally: 200 from the own directory, no fallback entry (operator ruling at WP07, 2026-10-06); control a reachable remote lacking the branch: 200 fallback; cache reset between (B, P) |
15 missionId lookup uses the v1 identity read | unreadable meta.json in B, ?missionId= of A is 200; unknown ULID is 404 (B, P) |
| 16 memo keyed by root and directory | two repositories, same name, different outcomes; one repository read twice runs the resolver once (in the project module, IC-03) (B, P) |
| 17 read directory coordination-aware; fallback; other 500s | coordination copy consistent with stale primary (none) and the reverse (kind 1); deleted branch a 200 naming coordination_branch_deleted; the other resolver outcomes 500 (B, P) |
| 18 strict sibling does not fall back, v1 does | the three fixtures in one test (B, P) |
| 19 completion reads the own directory | drift module (fixtures): merged_at in the own copy only versus the coordination copy only (B, P). Reality module (IC-09, corpus job): equality with is_mission_completed over every corpus Mission declaring no coordination branch, with a discovered-count guard: the compared count equals an independent meta.json scan, is above zero and is at least the floor of D-P13 (490); a planted empty directory fails (B, P) |
| 20 generation gate | older-generation snapshot in step with its replay (none); same Mission at the current generation with a field differing (kind 1) (B, P) |
| 21 undecodable files | invalid UTF-8 in each of status.json (finding), lanes.json (500 non-completed, unread completed), meta.json (500), the event log (500); clean control (B, P) |
| 22 the fourth kind is absent | stale .kittify/derived/ beside a clean and a mismatching Mission; DriftKind pinned to three (B, P) |
| 23 order and cap; nothing to report | 1001 findings in shuffled order give 1000 and truncated; exactly 1000 give false; a clean repository gives 200 with findings: [] (the FR-024 "nothing to report" line) (B, P, M: sort off) |
| 24 fixed summaries, closed pair table, no leak | slug shaped like a host path and an event log holding an address; a reader producing a pair outside the table fails; leak scan clean (B, P) |
25 scannedAt and no-store | injected clock advanced between reads; a bundle without the header fails the example/operation test (B, P) |
| 26 404, 400, per-Mission equality | unknown ULID 404; non-ULID 400; per-Mission equals project-wide filtered (B, P) |
| 27 read-only | materialize and file creation raising; a control that writes (B, P) |
| 28 no dead value | each DriftKind, DriftSeverity, DriftAuthority, DriftSide, DriftRemedy value produced by a fixture through the production entry point; info absent from the pin; an example with a null missionId or artifactPath fails validation (B, P) |
AC-OPS (module test_mission_status_ops.py, IC-08; contract halves IC-05)
| Row | Red-first test (proof kind) |
|---|---|
| 1 newest first, ties by id descending | three Ops with equal startedAt; different offsets; control distinct times (B, P, M: ascending) |
| 2 paging once, cursor bound to the filter | an Op written between page reads; cursor of profile=a used with b and without; forged cursor (each: 400 invalid_page_cursor, the FR-024 line) (B, P) |
3 filter; totalCount equals served count | no-match profile; malformed profile; skipped and filtered records present (B, P) |
4 skippedCount independent of profile | skipped records of several profiles with a filter; control none (B, P) |
| 5 index candidates and lag | lagging index; stale entry (skipped, counted); entry whose profile differs from its file's (the file's value); unreadable index; corrupt line; no index; control index in step (B, P, M: use index profile) |
| 6 closure spine | spine only; own and spine; two spine records; legacy own completion with and without a spine record; control own closure (B, P, M: copy the CLI gap) |
| 7 open and closed invariants | an Op carrying an outcome while open fails validation (B, P) |
| 8 legacy and unreadable skipped; non-Op files not records | legacy started, empty, bad JSON, invalid UTF-8, stem mismatch, bad timestamp, bad wp_id, vanished file; the index, spine and lifecycle.jsonl in the directory (B, P) |
| 9 empty and failing directory | no kitty-ops/; a file named kitty-ops; unlistable; unreadable Op file beside readable ones; unreadable spine; spine with invalid UTF-8 (B, P) |
| 10 evidence classes | the table of the spec row, one test per expected (kind, value, redacted); a record with no evidence_ref gives evidence null (the FR-024 line); controls clean relative path and clean URL (B, P, M: skip step 1(c)) |
| 11 credential withheld before redaction | one SECRET_PATTERNS token per shape; the Op stays in items, every other field intact, evidence null; clean twins (B, P, M: redact first) |
| 12 actor shape | e-mail-shaped actor served with actor null (B, P) |
| 13 omitted fields | a reader adding request_text; a description lacking one of the five names (B, P) |
| 14 read-only, no agent; linear redaction | writers raising; three 256 KiB shapes, each timed as the minimum of at least 5 in-test repeats against 0.1 second, the short-value control timed the same way; the planted quadratic mutation (the pre-fix pattern) still fails; every repeat is cold and the planted cache-the-result mutation turns the row red; the measured minimum and the margin go in the hand-off (D-P16) (B, P, M: quadratic-redaction and cache-the-result) |
AC-VERSION, AC-CROSS, AC-REALITY (existing proof module, examples module, reality module)
| Row | Red-first test (proof kind) |
|---|---|
| AC-VERSION a baseline SHA | recorded command asserts git merge-base --is-ancestor; planted: a commit on no branch fails (P, wrap-up) |
| AC-VERSION b byte identity; bundle minus new operations | the extended pair with frozen copies (in the source layout) and removed tags; the masking rule of D-P10 (only components.schemas.Project and the /project operation description are masked); planted byte change, planted 409, a planted extra response code on getProject, a planted changed 200 schema reference on getProject, and a planted change to another existing schema (B, P) |
| AC-VERSION c lowered-major and same-major runs | recorded wrap-up commands with counts; controls in D-P10 (P, wrap-up) |
| AC-VERSION d version equals main; provisional names; Deferred section; CHANGELOG statements | section-scoped test with planted entries; qualified-name plants (B, P) |
| AC-VERSION e enums pinned and snake_case; leak scan; terminology | pin check with a planted camel-case value; leak scan with each planted example of FR-027; terminology scan with a planted second feature use (B, P) |
| AC-VERSION f J-1 record | recorded commands: commit exists, ancestor, last touching contracts/mission-status; one line per job of contracts.yml; planted job name absent from the workflow (P, wrap-up) |
| AC-CROSS 1 determinism | two reads byte-equal; two bundle builds equal; a clock tick changes scannedAt only (B, P) |
| AC-CROSS 2 scope | scope_problems over the real diff plus planted extra file and planted src/ path (B, P, wrap-up) |
| AC-CROSS 3 dependencies | a pure check that no entry names pyproject.toml or uv.lock; planted added dependency fails (B, P) |
| AC-CROSS 4 bounded reads and time | fixture-built (drift and ops modules): counting opener; subprocess total equals the memo's count plus the listing; synthetic 10,000-file directory at most 30 s (minimum of at least 5 repeats, D-P16). Corpus-sized (reality module, IC-09): real directory at most 5 s (minimum of at least 5 repeats); one Project build and one scan at most 120 s each; one case timing the memoised pass, the Project build and the per-Mission reductions together at most 120 s; each with a discovered-count guard (served plus skipped equals discovered and is non-zero; Missions examined at least the floor); the planted cache-the-result mutation (a memoising listing, applied as a context manager in the reality module) must turn the real 5 s listing case red (B, P, M) |
| AC-CROSS 5 writers | covered by AC-DRIFT 27 and AC-OPS 14 |
| AC-REALITY 1 corpus outcome and fallback list | 200 with only CoordinationBranchDeleted; fallback list equals the independent derivation both ways; resolver once per Mission; offline named skip (B, P) |
| AC-REALITY 2 examined plus skipped equals discovered; floors; oracles; AST independence | named lists with stale-entry checks; a floor loosened to one fails; AST test (B, P). Offline: the Missions half (identity, Mission floors, kind 1 to 3 oracles, fallback lists) takes a named skip; the Ops half and the AST test run |
| AC-REALITY 3 per-Mission equals filtered; Ops walk equals the oracle; fingerprints; every generated case executed | the last test counts executed cases against generated cases (B, P). Offline: per-Mission equality takes a named skip (a case skipped by name counts as skipped, not executed); the Ops walk, skippedCount equality and both fingerprints run |
Implementation Concern Map (j)
This is intent for /spec-kitty.tasks, not the task list; lane placement is computed from the write scopes and dependencies ("Lanes"). Ten concerns (IC-01 to IC-10) and a wrap-up checklist (W-1 to W-7) that is not a work package; one pull request. The first commit of every code work package is its red test with a raising stub (exception: IC-01). Every x-derived/x-source citation, floor and name below is from the spec; the files are the write scope (explicit, no directory-wide glob).
IC-01 Sync, re-measurement, baseline and campsite (tidy-first)
- Purpose: Start from the orchestrator's Step 0 record (the D-0 sync, the synced environment and the baseline are not IC-01's; plan-round ruling 3), re-measure, as its first task, the kind-3 counts under the final rule and the other numbers the spec marks as moving, and land the opening campsite commit (or record "none").
- Relevant requirements: spec "Re-measurement owed", FR-012 evidence note, FR-013 (Gate B and the no-op re-check), FR-015 (fallback derivation agreement and the corpus condition), NFR-001, NFR-002, NFR-009 (measurements), A-2, Standing Order 2.
- Steps, in order: (1) re-measure kind 3 with a scratch script built from public product readers (never committed): Missions not completed, with a current-shaped manifest, with an expected lane, with a missing expected branch, by lifecycle state and by topology (coordination branch versus
mission_branch), the legacy count; record the result and pin no corpus floor; (2) re-check Gate B (no.kittify/derived/in the corpus, gitignored, untracked) and thatvalidate_derived_viewsis still a no-op returning an empty list; if either changed, the decision of FR-013 returns to the squad; (3) re-count the corpus facts of research R-2 and the fallback agreement and corpus condition of R-3; (4) time the resolver-included Project proxy, the scan, the real-directory listing repeated at least 5 times, and the AC-DRIFT row 19 completion equality over the real corpus (about 518 own directories), each measured and not estimated, with the remotes reachable, and record the sum (the proxy of D-P5's single bounded case included; the escalation line is about 70 s local, that is 120 s divided by the 1.7 factor, against the sum of the combined case and the 10 to 15 s of completion equality and listing repeats, so about 55 s local for the combined case alone; above it goes to the operator before the bound is set, plan-round rulings 8 and 11; the W-7 pass rule stays the final arbiter); (5) the campsite commit. - Affected surfaces:
tests/contract/test_mission_status_contract_1_1.py(campsite only). Everything else is scratch and reported to the orchestrator, which appends it totracer-approach.md(Record-keeping). - Write scope: that one file. Sequencing: depends only on the orchestrator's Step 0 record; the one parallel window of the Mission is IC-01 beside IC-02 and it stays.
- Acceptance (named): the recorded kind-3 result; the Step 0 record cited and any red met binned against it; the campsite counts identical before and after.
- Risks: P-4 (the corpus condition of R-3 must hold), P-5 (kind-3 honesty), P-6 (a moving upstream), P-2 (the blind gates).
IC-02 Tool extension (FR-027)
- Purpose: Give the new strict properties their leak class and plants.
- Relevant requirements: FR-027, NFR-004, C-003.
- Affected surfaces:
contracts/tools/fixture_builder.py(STRICT_FIELDSand the strict-name plants through the_NEW_STRICT_NAMESpattern: six names, six new kinds),contracts/tools/negative_cases.json(six cases),tests/contract/test_fixture_builder.py(the exactKINDSset; the eight old kinds still build byte-identically),tests/contract/test_leak_scan.py(each of the six is inSTRICT_FIELDS; a plantedprofileIdexample holding a bare absolute path fails the scan, its clean control passes),tests/contract/test_run_negative_cases.py(the manifest length). - Write scope: those five files.
enum_pins.jsonis written by IC-03 to IC-05, not here. - Sequencing: depends only on the orchestrator's Step 0 record (a measured base and a synced environment, so its reds can be binned and ruff can run); concurrent with IC-01. Red-first: the six plants are undetected by the unchanged scanner (a leading-tilde path is a host path only in the strict class).
- Acceptance (named):
leak_scan.py --root contractsexits 0 over the existing tree; the eight old kinds build byte-identically; the named census files run green after the test edits (against the Step 0 baseline). - Risks:
negative_cases.jsonandfixture_builder.pyhave this one writer; the unicode-escape hazard (quickstart, last section).
IC-03 Project vertical: contract, memo, builder and ratchet edits
- Purpose: Land the only change to an existing response together with the code that proves it, so no tip is red.
- Relevant requirements: FR-001 to FR-007, FR-023 (Project examples and the
healthprovisional entry), FR-024 (Project rows), FR-029 item 1, FR-015 (Memo, the Project consumer), FR-025 (the reality check's Project build, ratchet), NFR-009, OR-1, OR-8, OR-10, FRESH-005; AC-PROJECT 1 to 8, AC-DRIFT 16. - Affected surfaces: contract:
schemas/Project.yaml, newschemas/ProjectHealth.yaml,examples/Project.example.yaml, newexamples/Project.all-null.yamlandexamples/Project.schema-drift.yaml,paths/project.yaml(description only),schemas/_index.yaml,examples/_index.yaml,CHANGELOG.md(Changed, thehealthprovisional token, Deferred three gaps),contracts/tools/enum_pins.json(ProjectHealth); tests and helpers: new_mission_status_memo.py,_mission_status_project.py,test_mission_status_project.py; edited_mission_status_payloads.py(thederive_projectequality),test_mission_status_payloads.py(the two-key assertion),test_mission_status_reality.py(the Project build uses the new builder; the v1 pass stays),test_mission_status_examples.py,test_enum_pin_check.py,test_mission_status_contract_1_1.py(FR-029 item 1:DEFERRED_GAPSloses "project branch", the gap-outside-section test re-points to "lane weights", the entry test renames to three); registrations:.github/workflows/packs.yml,tests/architectural/test_ci_corpus_trigger_completeness.py,.github/workflows/ci-router.yml. - Sequencing: after IC-01 (same proof module) and IC-02 (the scanner meets every new file as it is written).
- Red-first: the two new
Projectexamples and the five-property required list as required cases oftest_mission_status_examples.py(red until the files exist), thetest_enum_pin_check.pyline and the pin (red untilProjectHealthexists), the AC-PROJECT rows oftest_mission_status_project.pyagainst a stub builder that raises, and the ratchet edit ofderive_project(the v1 tests turn red against the new schema until the builder is wired). - Acceptance (named): the ten Python checks over the IC-03 tree; the
breaking_check.pyspike of research R-5 repeated on the real files (lowered-majorbreaking=4); the named census files and the registration gates run green; the first task runs the two global-state scan files against the counting wrapper (D-P2). - Risks: this is the largest vertical (P-13); the ratchet edits to v1 tests are reviewed as ratchets (spec R-6); the Project description must state the cost and the network behaviour.
IC-04 Contract slice D: the drift read
- Purpose:
getDriftReport, its schemas, enums, refusal, responses, parameter and examples. - Relevant requirements: FR-008 to FR-014 (shape, summaries, remedy,
sourceCode), FR-015 (descriptions: no-store, fallback rule, network behaviour), FR-023 (Drift part, provisional table), OR-2, OR-3, OR-4, OR-7, OR-9, ARCH-002, FRESH-014; AC-DRIFT 22, 24 and 28 (contract halves). - Affected surfaces: new
paths/drift.yaml,parameters/DriftMissionId.yaml, schemasDriftReport,DriftFinding,LaneComparison,DriftKind,DriftSeverity,DriftAuthority,DriftSide,DriftRemedy,DriftRefusal,DriftRefusalCode, responsesDriftMissionNotFound,DriftScanUnreadable, and the Drift examples; editedopenapi.yaml(the/driftkey, theDrifttag), the four_index.yaml,CHANGELOG.md(Added, Provisional tokens, the not-shipped decision,infonot carried, non-null),enum_pins.json(six pins),test_mission_status_examples.py(path keys,MIN_EXAMPLES, required cases),test_enum_pin_check.py(thecounts:line). - Red-first:
EXPECTED_PATH_KEYSgains/drift, the required-case manifest gains each Drift example and the case that an example with a nullmissionIdorartifactPathis refused, the pin test gains the six pins and thecounts:line, and the CHANGELOG token test gains the Drift tokens; all red until the contract files exist. - Sequencing: after IC-03 (chokepoint files). Acceptance (named):
leak_scan.py,example_check.py,provisional_check.py,enum_pin_check.py,citation_check.py,layout_check.pyandrun_negative_cases.py(JVM, vacuum and oasdiff tags excluded) green over the tree; the lowered-major spike keepsbreaking=4with the new path and tag. - Risks: chokepoint
openapi.yaml, indexes,enum_pins.json,CHANGELOG.md; theif/thenstatus pinning ofDriftRefusal;provisional_checkword-satisfaction (D-P11).
IC-05 Contract slice O: the Ops read
- Purpose:
listOpsInvocations, its schemas, enums, refusal, response, parameter and examples. - Relevant requirements: FR-016 to FR-021 (shape, closure, order, skipped, evidence, omitted fields), FR-023 (Ops part), OR-5, OR-6, OQ-5, OQ-6, AD-5, AD-7; AC-OPS 13 (description half).
- Affected surfaces: new
paths/ops_invocations.yaml,parameters/OpsProfile.yaml, schemasOpsInvocationPage,OpsInvocation,OpsModeOfWork,OpsInvocationStatus,OpsOutcome,OpsClosedBy,OpsEvidence,OpsEvidenceKind,OpsRefusal,OpsRefusalCode, responseOpsUnreadable, the Ops examples; editedopenapi.yaml(the/ops/invocationskey, theOpstag), the four_index.yaml,CHANGELOG.md,enum_pins.json(six pins), the two test modules as in IC-04. - Red-first: as IC-04 for
/ops/invocations, the evidence-kind and closure-state required cases, the six pins and the Ops tokens (including thex-provisionalmarker on the operation). - Sequencing: after IC-04. Acceptance and risks: as IC-04; the description names the credential kinds and the five omitted record fields; the operation carries
x-provisional(AD-7).
IC-06 Contract proof edits (FR-029 items 2 to 4, FR-022, FR-023)
- Purpose: Make the proofs of the contract final: the frozen pre-slice copies and the extended scratch pair, the section-scoped CHANGELOG checks (qualified names, statements), the slice scope data, the dependency check, the docstring.
- Relevant requirements: FR-022, FR-023 (qualified names, provisional table), FR-029 items 2 to 4, FR-027 (scope), C-002, C-003, C-007, AC-VERSION a to e, AC-CROSS 2 and 3, SC-006, SC-008.
- Affected surfaces:
tests/contract/test_mission_status_contract_1_1.py(NEW_PATH_KEYS,_make_pair,CHANGED_ALLOWED,SLICE_ALLOWED,EXTRA_ALLOWED_REGISTRATIONS,IN_SCOPE_CHANGES,REGISTRATION_CHANGES, the shape test, new checks),contracts/mission-status/CHANGELOG.md(final statements and Provisional list), newtests/contract/fixtures/mission_status_pre_slice/schemas/Project.yaml,.../examples/Project.example.yaml,.../paths/project.yaml(generated bygit showof the same relative path; the layout mirrors the source so no two names in one directory differ only by case;SLICE_ALLOWEDkeeps the prefixtests/contract/). - Red-first: the extended pair test (red until the frozen copies exist and the two tags are removed from the scratch baseline), the four qualified-name tests with their plants, the section-scoped CHANGELOG statements, the dependency check and the real-diff scope data (a planted extra file and a planted
src/path must fail). - Sequencing: after IC-05. After IC-06 is approved the orchestrator runs J-1 (below), before IC-07 is dispatched.
- Acceptance (named): the pure proof tests with every planted mutation; the CHANGELOG checks pass on the real entry and fail on each planted entry;
provisional_check.pyoutput equals the named list. - Risks: the previous slice's data is replaced, not extended (R-6);
SLICE_ALLOWEDmust name every non-contract file the slice touches (list in the next block).
SLICE_ALLOWED (expected, written by IC-06 from this list; an entry the final diff does not need is removed at W-1, an entry it needs is added in the commit that needs it). A added, M modified, never D:
PREFIX contracts/mission-status/ A
EXACT contracts/mission-status/openapi.yaml M
EXACT contracts/mission-status/CHANGELOG.md M
EXACT contracts/mission-status/schemas/_index.yaml M
EXACT contracts/mission-status/examples/_index.yaml M
EXACT contracts/mission-status/parameters/_index.yaml M
EXACT contracts/mission-status/responses/_index.yaml M
EXACT contracts/mission-status/schemas/Project.yaml M
EXACT contracts/mission-status/examples/Project.example.yaml M
EXACT contracts/mission-status/paths/project.yaml M
EXACT contracts/tools/fixture_builder.py M
EXACT contracts/tools/negative_cases.json M
EXACT contracts/tools/enum_pins.json M
EXACT .github/workflows/ci-router.yml M (contract_tools globs)
EXACT .github/workflows/packs.yml M (EXTRA_ALLOWED_REGISTRATIONS)
EXACT tests/architectural/test_ci_corpus_trigger_completeness.py M (EXTRA_ALLOWED_REGISTRATIONS)
PREFIX tests/contract/ A or M
Registration files confirmed (plan-round ruling 7). The block names every registration file of PD-2 and no other: .github/workflows/ci-router.yml, .github/workflows/packs.yml and tests/architectural/test_ci_corpus_trigger_completeness.py; the two spec-named files that need no edit (.github/ci-module-registry.yml, .github/workflows/contracts.yml) are deliberately absent. The new fixtures sit under the tests/contract/ prefix.
Not touched, with the reason: contracts/tools/leak_scan.py (PQ-4, only if proven blind), contracts/tools/leak_patterns.py (the linear matchers redact_emails and email_matches exist), .github/ci-module-registry.yml (no new module row; new files under tests/contract need no entry), .github/workflows/contracts.yml (its path filters already cover contracts/ and tests/contract/), docs/development/reference/ci-gate-mechanics.md and tests/ci/test_contracts_workflows.py (the group description is generic and the pin test derives the list), pyproject.toml, uv.lock, everything under src/, packs/ and docs/, and every existing file under kitty-specs/ other than this Mission's own.
J-1 (orchestrator step, FR-028; not a work package). Once IC-06 is approved, on the lane tip: replay on a JVM-capable machine every job of .github/workflows/contracts.yml (verify-pins, python-checks, validate-bundle, resolver-parity, lint, breaking-change, release-dry-run, negative-tests, contracts-gate) over the final shapes, with TMPDIR outside any directory whose ancestors hold a .git; add the camel-case enum plant on a scratch copy (Departs 6) and the two breaking_check.py runs with their scratch baselines. Record in tracer-design-decisions.md the commit of the final contracts/ change and one line per job with its conclusion (nine lines). A defect in an approved work package returns by a rejection transition; the replay repeats for any later change to contracts/mission-status/. If no JDK and network are available the risk stays open and the operator is told before the draft PR (P-9).
IC-07 Drift reader and its tests
- Purpose: The drift reader, the strict sibling, the fallback list, kinds 1 to 3, the fixed summaries, order and cap, and every fixture of FR-026 that concerns drift.
- Relevant requirements: FR-008 to FR-015, FR-024 (drift rows), FR-026 (drift fixtures), NFR-002, NFR-003, NFR-005, NFR-007, ARCH-001, GOV-001, GOV-007, COMPLETE-001, COMPLETE-013's drift counterpart (AD-10), FRESH-001, FRESH-007, FRESH2-001 to FRESH2-007, FRESH3-001; AC-DRIFT 1 to 15, 17, 18 and 20 to 28 (row 19's fixture half stays here; its corpus half is IC-09's), AC-CROSS 1, 4, 5 (drift parts: the counting opener and the subprocess total on fixtures; the corpus-sized cases of row 4 are IC-09's).
- Affected surfaces: new
_mission_status_drift.py, newtest_mission_status_drift.py(markerscontract,corpus,git_repo; modulepytestmark),.github/workflows/packs.yml,tests/architectural/test_ci_corpus_trigger_completeness.py,.github/workflows/ci-router.yml. - Red-first: the AC-DRIFT rows of the Test strategy, each with its control, against a stub
scan_driftthat raises; the mutation catalogue of the drift reader. - Sequencing: after IC-06 (J-1 done). Acceptance (named): the first commit holds the red tests and a raising stub; every mutation of the catalogue is killed; the named census files and the registration gates run green; the named gate files of the quickstart.
- Risks: the resolver (assumptions only, spec R-9), real-git fixtures (0.02 s a repository), network (the offline fixture), the 500 line (D-P7), the size of the module (a possible split into IC-07a kinds 1 and 2 and IC-07b kind 3 is allowed, same files, sequential).
IC-08 Ops reader and its tests
- Purpose: The Ops reader: candidate set, order, cursor, closure, skipped records, evidence pipeline, the loud failures.
- Relevant requirements: FR-016 to FR-021, FR-024 (Ops rows), FR-026 (Ops fixtures), NFR-002 (listing bounds), NFR-003, NFR-005, NFR-008; AC-OPS 1 to 14, AC-CROSS 1, 4, 5 (Ops parts: the synthetic 10,000-file case; the real-directory case is IC-09's).
- Affected surfaces: new
_mission_status_ops.py, newtest_mission_status_ops.py(markerscontract,corpus; modulepytestmark), the two registration files,.github/workflows/ci-router.yml. - Red-first: the AC-OPS rows, each with its control, against a stub
list_opsthat raises; the mutation catalogue of the ops reader; the three timed shapes with their short-value controls, each timed as the minimum of at least 5 cold repeats (D-P16); the cache-the-result mutation joins the catalogue and must fail. - Sequencing: after IC-07 (the registration chain; no semantic dependency). Acceptance (named): as IC-07; the three 256 KiB timed shapes and the 10,000-file listing run with their controls, timed as the minimum of at least 5 repeats, and the planted quadratic mutation still fails; the timed assertions are measured first and the measured minimum and margin recorded in the hand-off (P-7, D-P16).
- Risks: a 0.1-second assertion under
-n 4contention (P-7); credential withholding before redaction (the order is the rule); anOSErrorversus a vanished file.
IC-09 Oracles and the reality extension
- Purpose: The independent oracles, the corpus run of the three reads, named lists, floors, fixture, offline skip, read-only fingerprints.
- Relevant requirements: FR-025, NFR-001, NFR-002 (corpus parts), D-P13, D-P14, SC-002, SC-004, SC-005, SC-007, AC-REALITY 1 to 3; plus the corpus-sized cases moved here by plan-round ruling 4: AC-DRIFT 19 (equality with
is_mission_completedover every non-coordination corpus Mission) and AC-CROSS 4 (the realkitty-ops/listing at most 5 s, oneProjectbuild and one scan at most 120 s each, and the single case that times the memoised pass, the Project build and the per-Mission reductions together), each with a discovered-count guard. - Affected surfaces: new
_mission_status_oracles.py; editedtest_mission_status_reality.py,_mission_status_payloads.py(FLOORS), newtests/contract/fixtures/mission_status_health_drift_ops_expected.json. - Red-first: the AST independence test, the floor tests (a floor loosened to one fails), the named-list and stale-entry tests, the offline-skip test (a planted unreachable remote: only the named resolver-dependent assertions skip, including the Project-build, scan and combined timing cases, which count as skipped and not executed; the Ops walk,
skippedCountequality, the Ops floors, the AC-DRIFT 19 completion equality with its 490 floor, the realkitty-ops/listing bound and the fingerprints still run and count as executed; a plant that moves the completion equality or the listing into the skip, or runs the Project build offline, must fail) and the fingerprint tests with a writing reader, the completion-equality case (the guard: compared count equals the independent count of Missions declaring no coordination branch and is at least its floor of 490; a planted zero-Mission directory fails) and the corpus-sized timing cases (a planted over-bound case fails), plus the cache-the-result mutation applied as a context manager in the reality module (a memoising real-directory listing must turn the real 5 s listing case of AC-CROSS 4 red; the mutation is scoped to the module that holds the case, plan-round ruling 9), all against oracle stubs that raise. - Sequencing: after IC-07 and IC-08. Acceptance (named): the corpus run green with the remotes reachable and the named skip offline (both shown); an AST test; planted floors, stale entries, a writing reader and the cache-the-result mutation (a memoising real-directory listing turns the real 5 s case red) fail; the runtime within NFR-001 (measured).
- Risks: runtime (D-P5); network flakiness in a required job (Reflexivity item 2); the 8-Mission ratchet header and the v1 pass must not be touched.
IC-10 Close-out evidence, pre-rebase
- Purpose: The close-out assessment of the three tracer files (what held, what the plan got wrong), appended after every record the orchestrator collected. Labelled pre-rebase; the final evidence is W-7.
- Write scope:
tracer-approach.md,tracer-design-decisions.md,tracer-tooling-friction.mdof this directory (append-only); the only work package with a file underkitty-specs/in its scope. Execution mode:planning_artifact, set explicitly in its frontmatter (inference reads the body: a mention oftests/or a.pyfile would make itcode_change). Expected placement:lane-planning, depending on the code lane. Sequencing: after IC-09.
Lanes: the expected computed outcome
Lanes are computed, not chosen. compute_lanes works in this order: planning_artifact work packages go to lane-planning; among code work packages rule 1 unions every pair whose owned_files overlap; rule 2 (shared surface keywords) merges only pairs whose ownership is not provably disjoint, so with a manifest on every work package it adds nothing; dependencies only become edges between lanes; the lane graph must be acyclic. The plan states dependencies and write scopes and has no manual merge point: lane work reaches the Mission branch at one place only, the orchestrator's consolidation at W-1.
Expected outcome, from running compute_lanes on the planned write scopes (a scratch script with explicit file lists and empty work package bodies; not committed; the files named in the IC sections, one sample file per new contract family):
| Case | Computed lanes |
|---|---|
IC-10 planning_artifact (planned) | lane-b holding IC-02 alone; lane-a holding IC-01, IC-03, IC-04, IC-05, IC-06, IC-07, IC-08, IC-09 in dependency order, depending on lane-b; lane-planning holding IC-10, depending on lane-a |
IC-10 left code_change | IC-10 is a lane of its own depending on lane-a (finalisation warns: a code work package owning no src/ or tests/ file); the explicit mode is what yields lane-planning |
Why the big lane: IC-01, IC-03 and IC-06 share the 1.1 proof module; IC-03, IC-04, IC-05 and IC-06 share the indexes, enum_pins.json, the examples and enum-pin tests and the CHANGELOG; IC-03, IC-07 and IC-08 share the two registration files and the router workflow; IC-03 and IC-09 share the payload helper and the reality module. IC-02's five files are owned by nobody else. Step 0 and the parallel window (plan-round ruling 3). The D-0 sync, the baseline and the synced environment are an orchestrator step before any dispatch (Baseline section), so neither IC-01 nor IC-02 waits on the other for a measured base or an environment, and the IC-01/IC-02 parallel window stays; the lane computation is unchanged. Consequences stated plainly: the one real parallel window is IC-01 beside IC-02; everything else is sequential, one work package at a time in the lane worktree; the tooling merges IC-02's approved tip into the big lane when its next work package starts. Two things /spec-kitty.tasks must keep true: (1) no work package owns a directory-wide glob under tests/contract/ or contracts/ (it would overlap IC-02's files and pull IC-02 into the big lane); (2) the owned_files are authored final before finalize-tasks is run, because finalisation fills a package's owned files only when they are empty and an amended manifest never propagates (tooling hazard, research R-14).
| WP | Depends on | Write-scope notes |
|---|---|---|
| IC-01 | none (the orchestrator's Step 0 record, not a work package) | the 1.1 proof module (campsite) |
| IC-02 | none (Step 0 record) | five tool and tool-test files |
| IC-03 | IC-01, IC-02 | Project contract files, indexes, pins, CHANGELOG, memo, Project builder, payload helper and reality (ratchet), proof module (deferral), registrations, router |
| IC-04 | IC-03 | Drift contract files, openapi.yaml, indexes, pins, CHANGELOG, examples and enum-pin tests |
| IC-05 | IC-04 | Ops contract files, same shared files |
| IC-06 | IC-05 | proof module, CHANGELOG, frozen copies |
| IC-07 | IC-06 | drift helper and fixture-built tests, registrations, router (not the reality module) |
| IC-08 | IC-07 | ops helper and fixture-built tests, registrations, router (not the reality module) |
| IC-09 | IC-07, IC-08 | oracles, reality module (the corpus-sized cases of AC-DRIFT 19 and AC-CROSS 4 included), payload helper (floors), anomaly fixture |
| IC-10 | IC-09 | the three tracer files (planning_artifact) |
Declared shared-file exceptions (each is a dependency chain, so an overlap is never concurrent). (1) openapi.yaml, the four _index.yaml, enum_pins.json, test_mission_status_examples.py, test_enum_pin_check.py: IC-03, IC-04, IC-05. (2) CHANGELOG.md: IC-03, IC-04, IC-05, IC-06. (3) The built-in-corpus-suite command in packs.yml (one physical line), _CORPUS_MARKED_MODULES and the contract_tools list: IC-03, IC-07, IC-08; each appends its own entries in sorted position in the same commit as its module, so a line is never edited from two places at once and each module's own gates are green on every commit. (4) The 1.1 proof module: IC-01, IC-03, IC-06. (5) The payload helper and the reality module: IC-03, then IC-09 (IC-07 and IC-08 never edit either, since the corpus-sized cases live in the reality module).
Record-keeping and write scope (Standing Order 3). No code work package has a file under kitty-specs/ in its write scope. What a work package must record (a refinement of a contract note, a floor re-measure, a friction observation) goes into its hand-off report; the orchestrator appends it, add-only, to the right tracer file between dispatches (never while IC-10 is in progress).
Critical path. IC-01, IC-03, IC-04, IC-05, IC-06, J-1, IC-07, IC-08, IC-09, IC-10; IC-02 runs beside IC-01.
WP-shaping notes (for /spec-kitty.tasks)
- One work package per IC, ten in all, except where noted. IC-10 is the only
planning_artifactpackage; IC-01 is a code package whose first task is a measurement. No package is dispatched before the orchestrator's Step 0 record exists (D-0 sync, synced environment, baseline; Baseline section). - Chokepoints (serialised by construction): the contract files (IC-03 to IC-06:
openapi.yaml,_index.yamlx4,enum_pins.json,CHANGELOG.md), the shared CI gate files (packs.yml, the corpus-trigger registry,ci-router.yml: IC-03, IC-07, IC-08) and the registriestest_mission_status_contract_1_1.py, the payload helper and the reality module (which also holds the corpus-sized cases, IC-09; the three new modules are fixture-built only). A reviewer of any of them runs the named gate files of the quickstart, not a subset. - IC-03 is the largest and cannot be split without a red tip; keep one package with three ordered commits (red tests and stubs; contract and ratchets; builder). IC-07 may be split (kinds 1 and 2 with the read-directory logic, then kind 3 and the listing), same files, sequential, the registration pair in the first part. IC-09 may be split (oracles, then the corpus run and floors), same lane.
- The red-first commit is the first commit of every code package and carries a raising stub; the reviewer checks that the red run shows behaviour failures, not collection errors, and that the control tests are red too.
- J-1 sits between IC-06 and IC-07 in the dispatch order; IC-07 is not dispatched before the record exists (or the operator accepts the open risk P-9).
- Dispatch brief hygiene (lessons of the previous slice): forbid sub-agents inside a work package brief; forbid pattern kills, stash, and
rmwith variable globs; "a denied command means stop and report"; give every package its named gate files; paths in briefs are repository-relative (agents copy absolute paths into public artefacts).
Wrap-up checklist (orchestrator-owned steps, not work packages)
| Step | What | Notes |
|---|---|---|
| W-1 | (i) Accept, (ii) lane consolidation, (iii) registration and wiring check and scope check over the real diff, in this order | (i) spec-kitty accept --mission mission-status-health-drift-ops-01M464D3 on the repository-root checkout, after the acceptance-criterion verdicts are authored with spec-kitty agent mission acceptance-verdict (never hand-edited); accept reads the Mission directory and status log, not the lane code. (ii) spec-kitty consolidate --mission <slug> --strategy rebase --dry-run first, then without --dry-run; it works in two local hops (lane branches into kitty/mission-<slug>, then into the target branch of meta.json) and never publishes; rebase keeps the per-package red then green commits for the squad and the compaction (squash would make the Mission one commit and the lane branches the only record); the orchestrator records the strategy and the resulting HEAD. (iii) The named gate files and census files, every new module collected by the router marker expression (--collect-only), the scope check (SC-008) and the add-only check of kitty-specs/ against the merge base, both empty, and the output kept for the PR body. Issue-matrix rows were set before the first approval; the terminal verdict is not set here (W-4, after the squad). Expected consolidate evidence: with the #5776 row still in-mission, the issue_matrix_verdict_terminality merge gate (_evaluate_issue_matrix_verdict_terminality_gate, src/specify_cli/policy/merge_gates.py) reports FAIL for that row. Its mode is read first from .kittify/config.yaml (policy.merge_gates.mode; this checkout sets no policy section, so the default warn applies and the gate is a printed warning, not an abort; block would abort before any mutation). The warning is the expected outcome of the ruled order (plan-round ruling 5), is classified as such (not a defect), is recorded in the W-1 evidence and in the PR body, and is never cleared by setting the verdict early; a block mode is an operator escalation, not a reason to reorder. |
| W-2 | J-1 confirmation | git diff --exit-code <J-1 commit> HEAD -- contracts/mission-status on the consolidated tree; clean means the J-1 record stands; otherwise J-1 is replayed (FR-028). |
| W-3 | Dev-assist cleanup | The procedure's order: after accept and before the verdict step and the squad. Judge the tests this Mission wrote to facilitate its own slices (mutation tables, counting wrappers, the timed cases) against the permanent suite; retire or keep with a reason; behaviour-preserving, own commit. |
| W-4 | (a) Issue-verdict evidence, (b) aggregate adversarial squad, (c) terminal #5776 verdict, in this order | (a) In the procedure's position (after dev-assist, before the squad) record the evidence for the #5776 verdict; the matrix row stays an in-mission placeholder. The consolidate output of W-1 (ii) is expected to carry the verdict-terminality warning for this row (see W-1); it is the ruled order, recorded in the PR body. (b) The squad runs on the consolidated pre-rebase tree; report-only; not re-run after the rebase; it checks the J-1 conclusions against a re-run, the ratchet edits as ratchets, the oracles' independence, and every FR-022 and FR-028 record against git, and whether #5776 is actually fixed. (c) After the squad's LAND (every finding folded or ruled), spec-kitty agent issue-verdict ... --verdict fixed moves #5776 to its terminal verdict (plan-round ruling 5); a squad that does not land holds the verdict. |
| W-5 | History compaction | The shape of "The one pull request". Leading rule (by path): the first commit is mission planning artifacts and status log, built by path from the final kitty-specs/mission-status-health-drift-ops-01M464D3 tree, worded without the word feature (the finalisation message cannot be reworded in place); status.events.jsonl is compared byte for byte before and after (no line added, dropped or re-timed); the add-only check is re-run on it. Then one commit per code concern; measured diff size recorded (threshold in "The one pull request"). |
| W-6 | Rebase onto upstream main | Fetch first; no tag is needed; no version conflict is expected (this Mission never edits info.version); a CHANGELOG conflict is possible and is resolved by keeping both sides; the J-1 commit is re-cited (Departs 4). |
| W-7 | Final evidence | The FR-022 baseline record (the full SHA of the merge-base at this run, git merge-base --is-ancestor against origin/main, the two breaking_check.py runs with their counts: lines and planted controls, the byte-identity command); the FR-028 record re-cited; the quickstart symbol check; floors re-measured and checked below the re-measure; the named gate files, ruff, TID251, cutover-guard; the scope and add-only checks re-run on the rebased tree; the hygiene scan of this directory; the NFR-001 pass rule of D-P5 (the first PR run's tests (corpus-blocking) duration minus the Step 0 median is at most 120 s; tests (contract tools) under its timeout, margin recorded); the measured minimum and margin of every timed bound (D-P16) taken from the work package hand-offs. The only evidence cited as final in the PR body. Then the draft PR (targeting main, body ends with the standard attribution line), then the closing tracker comment with the PR link. |
Which tree each step reads. Before W-1 (ii) the repository-root checkout holds only the Mission directory; W-1 (i) reads the Mission directory and status log only. From W-1 (ii) every step reads the consolidated HEAD of the target branch; W-5 rewrites that history; W-6 and W-7 read the rebased tree.
Reality-check design notes (FR-025 anchors for IC-09)
- Reader chain per Mission:
load_sourceas today (the v1 pass, untouched); the new builders take the memo's outcome; the strict sibling serves drift; the Project builder servesProject; the oracles never see the readers. - The corpus run order: network probe (outside the reader,
git ls-remote --headson each configured remote) decides online or offline; online asserts the 200, the fallback list both ways, the floors, equality with the oracles and the examined-plus-skipped identity; offline asserts the 200 with no fallback entries alone (operator ruling at WP07, 2026-10-06; the first text said 500) and takes a named skip for the resolver-dependent assertions only (Departs 5; operator ruling 1; the spec's AC-REALITY 2, 3 and the FR-025 sentence say the same): the project-wide report equality, the Missions examined-plus-skipped identity and Missions floors, the fallback list, the kind 1 to 3 oracles and per-Mission equality. The Ops walk and its oracle comparison,skippedCountequality, the Ops floors, the AST independence test and thekitty-specs/andkitty-ops/fingerprints run in both, because they need no network. Offline is not evidence for SC-002 or SC-007. - Independent counts: Missions (a scan for
meta.json), the fallback list (D-P14), kinds 1 and 2 (file presence plus the classifier), kind 3 (D-P6), Ops (a direct scan of every Op file and of the spine), evidence kinds (a regex classification of the stored strings); each compared with the examined count; numeric floors second. - Controls on one shared fixture: a real payload validates; the same payload with a planted host path, e-mail address, credential, unknown enum value or extra property fails; the wiring guard is extended so a removed check turns every case red.
- The case budget: one scan 40 to 52 s locally (spec); no new case over 120 s; the Project build once; per-Mission reports are filtered from the project-wide findings, then compared with the per-Mission entry point (cheap); one bounded case times the memoised pass, the Project build and the per-Mission reductions together (D-P5).
- Corpus-sized cases (plan-round ruling 4) live here and nowhere else: AC-DRIFT row 19 (equality with
is_mission_completedover every Mission declaring no coordination branch, guarded: the compared count equals an independentmeta.jsonscan, is above zero and is at least the floor of D-P13) and the AC-CROSS 4 cases on the real directories (at most 5 s for the realkitty-ops/listing, timed as the minimum of at least 5 repeats, D-P16; at most 120 s for oneProjectbuild and for one project-wide scan). Their discovered counts appear in the failure text (contracts/tool-extension-and-reader.md, G). Offline classification of the corpus-sized cases (applies ruling 1; derived from whether a case calls the coordination resolver or the network): (a) the AC-DRIFT 19 completion equality with its 490 floor and discovered-count guard RUNS offline (it reads each Mission's own directory for Missions declaring no coordination branch and never calls the resolver); (b) the realkitty-ops/listing bound (at most 5 s, minimum of at least 5 cold repeats) RUNS offline (a directory listing, no resolver, no network); (c) the one-Project-build bound, (d) the one-project-wide-scan bound and (e) the combined timing case TAKE the named resolver-dependent skip offline (the Project build and the scan reach the resolver, which offline answers each Mission's own directory with no fallback entry (operator ruling at WP07, 2026-10-06), so a bound measured offline does not measure the online path) and count as skipped, not as executed, in the "every generated case executed" tally. kitty-ops/fingerprint: the same helper askitty-specs/; both before and after.
Risks added or sharpened by planning
| # | Risk | Mitigation |
|---|---|---|
| P-1 | A new corpus-marked module is red in two gates the spec does not name | PD-2; registrations written by work packages in one dependency chain; the gate files are run after every module (quickstart). |
| P-2 | The two gates that guard the registrations cannot run in this checkout (respx missing; ruff missing), so a missing registration is first seen in CI | The orchestrator's Step 0 closes it in a synced scratch environment before any dispatch; if impossible the operator is told and the first signal is a throwaway CI run, not the PR. |
| P-3 | A stray .git at the system temporary root breaks tools that refuse a temporary directory below a repository (the tamper check, placement tests) | Every pytest and J-1 run sets TMPDIR to a directory whose ancestors hold no .git; the two architectural reds of that class on the previous slice are classified environment, not introduced. |
| P-4 | The check depends on the network and on a corpus condition (no non-merged Mission declares a coordination branch present only on a remote or as a remote-tracking ref; holds today: 0 such Missions, research R-3) | The check names any such Mission; a red is classified first; one diagnosed rerun at most; if the condition ever fails on main the point goes to the operator (the spec ruled the stock resolver, FRESH3-001). |
| P-5 | Kind 3 is noisy or its false positives are invisible to an oracle that shares the expectation (a manifest can name a mission_branch the product never created) | Provisional; "no local branch" wording; no corpus floor; IC-01 re-measures; the honest-rule criterion is checked against the re-measure; no resolver or src/ change (C-006). |
| P-6 | Upstream moved 133 commits including files the reader imports (the resolver module, mission_metadata.py, lanes/branch_naming.py, status/__init__.py, the read-path resolver, workspace/context.py, core/dependency_graph.py) and a directory-alias decision for bare-slug coordination Missions | D-0 sync and the symbol check; IC-01 re-measures; the resolver is used by outcome only, so an internal change that alters no outcome is an assumption (spec R-9); an outcome change is reported, not absorbed. |
| P-7 | The 0.1-second redaction bound (NFR-008) can flake under -n 4 contention | Measure first, record the margin in the work package report; the test is an absolute bound as the spec states, timed as the minimum of at least 5 in-test repeats with the control timed the same way (D-P16) and the planted quadratic mutation still fails; the measured minimum and margin go in the hand-off; a flake is diagnosed, never rerun to green; the escalation is to ask the operator for a bound, not to loosen silently. |
| P-8 | NFR-001 and NFR-009 are bounds with the remotes reachable and on a CI runner not yet measured; NFR-001 is a gain and needs a baseline | Local proxy now (R-12) with the stated local-to-CI factor (D-P5); the baseline medians of both jobs are recorded at Step 0; the CI figure and the pass rule at W-7; re-plan above 6 minutes of corpus job time. |
| P-9 | J-1 needs a JDK and network access, which the agent hosts lack | An orchestrator or operator step on a JVM-capable machine; if neither is available the operator is told before the draft PR and a second compaction is budgeted. |
| P-10 | provisional_check is word-satisfied by the previous slice's kind, code and truncated | The qualified-name tests with plants (D-P11). |
| P-11 | The ratchet edits to v1 tests (R-6) can weaken an assertion unnoticed | The reviewer compares each edit with the assertion it replaces; the planted entry that lacks "lane weights" fails; the squad checks them as ratchets. |
| P-12 | One unreadable non-completed Mission fails the project-wide report and the corpus job for everyone (AD-10, R-2) | missionId isolates per-Mission reports; the 500 is loud by design; the triage rule of Reflexivity item 2. |
| P-13 | IC-03 is large and touches v1 tests | One package, three ordered commits; the ratchet edits reviewed separately in the review report. |
| P-14 | The counting wrapper around Popen and the subprocess.run stub trip the global-state scans | pytest.MonkeyPatch only; the first task of IC-03 runs the named scan files against a stub; no allowlist entry is ever added. |
| P-15 | A work package owning a directory-wide glob pulls IC-02 into the big lane | The two things /spec-kitty.tasks must keep true (Lanes). |
| P-16 | SLICE_ALLOWED misses a file the slice touches | The data lists every expected file; the real-diff scope check at W-1 and W-7 reports the rest; an unused entry is removed at W-1. |
| P-17 | The one-line packs.yml command is edited from two lanes | The single-writer chain (Lanes, exception 3). |
Complexity Tracking
| Violation | Why needed | Simpler alternative rejected because |
|---|---|---|
Registrations outside the slice file set: three --deselect entries and three registry rows, selecting the heavy battery (PD-2) | A new corpus-marked module is red in two gates without them | Putting every test in existing modules (PD-1) grows modules of 1,000 to 2,100 lines and moves them to the serial corpus job. |
| Five helper modules for three reads | The oracles must share the memo without importing the reader (FR-025); the readers have different failure vocabularies | One module of about 2,400 lines is unreviewable and hides the independence boundary the AST test enforces. |
Reproducing the four v1 fallbacks in the Project builder instead of calling v1's load_source | The v1 helper stays untouched (a ratchet file) and the builder must reach the resolver only through the memo | Calling v1's build would make a second resolver pass and defeat the single-pass memo (NFR-001). |
| An extended scratch pair that also drops two tags (PD-4) | The bundle comparison must equal the baseline except Project | Leaving the tags would make the proof unequal for a reason that is not a contract change. |
| A planning-artifact work package (IC-10) | The close-out assessment is a write under kitty-specs/ | The orchestrator could append it, but the tracer discipline asks for an assessed close at the end; the previous slice used the same shape. |
Traceability: spec identifiers to plan elements
| Spec id | Plan element |
|---|---|
| FR-001, FR-002, FR-003, FR-004, FR-005, FR-006, FR-007 | IC-03; D-P4; D-P2; AC-PROJECT 1 to 8; contracts/operations-and-schemas.md (Project) |
| FR-008, FR-009 | IC-04, IC-07; D-P9; AC-DRIFT 23, 26 |
| FR-010 | IC-04, IC-07; data-model (drift order of decision); AC-DRIFT 1 to 3, 5, 17, 20, 21 |
| FR-011 | IC-04, IC-07; data-model (drift order of decision); AC-DRIFT 4 |
| FR-012 | IC-04, IC-07; D-P6, D-P7; research R-3; AC-DRIFT 6 to 13, 19 (fixture half in IC-07, corpus half in IC-09) |
| FR-013 | IC-01 (re-check), IC-04, IC-07; research R-4; AC-DRIFT 22; SC-003 |
| FR-014 | IC-04, IC-07; data-model (summaries); AC-DRIFT 24 |
| FR-015 | IC-03 (memo), IC-07 (strict sibling, fallback, network), IC-04 (descriptions); D-P2, D-P3; AC-DRIFT 14 to 19, 25 |
| FR-016, FR-017, FR-018, FR-019 | IC-05, IC-08; data-model (Ops order of decision); AC-OPS 1 to 9 |
| FR-020 | IC-05, IC-08; data-model (evidence pipeline); AC-OPS 10 to 12, 14 |
| FR-021 | IC-05, IC-08; AC-OPS 13 |
| FR-022 | IC-06, wrap-up W-1, W-7; D-P10; research R-5; AC-VERSION a to c |
| FR-023 | IC-03 to IC-06; D-P9, D-P11; contracts/operations-and-schemas.md (provisional table); AC-VERSION d, e |
| FR-024 | IC-03, IC-07, IC-08; each catalogue line mapped to its rows in "FR-024 catalogue line to acceptance row" below; "When it cannot do its job" column of the gate set |
| FR-025 | IC-09; D-P13, D-P14; "Reality-check design notes"; AC-REALITY 1 to 3 |
| FR-026 | IC-03, IC-07, IC-08 (fixtures per read); contracts/tool-extension-and-reader.md (fixture map) |
| FR-027 | IC-02, IC-04, IC-05; negative_cases.json; Departs 6 |
| FR-028 | J-1 after IC-06; W-2; Departs 4; AC-VERSION f |
| FR-029 | IC-03 (item 1), IC-06 (items 2 to 4); Departs 10 |
| NFR-001, NFR-009 | D-P5, D-P16; IC-01 (measure), IC-09, W-7; research R-12; P-8; AC-CROSS 4 (corpus-sized cases in the reality module) |
| NFR-002 | D-P1, D-P2, D-P16; AC-CROSS 4 (fixture-built half in IC-07 and IC-08, real-directory half in IC-09); AC-DRIFT 13; IC-07, IC-08, IC-09 |
| NFR-003 | D-P12; AC-DRIFT 27, AC-OPS 14; IC-09 |
| NFR-004 | IC-02, IC-09 (leak scans of corpus-built payloads); AC-DRIFT 24; AC-OPS 10 to 12; AC-VERSION e |
| NFR-005 | AC-CROSS 1 |
| NFR-006 | Gate set (ruff pair, mypy discipline); independent counts and minimum input counts; same-fixture controls |
| NFR-007, C-006 | Seam; scope function (IC-06); AC-CROSS 2 |
| NFR-008 | IC-08; P-7; D-P16; AC-OPS 14 |
| C-001 | Operator decisions; no re-opened ruling |
| C-002 | Contracts table; D-P10; IC-06 |
| C-003 | SLICE_ALLOWED; scope check W-1, W-7 |
| C-004 | Reflexivity item 7; add-only check |
| C-005 | Charter Check row; hygiene scan; quickstart (placeholders) |
| C-007 | Technical Context; AC-CROSS 3 |
| C-008 | Terminology row; AC-VERSION e |
| C-009 | D-P12; AC-DRIFT 27; AC-OPS 14 |
| OR-1 | IC-03; Contracts table; D-P10 |
| OR-2 | IC-01 (re-measure), IC-04, IC-07; D-P6; FR-013; the reality-check example is asserted by the kind-3 oracle comparison of AC-REALITY 2 (IC-09) and by the named kind-3 plants of AC-DRIFT 6 to 9 (IC-07); the provisional marker by AC-VERSION d (provisional-name test) |
| OR-3, OR-4 | IC-04, IC-07; AC-DRIFT 1, 3 |
| OR-5 | IC-05, IC-08; AC-OPS 6 |
| OR-6 | IC-05, IC-08; AC-OPS 3, 5 |
| OR-7 | IC-04, IC-07; AC-DRIFT 23 |
| OR-8 | IC-03; AC-PROJECT 7; FR-007 |
| OR-9 | IC-04; contracts/operations-and-schemas.md (no badge namespace) |
| OR-10 | IC-03; Project description (cost); AC-PROJECT 7, 8 |
| Round 2: ARCH-001, ARCH-002, GOV-007/ARCH-008, COMPLETE-004, COMPLETE-013, OQ-1 to OQ-6, GOV-001, COMPLETE-001 | D-P6, D-P7; IC-04, IC-05, IC-07, IC-08; AC-DRIFT 7, 8, 9, 10, 13; AC-OPS 4, 9, 11 |
| Round 3: FRESH-001, FRESH-005, FRESH-007, FRESH-014 | D-P3, D-P4; IC-03, IC-07; AC-DRIFT 10, 17, 28; AC-PROJECT 7 (FRESH-005: no process of its own) and 8 (FRESH-005: the cost sentence) |
| Round 4 and 5: FRESH2-001 to FRESH2-010, FRESH3-001 to FRESH3-007, FRESH4-001 to FRESH4-004 | D-P2, D-P3, D-P4; Reflexivity item 2; AC-DRIFT 14 to 21; AC-REALITY 1; Departs 5 |
| AD-1 to AD-10 | built as written; D-P7 (AD-10); IC-04, IC-05, IC-07, IC-08 |
| AC-PROJECT, AC-DRIFT, AC-OPS, AC-VERSION, AC-CROSS, AC-REALITY | "Test strategy per acceptance criterion" |
| SC-001 | IC-03; SC-002 IC-09; SC-003 IC-01, IC-04, IC-07; SC-004 IC-08, IC-09; SC-005 IC-02, IC-09; SC-006 IC-06, W-7; SC-007 IC-09, W-7; SC-008 IC-06; SC-009 J-1, W-2, W-7; SC-010 the named rows of AC-DRIFT and AC-OPS |
| R-1 to R-9 (spec) | Risks P-4, P-5, P-9, P-11, P-12; D-P6 (R-1, R-8); D-P2 (R-9); D-P10 (R-5) |
| A-1 to A-5 | research R-2 (A-2); Technical Context; A-5 re-checked by the symbol check |
FR-024 catalogue line to acceptance row
Every line of the spec's FR-024 catalogue has at least one named row (checked against the Test strategy above; the two lines that had none are now carried by row 23 of AC-DRIFT and row 10 of AC-OPS).
| FR-024 catalogue line | Acceptance row(s) |
|---|---|
Project version, schema: absent, unreadable, wrong shape gives null; health schema_drift | AC-PROJECT 1, 2, 3, 4 |
Project.currentBranch: detached, no git, unreadable HEAD, unrepresentable name gives null | AC-PROJECT 5 |
Project.lastActivityAt: no Mission or none with activity gives null | AC-PROJECT 6 |
GET /drift unknown Mission: 404 mission_not_found | AC-DRIFT 15, 26 |
GET /drift unreadable directory, resolver outcome, status.json OSError, event log, meta.json, lanes.json (also invalid UTF-8): 500 drift_scan_unreadable | AC-DRIFT 2, 10, 11, 17, 21 |
GET /drift CoordinationBranchDeleted: not a failure, read from the own directory | AC-DRIFT 17, 18 |
GET /drift remote unreachable or erroring: not a 500; a 200 from the own directory with no fallback entry (operator ruling at WP07, 2026-10-06); any other resolver error stays 500 drift_scan_unreadable | AC-DRIFT 14 (the offline row); AC-REALITY 1 (the offline outcome asserted alone) |
GET /drift status.json not UTF-8, not JSON or not an object: CORRUPT_JSON finding | AC-DRIFT 2, 21 |
GET /drift legacy-shaped lanes.json: not evaluated, on the named list | AC-DRIFT 10; AC-REALITY 2 (named list) |
GET /drift branch listing fails: 500, never kind-3 findings | AC-DRIFT 12 |
GET /drift nothing to report: 200, findings: [] | AC-DRIFT 23 (the clean-repository case) |
GET /ops/invocations kitty-ops/ absent: 200 empty page | AC-OPS 9 |
GET /ops/invocations not listable, an Op file or the spine that cannot be opened, spine invalid UTF-8: 500 ops_unreadable | AC-OPS 9 |
GET /ops/invocations bad cursor: 400 invalid_page_cursor | AC-OPS 2 |
GET /ops/invocations legacy, unreadable, vanished or index-only record: skipped and counted | AC-OPS 3, 4, 5, 8 |
Ops.evidence no evidence_ref or a credential: explicit null | AC-OPS 10 (no evidence_ref), 11 (credential) |
| "Every check this Mission adds fails when it cannot do its job" (floors, plants, injected faults) | contracts/tool-extension-and-reader.md (G); AC-REALITY 2 (floors); every row's control and plant |
Final report notes for the next command
Branch contract, repeated for the report: current branch issue-5776-mission-status-contract-health-drift-ops; planning/base branch the same; final merge target main through one draft pull request after the wrap-up sequence. PD-2 (the registration paths outside the spec's file set and the heavy battery they select on this PR) is operator-acknowledged (plan-round ruling 7), not open. Next suggested command: /spec-kitty.tasks after the adversarial squad of this point-cut (the user or orchestrator runs it; this plan does not generate work packages). The tasks phase must author final owned_files before the first finalisation (research R-14).