Implementation Plan: Mission Status contract 1.1

Branch: issue-5625-mission-status-contract-1-1 | Date: 2026-10-04 | Spec: spec.md

> Operator ruling 2026-10-05: no new contract version. The Mission Status contract is unreleased on main (info.version: 1.0.0-SNAPSHOT, never tagged). This slice ships in the same release as the previous API changes: info.version stays 1.0.0-SNAPSHOT, the CHANGELOG additions merge into the existing 1.0.0-SNAPSHOT section, and no tag is needed. "1.1" in the Mission slug, the issue title and the test module name is the working name of this slice, not a version. The "minor-version proof" is an additive proof against the contract tree on main. Wherever a passage below still speaks of 1.1.0, a minor bump, a release tag, the live baseline test or ACK-1 (the OD-1 X history in the options table and in ACK-1 a to c are kept as the record only), this ruling and the amended rows (IC-05, D-P10, D-P11, W-1 to W-7, P-5, P-9, P-10, P-12, R-7) prevail. Input: Mission specification from kitty-specs/mission-status-contract-1-1-01M42XJC/spec.md (issue #5625, part of epic #5528)

Note: Scaffolded by spec-kitty plan --mission mission-status-contract-1-1-01M42XJC --json (scaffold_only: true) and filled by hand. Companion planning artifacts in this directory: research.md (the evidence, R-1 to R-15), data-model.md, quickstart.md, contracts/operations-and-schemas.md, contracts/tool-extension-and-reader.md and the three tracer files (tracer-*.md). The sections follow the layout of the v1 plan (a) to (l).

Branch contract

  • Current branch at plan start: issue-5625-mission-status-contract-1-1.
  • Planning/base branch and merge target recorded by the tooling: issue-5625-mission-status-contract-1-1 (branch_matches_target: true). The branch head is the merge of origin/main at its tip when the plan started (git merge-base HEAD origin/main equals that tip; PR #5581, the reality check and its projector, is in the tree).
  • Integration branch for the finished work: main, through one pull request opened as a draft after the wrap-up sequence (accept, dev-assist cleanup, aggregate adversarial squad, history compaction, rebase onto upstream, then the draft PR). Work packages never push and never open a pull request; implementers and the orchestrator never merge; nothing is pushed to main.
  • Topology in meta.json: lanes. Work packages run in lane worktrees, but the lanes are computed, not chosen: compute_lanes (src/specify_cli/lanes/compute.py) puts every pair of code work packages whose owned_files overlap into one lane, and a dependency only becomes an edge between lanes. The plan therefore states the dependency per work package and the expected computed outcome (section "Lanes: the expected computed outcome"), names no lane letter and no manual merge, and leaves the final lane assignment to /spec-kitty.tasks (finalisation).
  • The pull request is not held for any release or tag (operator ruling 2026-10-05, CL-2): the contract is unreleased, info.version stays 1.0.0-SNAPSHOT, and the additions ship in the same release as the previous API changes.
  • Wrap-up order and which evidence is final. accept first (it runs before consolidation, the tooling's order), then lane consolidation through spec-kitty consolidate (the lane branches into kitty/mission-<slug>, which is then integrated into the target branch of meta.json; W-1 ii), then the registration check, the scripted scope check and the move of the #5625 issue-matrix row to a terminal verdict (all W-1; the matrix rows themselves were set before the first approval, and the acceptance-criterion verdicts are authored before accept), JVM shake-out (W-2), dev-assist cleanup (W-3), aggregate adversarial squad (W-4, on the pre-rebase tree, report-only), history compaction (W-5), rebase onto upstream main, no tag needed (W-6), final evidence (W-7), then the draft PR. The tree changes at W-5 and W-6, so only the output of W-7 is cited as final evidence; anything recorded earlier is labelled pre-rebase (see "Wrap-up checklist").

Operator decisions

Four forks were open and are resolved, decided by the operator 2026-10-04: OD-1 = X-pure (three new pure test modules, six registrations including the packs.yml --deselect entries and the corpus-completeness architectural test; no committed live baseline test, so ACK-1 does not apply); OD-2 = Y (the example-required guard lives in-test); OD-3 (a work package id held by two files is served by the first regular work package file in byte order; the rule is documented in the contract); OD-4 (scope the leak scan's text pass so path-name fields are judged by the artifact-path rule, AD-19, not by the e-mail pattern; keep the prototype's proof that the real tree still scans clean). The option tables below are kept as the record of what was weighed; the plan text is written for exactly these decisions. Where the tables and the text say "open" or "recommended", read "decided by the operator 2026-10-04".

IDQuestionOption XOption X-pureOption YRecommendation
OD-1 (decided by the operator 2026-10-04: X-pure; Option X and Option Y are not chosen (OD-1 = X-pure, operator 2026-10-04))Where do the new reader and proof tests live, and is there a committed live baseline test?Four new test modules: test_mission_status_artifacts.py, test_mission_status_detail.py, test_mission_status_contract_1_1.py (the pure proofs) and test_mission_status_baseline_live.py (the one tag-dependent test, isolated so the pure module is green before the tag; ACK-1). They run in the router job tests (contract tools). What X costs, all of it (items (1) to (6)): (1) four registrations outside the spec's slice file set: one --deselect per module in the built-in-corpus-suite command of .github/workflows/packs.yml (that whole pytest command is one physical line, so two lanes appending to it would conflict; the four writers (IC-05 twice, IC-06, IC-07) all own those two files, so the lane computation puts them in one lane and they run one after the other, see "Lanes") and one row per module in _CORPUS_MARKED_MODULES of tests/architectural/test_ci_corpus_trigger_completeness.py (one entry per line); proven necessary and sufficient on a scratch repository (research R-6). (2) It selects the architectural-heavy battery on this PR: editing .github/workflows/packs.yml selects it through router group ci_config (.github/workflows/) and editing the registry file selects it through group architectural (tests/architectural/); either alone is enough. The battery is two shards (1/2, 2/2), timeout-minutes: 30 each, full-history checkout, --all-extras; it is CI-owned (charter: no full architectural sweep in mission work). Baseline on main: in the two of the last three ci-router.yml runs that ran it, both shards were green in 7 to 10.5 minutes (research R-13); a red there on this PR is first compared with that baseline before it is attributed to the Mission. (3) Scope: the scripted scope check (SC-008, the AC-VERSION scope bullet, D-P10) is read with exactly two paths added to the slice file set, .github/workflows/packs.yml and tests/architectural/test_ci_corpus_trigger_completeness.py; the allowed set is data, so nothing else widens (D-P10, "Departs from the spec" 5). (4) The Charter Check row "workflow YAML is not touched" and the Contracts-table row "CI workflows preserved" hold only under Y. (5) The shared registrations bind IC-05, IC-06 and IC-07 into one computed lane with IC-03 and IC-04 ("Lanes"); that fixes the order of those work packages and costs no time, since the Mission's dependencies are sequential anyway. (6) The ACK-1 costs, which only X carries; acknowledging X acknowledges them (ACK-1 below): a committed test that is red by design until the release tag exists (the one accepted red until then, local only); the same test red in every clone that never fetched the tag (a contributor clone of a fork, a shallow clone) and under a local make test-full (its parallel pass is not marker-filtered); a kind of test new to this directory (its neighbours carry fast and corpus and are green on a clean clone); a departure from FR-017 items 1 and 5 ("Departs from the spec" 10); and a retirement duty that falls on a later actor (ACK-1 c, owner named at D-0). Option X-pure drops exactly this item. What X buys: four focused modules (estimated 800, 1,000, 450 and 80 lines, planning estimates, not measures) instead of growing existing ones, and parallel execution (-n 4) outside the 10-minute serial corpus job.Three new pure test modules: test_mission_status_artifacts.py, test_mission_status_detail.py and test_mission_status_contract_1_1.py; no live module and no committed live test. The comparison against the released tag runs once at W-7 as a recorded command, exactly as under Y; FR-017 item 1 is evidenced at W-7 only. They run in the router job tests (contract tools). What X-pure costs, all of it: (1) six registrations outside the spec's slice file set (three --deselect entries in the one-line built-in-corpus-suite command of .github/workflows/packs.yml and three rows in _CORPUS_MARKED_MODULES), written by IC-06, IC-07 and IC-05 (once) in one dependency chain, so the same single-writer rule and the same one computed lane as X ("Lanes"). (2) It selects the architectural-heavy battery on this PR, exactly as X does (either edited file alone selects it; baseline in research R-13). (3) Scope: the same two extra paths as X are added to the allowed data (D-P10, "Departs from the spec" 5). (4) The Charter Check row "workflow YAML is not touched" and the Contracts-table row "CI workflows preserved" hold only under Y. (5) The permanent guard on the baseline's byte identity is the pure function with its planted mutations only, the same weakness as Y: the live comparison against the tag is evidence, not a test. (6) The test lines added are an estimated 2,250 in three new modules (planning estimates, not measures). What X-pure does not cost: none of ACK-1: no test that is red by design, no red in tag-less clones or under make test-full, no departure from FR-017 item 5 on account of a committed test, no retirement duty for a live module (the pure module's version assertion keeps its slice lifetime, D-P10). What X-pure buys: X's focused modules and -n 4 parallelism outside the 10-minute serial corpus job, and a tag-free pure module set that is green on every clone.No new module. Every new reader and proof test joins test_mission_status_payloads.py (1,065 lines today), test_mission_status_examples.py (795) and the reality module (708); no committed live baseline test (a live id inside test_mission_status_examples.py would inherit that module's fast marker, which the nightly fast or unit run selects on a shallow, tag-less checkout: red on every nightly, ACK-1); the comparison against the released tag runs once at W-7 as a recorded command instead (ACK-1). A variant that keeps a committed live test under Y needs its own corpus-only module, hence two registrations, the heavy battery on this PR and all of ACK-1: it is not planned. (The converse, X without the live module, is option X-pure.) What Y costs: (0) the detail reader's change-state matrix drives real git repositories and worktrees (D-P4, IC-07), and under Y it lands in test_mission_status_payloads.py, whose pytestmark includes fast; pytest.ini defines fast as pure logic with no subprocess or git, so those tests would be selected by the nightly fast or unit run, against the definition the plan applies to X-family (ACK-1 b) and a module-level marker cannot be removed per test (tracer-tooling-friction F-19). Y therefore pays one of two prices, chosen with Y and recorded at D-0: follow the reality module's precedent (it carries fast beside its git init; the git tests inherit fast, the definition is bent, no tag is read so the shallow nightly stays green), or change the marker of the payloads module (its existing pure tests then leave the nightly selection, a wider edit than the tests need). The rule differs from the X-family only because a module-level marker cannot be split inside one module. The estimated 2,200 added test lines land in modules that are already large (an estimate: about 2,800 lines for the payload test module, about 1,250 for the examples module; planning estimates from the v1 ratio of test lines to helper lines, not measures); the new tests then run serially in the router corpus job tests (corpus-blocking) (10-minute timeout, 178 s and 183 s baseline; the extension fits NFR-001, research R-5 and R-6); the permanent guard on the baseline's byte identity is the pure function with its planted mutations only; the live comparison against the tag is evidence, not a test. What Y buys: no registration, no workflow or architectural edit, no architectural-heavy run on this PR, the scope proof exactly as the spec states it, no chokepoint on packs.yml.X-pure; Y as the fallback; X only if the operator weights a committed tag test above ACK-1. With ACK-1 priced into X, X is X-pure plus one thing: a committed test that compares the tree with the released tag, bought at the price of X's item (6) (red by design, red in tag-less clones and under make test-full, a departure from FR-017, a retirement duty) for a guard that expires at the 1.1.0 release and whose job the W-7 recorded command does once. X-pure keeps what X buys and pays X's items (1) to (5) in full, with six registrations instead of eight. Against Y, X-pure trades six registrations, the heavy battery run on this PR, a scope reading and a single-writer rule for about 2,250 test lines that are not added to modules that are already large and for staying off the serial corpus job: that margin is narrow, and Y is safe and complete (research R-6). The plan text is written for X-pure; X is X-pure plus the live module, ACK-1 (a) to (c), "Departs from the spec" 10, the live parts of IC-05 and the live checks of W-1 and W-7 (each marked "X only" where it occurs), and Y differs in every X-family statement (registrations, heavy battery, scope extension, lane binding); the test map in contracts/tool-extension-and-reader.md names all three homes. The operator is asked to acknowledge the scope extension (as for FR-015) under X or X-pure, and ACK-1 under X only.

Reading OD-1 (notation used in the rest of the plan). "X-family" means X or X-pure; "Y" is always the option with no new module. Counts, stated once: new test modules three (X adds the live module: four); --deselect entries three and registry rows three (X: four and four), so six registrations (X: eight); IC-05 appends one pair for the proof module (X: two pairs, the live module's too); writers IC-06, IC-07 and IC-05 (X: IC-05 twice). A statement about registrations, the heavy battery, the scope extension, the lane binding, the single-writer rule or the IC-05-after-IC-07 edge holds for the X-family; a statement about the live module, its isolated marker set, the tag-less red or ACK-1 holds for X only; under Y none of these exists.

Decided reading (tasks phase, round 1 of the squad fix; add-only). The decided options are OD-1 X-pure, OD-2 Y, OD-3 first in byte order and OD-4 the scoped text pass. Read every "X-family" statement below as X-pure, every "X only" or "ACK-1" statement about the live module as the record of the option not chosen (OD-1 X, OD-2 X), and every "OD-4 X" or "under the recommended OD-4 X" statement as the chosen scoped text pass (OD-4 X is not an unchosen option: it is decided). "Y" for OD-1 and OD-2 is the option not chosen for OD-1 and the chosen option for OD-2.

IDQuestionOption XOption YRecommendation
OD-2 (decided by the operator 2026-10-04: Y; Option X is not chosen (OD-2 = Y, operator 2026-10-04))Where is the "every required example exists" guard?Add entries to contracts/tools/fixtures/example_check/required_examples.json, so the workflow job python-checks also fails when a required example is deleted. What X costs, all of it: (1) a third path outside the slice file set (spec Terminology: under contracts/tools/ only four files may change; this one is a fixture file): contracts/tools/fixtures/example_check/required_examples.json; (2) the scripted scope check (D-P10) is then read with that path added to its allowed data (and to OD-1 X's two, when both are chosen); (3) the operator's acknowledgement of that scope extension, as for OD-1 X and FR-015; (4) tests/contract/test_example_check.py reads this file as REAL_MANIFEST, so its expectations are re-checked in the same change; (5) one more row under "Departs from the spec" and in the Complexity table. What X buys: the guard also fails in the workflow job python-checks, not only in the router tool job.Keep the guard in the in-test cases of the existing tests/contract/test_mission_status_examples.py (inside the slice file set), enforced by the router tool job. example_check in the workflow still validates every example and reports an orphan file.Y. It stays inside the slice file set and is equally non-vacuous (the test fails on a deleted example). X adds a fixture edit for a guard that already runs.
OD-3 (decided by the operator 2026-10-04: the first-in-byte-order rule)Which file serves a work package id held by two files? (4 ids of one archived Mission: WP02, WP03, WP04, WP06 of cli-boundary-robustness-01M2NQCB; 3,197 files, 3,193 ids.)The first regular, non-symlink file in byte order of the file name among the wp_task_files entries that hold the id is the work package's authored file (D-P6); the work package's prompt file stays the spec's name-keyed definition (the first qualifying file in byte order), which for the four duplicates is the same file. The detail universe is the 3,193 distinct ids.Refuse the id as ambiguous (a 500 source_unreadable). The corpus run would then hold four expected 500s, which contradicts "zero exclusions, examined equals discovered".X. It is the only rule consistent with the spec's own prompt-file rule (the first in byte order), the v1 corpus (one payload per file) and the empty skip list. The spec did not know of the duplicates (research R-5).
OD-4 (decided by the operator 2026-10-04: scope the text pass)How does the leak scan's raw-text pass treat a legitimate artifact path with an at sign (for example a retina-style image name: a stem, an at sign, a scale factor and an extension)? The scan runs the human e-mail pattern over every raw line of every file under contracts/, before and independent of the structured pass that AD-19 changes; run on the planning base, leak_codes over such a name (a probe assembled at run time from fragments, never typed as a literal) returns EMAIL, so a control or example holding such a path would be reported as EMAIL in text, and because run_self_test runs on every leak_scan invocation a flagged control would fail every run of the tool (Contracts workflow, negative-tests, router tool job).Scope the text pass (edit inside leak_scan.py, within the slice file set). The text pass blanks, before scanning, the character span of every YAML scalar value that the artifact-path class owns (the value of a key path or artifactPath with no x-source or x-derived key on the way down), so those values are judged only by malformed_artifact_path; every other part of every line, including a sibling title on the same flow-mapping line and every x-source path, is still scanned for host paths and e-mail; SECRET is never blanked (a token inside a path value is still reported by the text pass, stricter than AD-19's accepted residual, which says credentials in file names are not detected). Cost: about 45 added lines in leak_scan.py (two small functions and a changed scan_text signature), a changed module docstring (it now says "nothing is exempt" and must name this masked span), and one more way for the scanner to be wrong; JSON files stay unmasked (no JSON file under contracts/ holds an artifact path). Verified on a scratch copy (research R-14): an at-sign path under path passes in text and structure; the same line's title holding an address is still reported; an x-source path with an address is still reported; a token in a path value is reported; the real contracts/ tree still exits 0 with the scanner edit alone (values_strict=333, values_human=8137, unchanged from the planning base); once the ten strict names of FR-015 also land, the same scan reads values_strict=518 and values_human=7952 (measured on a scratch copy at the plan fix, research R-8; the new contract files add further values, so any pinned count is taken on the final tree, never copied from this text). Consequence for AD-19: the premise "a path with an at sign is listed and must pass" is delivered by the scanner itself, in every contract file, control and example.Avoid the pattern (no scanner change beyond FR-015). No file under contracts/ (controls, examples, fixtures) holds an at-sign path; the at-sign name is proved at reader level only: payload_leaks over outputs built from a synthetic Mission (outside the contracts/ tree, where the text pass does not run) and the artifact reader's own tests. The controls of the artifact-path kinds carry only the names the text pass tolerates (a home/<x>/ segment, a space, an accented letter; verified safe on the planning base). Cost: AD-19 is delivered for the reference reader and payload_leaks, not for the shared scanner's text pass: a contract example or control holding an at-sign path is reported EMAIL in text, which is the pre-existing behaviour left in place, and no contract example can illustrate such a name. SECRET and EMAIL in a path value stay detected in text, which is also stricter than AD-19's residual. No added scanner code.X. It is the only option that makes the scanner honour the operator-acknowledged AD-19 premise rather than route around it, and Y leaves a latent trap (an example author adds a retina-style image name and the whole tool goes red). X is verified, bounded (one function pair, tests listed in contracts/tool-extension-and-reader.md A) and stays inside the slice file set. Y is the fallback if the operator prefers a smaller scanner change.

Rename-names seam (NFR-007): a public seam exists, no operator decision is required. kernel.git.changed_paths(cwd, *revs, renames=False) (src/kernel/git/listing.py, exported in kernel.git.__all__) lists a rename as a delete plus an add, so both names appear; run on a scratch repository (research R-4). The plan therefore calls it and plans no change under src/. merge_base_changed_files_checked cannot serve rule 2 (it returns the new name only), so the reader composes git_merge_base with changed_paths.

Acknowledgement ACK-1 (a departure, not a fork) — VOID: the operator decided OD-1 = X-pure on 2026-10-04, so no live test exists and nothing below applies; kept as the record. Spec FR-017 item 1 asks for permanent assertions green before and after the release, and item 5 names the Contracts breaking-change job as the only accepted red. The plan departs on three connected points; the operator is asked to acknowledge them together with OD-1 to OD-4. ACK-1 attaches to OD-1 X only (it is item (6) of X's cost list); it is void under X-pure and under Y.

  • (a) Local red until the tag (OD-1 X). The live baseline test (it takes the baseline from the released tag with git archive) fails, never skips, while the tag is missing, so a local run of that one module between IC-05 and the tag is red. It lives in its own module, test_mission_status_baseline_live.py, so the pure module is green first; the only accepted red until the tag is exactly the ids of that module, and the WP acceptance for IC-05 requires the reviewer to run the pure module green, then the live module alone, and show its stated failure message. Nothing of this is observable in CI: the branch is not pushed before the tag and the Contracts workflow triggers only on pull_request and push to main, so the spec's accepted red (the breaking-change job at 1.1.0-SNAPSHOT) is a local-only expectation, never a CI state. Under OD-1 X-pure and under OD-1 Y there is no committed live test (see OD-1), so (a), (b) and (c) fall away and the W-7 command run is recorded evidence.
  • (b) Marker set, and every clone without the tag (OD-1 X). The live module carries pytest.mark.contract and pytest.mark.corpus and neither fast nor unit. Its neighbours carry fast; of the new pure modules the artifacts module and the 1.1 proof module may keep it, because they read no tag, no history and start no subprocess. The detail module does not carry fast: it builds real git repositories and worktrees (D-P4, IC-07) and pytest.ini defines fast as pure logic with no subprocess or git, so it carries contract and corpus plus git_repo (the repository's marker for tests that create real git repositories); the nightly fast or unit run therefore does not select it and it runs in the router job tests (contract tools) only. The reality module already carries fast while running git init; that precedent is not copied under the X-family (under Y it is the cheaper of the two prices, see the Y cost in OD-1). Checked against the selections: the nightly interpreter shard 4 runs pytest -m "fast or unit" over tests/contract (.github/workflows/ci-nightly.yml, the step that ends at line 646) on a checkout with no fetch-depth and no tags (the checkout at line 627); a live test carrying fast would fail on every nightly and the nightly escalation files a deduped P0 for a red on main (scripts/ci/nightly_escalation.py, the step at line 658). The corpus-only marker set is not selected by that expression: --collect-only -m "fast or unit" over the live module collects zero ids (a W-1 check). The jobs that do select the module (tests (corpus-blocking) and tests (contract tools) of ci-router.yml, checkouts at lines 859 and 891; the packs corpus suite, line 265) all check out with fetch-depth: 0, which fetches every tag, and the packs run deselects the module besides. The fork for the operator: a clone that never fetched the tag (a contributor clone of a fork, a shallow clone) and a local make test-full run there (its parallel pass is not marker-filtered) see the live module red. The plan accepts that red, loudly: the failure message names the tag, the fetch recipe git fetch <remote> tag contract-mission-status-v1.0.0 (it works on a shallow clone) and the --baseline-root recipe. Skipping when the tag is absent (or outside CI) breaks the never-skip rule (FR-020) and is rejected; acknowledging ACK-1 acknowledges this red. A tag-less shallow-clone run (git clone --depth 1; pure modules green, live module red with the stated message) is part of IC-05's acceptance and of W-7.
  • (c) Lifetime. The live module has one baseline, contract-mission-status-v1.0.0; once 1.1.0 ships it compares the wrong thing. Its docstring says so and names its retirement: its named owner (below) or the Mission that cuts the 1.1.0 release or the next contract-changing Mission, whichever acts first, deletes the module and its --deselect entry and registry row (the corpus-trigger gate fails on a stale row), or rebases its baseline tag to the then-latest release. Owner, tracker hook and exit (Standing Order 5: transient scaffolding carries an issue, an owner and an exit). The issue is #5625 itself (its D-0 claim comment carries the checklist line, so no follow-up issue is opened); the owner is the Human-in-Charge of #5625 unless the operator names another person at D-0; the exit is the first of the 1.1.0 release commit and the next contract-changing Mission; the retirement is one small commit deleting the module, its --deselect entry and its registry row. A release is a maintainer action, so a release Mission may never be opened: in that case the owner deletes the three artefacts in an Op at the time of the release commit, and the closing tracker comment (the step after W-7) states this duty so that the maintainer cutting the release sees it. Nothing forces the retirement (the corpus-trigger gate fails on a stale row only when the module is deleted without it, not the reverse), which is why it has a named owner. Until then, with the tag present in every CI checkout, it is green. The pure module's version assertion has the same lifetime (D-P10).

No release tag is needed (operator ruling 2026-10-05). The Mission Status contract is unreleased on main (info.version: 1.0.0-SNAPSHOT, never tagged) and this slice ships in the same release as the previous API changes, with no new version number. There is no operator dependency on a tag and no hold on the PR. Dispatch point D-0: at implement start, before IC-01 is dispatched, the orchestrator does the tracker claim (Charter Check, Standing Order 8). The additive proof compares the candidate with the contract tree on main (aef7cc967 or the current origin/main) through the pure functions and the --baseline-root recipe; W-6 rebases onto upstream main and W-7 repeats the comparison against the main tree. (The earlier text about a release tag, its fallback and the live baseline test is superseded; ACK-1 is void.)

Summary

Deliver the additive slice ("1.1" is the working name of this slice, not a version) of the Mission Status contract: three new operations (getWorkPackageDetail, listArtifacts, getArtifactContent) and the schemas, parameter, responses and examples they use, all new files under contracts/mission-status/, plus the index entries, the path map, the unchanged version 1.0.0-SNAPSHOT and the CHANGELOG additions in its existing section. Prove it three ways: a test-tree reference reader that implements the rules well enough to build all three reads over this repository's own Missions and over synthetic repositories; a reality-check extension that runs it over every Mission with examined-equals-discovered floors, controls and leak scans; and an additive proof against the contract tree on main (breaking check over a scratch baseline with a lowered version, byte identity, bundle comparison, scope function, planted negatives; no version-bump assertion). Extend the shared contract tooling only as FR-015 states. No file under src/ changes.

Technical approach in one paragraph. The reader is two helper modules beside the #5581 projector (_mission_status_artifacts.py, _mission_status_detail.py) over public readers, with an injectable file-system seam and an explicit host capability so every failure and every git state is constructed, never discovered. The corpus run passes a host without worktrees, so changeState is unknown and worktreePresent is false by construction. The artifact-path rule is one predicate (malformed_artifact_path), stated in the spec, in the ArtifactPath schema pattern and in the scanner, and tested three ways against each other. The new tests run in the router job tests (contract tools); the corpus extension runs in tests (corpus-blocking). No new CI job. Where the work lands in lanes and wrap-up steps, and which evidence is final, is stated under "Lanes: the expected computed outcome" and "Wrap-up checklist".

Technical Context

Language/Version: Python 3.11+ for contracts/tools/ scripts and tests/ (CI runs 3.12; no 3.12-only syntax, 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-005). The pinned oasdiff and vacuum binaries of contracts/tools/pins.json are CI-only and used locally only through install_tools.py into a scratch directory. Storage: Files only. Committed: new contract files, examples, the CHANGELOG entry, one pinned fixture tests/contract/fixtures/mission_status_artifacts_expected.json. Never committed: the bundle. Testing: pytest in tests/contract/ (router jobs tests (corpus-blocking) and tests (contract tools)), 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/; no src/ change. Performance Goals: NFR-001: corpus job at most 8 minutes (baseline 2 min 58 s and 3 min 03 s measured on CI; expected 4 to 5 minutes), no new case over 60 s (planned maximum: the 1000 times 262,144 byte listing at 6.2 s); two bundle builds byte-identical (NFR-005). Constraints: C-001 to C-005; the slice file set of the spec's Terminology; only two options extend it: OD-1 X or X-pure by exactly two paths (.github/workflows/packs.yml, tests/architectural/test_ci_corpus_trigger_completeness.py) and OD-2 X by exactly one (contracts/tools/fixtures/example_check/required_examples.json); the recommended options (X-pure for OD-1, Y for OD-2) extend it by the first two only, OD-3 and OD-4 extend nothing (OD-4 X edits leak_scan.py, which is inside the set); the scope check reads the extensions as data (D-P10); public repository: no absolute path, user name, e-mail address or private reference in any file. Scale/Scope: 548 Missions, 3,193 distinct work package ids in 3,197 files, 15,561 eligible artifact files, 1,322 review-cycle files, 14,638 owned-file entries; nine of the eleven files over 256 KiB at specify time are root event logs, which the listing excludes.

Charter Check

GATE: passed before research; re-checked after design (second column).

Charter rulePre-designPost-designPlan element
Single canonical authority (DIRECTIVE_044)passpassOne predicate for the artifact path (D-P7); one reader per read, imported by the reality check and the synthetic tests; the owned-files matcher is reproduced once with a parity test against its two repository copies (D-P3) instead of a third copy; the leak patterns stay in leak_patterns.py.
Architectural alignment (DIRECTIVE_001)passpassSee Seam: no src/ change, no kernel reach beyond the public kernel.git listing seam, no coupling to hosted transport.
Tiered rigourpasspassThe contract, its leak rules and the readers are core and get planted-violation proof and mutation tests. Workflow YAML is not touched under OD-1 Y; under the X-family packs.yml gains three --deselect entries and the corpus-trigger registry three rows (X: four and four) (OD-1 states the costs, including the heavy battery it selects).
ATDD-first (C-011)passpassThe first commit of every work package is its failing test or planted fixture, red on the planning base, green on the final commit; the reviewer verifies red then green. Declared exceptions, not claimed as red-first: the three content and title leak kinds (content-host-path-and-email, credential-in-content, credential-in-title) already pass on the planning base and are regression controls (they prove the artifact-path edit did not weaken the human-text scan); the behaviour-preserving campsite commit; (the live baseline test no longer exists, ACK-1 void). The red-first split of the 25 planted kinds (22 and 3) is measured in research R-15.
Terminology (Mission, never feature; status lane versus code lane)passpassA new terminology scan over the module (zero hits today) and the 1.0.0-SNAPSHOT section; every description states which sense of lane it uses.
Standing Order 1 (adversarial squad)n/arequested after this point-cutAdvisory, never a gate; the orchestrator runs it. Contested points are listed as squad notes below.
Standing Order 2 (campsite first)passpassOne distinct behaviour-preserving opening commit on a counted duplicate in the file about to change (Campsite-clean).
Standing Order 3 (tracer files)passpassThe three tracer files carry plan-phase entries.
Standing Order 4 (test remediation, red-first)passpassRed-first through the pre-existing entry points (the leak scanner, the reality check, the payload helper), the red proven on the planning base before the fix (ATDD row); stale tests are re-pinned, not skipped (the counts those tests pin today, enums=3 values=19 in test_enum_pin_check.py and the path-key set and MIN_EXAMPLES in test_mission_status_examples.py, are re-pinned because the Mission itself changes them); no retry-to-green anywhere (a flaky or red run is classified, never re-run to green; the bins are in the Baseline section).
Standing Order 5 (non-vacuous gates, priced debt)passpassEvery new check has a floor, an independent count and a planted negative; no new allowlist or ratchet entry (the existing shrink-only list of 8 Missions is untouched; the empty-allowlist invariant holds for everything added).
Standing Order 6 (canonical sources)passpassCanonical plan template; generated files are never hand-patched.
Standing Order 7 (git and workflow)passpassOne PR, draft, operator merges; no version number in scope (AD-16).
Standing Order 8 (mission hygiene) and Tracker Ticket Assignment Rulepasspass, with named ownersReviewer and implementer are distinct roles (every work package is reviewed by a separate reviewer, never its implementer). Tiered rigour as in its row above. Ownership-map leeway: write scopes overlap only where declared, and every overlap is a dependency chain ("Declared shared-file exceptions"); no-overlap between concurrent work packages is the real guard. Issue-matrix row, claim and tracker comment are an orchestrator duty, not a work package and not an agent-in-phase duty (agents post no comments in this phase; no work package agent ever posts one): at D-0 the orchestrator assigns issue #5625 to the Human-in-Charge (the Tracker Ticket Assignment Rule: before or as part of starting) and posts the claim comment naming the Mission slug and mid8; the orchestrator sets the rows of issue-matrix.json through spec-kitty agent issue-verdict before the first approval (the approval gate refuses unknown rows) and at W-1 only moves #5625 to a terminal verdict (add-only, closed-set verdicts; one row per issue the spec references, six today, re-counted at W-1; the scaffold made at tasks finalisation is checked first and never overwritten; verdict and evidence reference per row, as the accept and review gates and the spec-kitty-mission-review skill's Gate 4 read it); after the draft PR is opened the orchestrator or the operator posts the closing tracker comment with the PR link (step after W-7). The operator can substitute for the orchestrator on any of the three.
Standing Order 9 (red-main and release discipline)n/an/a, watchedNo red was found on the base (research R-11, R-13), so no P0 is filed and no reproduction test is owed. The Mission never reverts or green-washes an honest red: a red found at IC-01 or later is binned first (Baseline section) and a pre-existing one is reported before work continues; the draft PR is held, never merged red, until CI is green or the red is attributed and the operator has said so.
Pre-existing Failure Reporting RulepasspassBaselines taken, no red found (research R-11, R-13), so no issue is owed. IC-01 re-takes them, the architectural ones included.
NO_FULL_HEAVY_SUITES_IN_MISSIONpasspassOnly named gate files are run (the gate set lists them, the architectural ones that read tests/ included); tests/architectural is never swept locally; the heavy battery and architectural-fast are CI-owned except for WP05, WP06 and WP07, which touch registries or a workflow: there the operator's brief requires both battery parts to pass exactly as CI runs them, which is the explicit operator request the charter names as the one exception, so those three work packages run architectural-fast and both architectural-heavy shards before approval and the reviewer re-runs or verifies them (tasks.md, Gates per work package).

Drift flagged (charter wins). CLAUDE.md says branch protection and review requirements enforce the repository workflow; the charter says nothing on GitHub enforces it and a failing run blocks nothing by itself. The plan uses the tiers of the v1 plan: tier 1 means the job is a needs of a terminal gate (router-gate), tier 2 is fleet-reported, tier 3 is local discipline; none is a GitHub required check.

Seam: where the change lands (a)

None of kernel, doctrine, CLI or sync. The change lands at:

SurfaceNew or editedWhat goes there
contracts/mission-status/new files; openapi.yaml, four _index.yaml files and CHANGELOG.md editedThe slice (contracts/operations-and-schemas.md).
contracts/tools/four files edited, nothing elsefixture_builder.py (strict names, 25 kinds, the plant shape), leak_scan.py (artifact-path class, predicate, and under OD-4 X the masked text pass), negative_cases.json, enum_pins.json (FR-015).
tests/contract/two helper modules and (X-family) three test modules new (X adds the live module: four; Y none); the payload helper and seven existing test modules edited (test_mission_status_reality.py, test_mission_status_payloads.py, test_mission_status_examples.py, test_leak_scan.py, test_fixture_builder.py, test_enum_pin_check.py, test_run_negative_cases.py); one fixture newReaders, proof, live baseline test, reality extension (contracts/tool-extension-and-reader.md).
.github/workflows/packs.yml, tests/architectural/test_ci_corpus_trigger_completeness.pyedited only if OD-1 is X or X-pureOne --deselect and one registry row per new test module (three; X: four), written by work packages that share these two files and therefore run in one computed lane, one after the other ("Lanes"); either file selects architectural-heavy on the PR (OD-1).

Boundary rules:

1. No src/ change (NFR-007). The reader composes public, read-only seams from the test side: wp_task_files, read_authored_wp_frontmatter, reconstruct_wp_view, read_lanes_json, lane_branch_name, resolve_workspace_for_wp, build_review_cycle_pointer, ReviewCycleArtifact.from_file, is_glob_pattern, git_merge_base, kernel.git.changed_paths (which returns GitPath objects, converted with as_posix() at the seam, D-P4), get_current_branch. It never imports a private name; the two private matcher copies are imported by the parity test only. It never calls the writing materialize (a severity-5 defect: the reality check proves it left kitty-specs/ unchanged). 2. contracts/tools/ never imports pytest or anything under tests/. tests/contract/ imports tools by file path through the existing loader (the allowed direction). 3. Primary planning surface. Artifacts, review cycles and prompt files are read from MissionSource.own_dir (kitty-specs/<mission> under the repository root checkout), never from read_dir (ledger SK-310); status lanes and verdicts are read from read_dir as v1 does (D-P9). 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.

Generated artefacts: what is generated, by which command (b)

Hand-patching a generated file is forbidden. This Mission touches or creates:

ArtefactGenerated byCommitted?Handling
The bundle openapi.yaml and its checksumcontracts/tools/bundle.py (Python resolver; two builds compared)No (the layout check fails on a tracked bundle)Never edited; used under <scratch> for the lint and comparison runs.
kitty-specs/<mission>/status.events.jsonl, meta.json, tasks/, tasks.mdThe spec-kitty CLIYesWritten only by CLI commands; the first spec-kitty plan call appended a plan-started lifecycle event (tracer F-9). spec-kitty plan is not re-run after the plan is filled: its second call auto-commits.
contracts/tools/negative_cases.jsonAuthored (one case per new planted kind)YesPlants are built at run time; no leaking text is committed.
contracts/tools/enum_pins.jsonAuthored; the pin is the contract's enum value setYesEdited in the work package that adds the enum.
Derived inventories (tests/release/pinning_rule_inventory.json, docs/development/docs-retrieval-index.yaml), agent command copies, glossary, DRG, pack manifesttheir own scriptsn/aNot touched: no workflow script (the only workflow-file edit is packs.yml under OD-1 X or X-pure), template, skill, doctrine artefact, glossary entry or documentation heading changes (no edit of scripts/ci/fleet_verdict.py, tests/ci/test_fleet_verdict.py, ci-gate-mechanics.md). spec-kitty regen --check is unaffected.
tests/contract/fixtures/mission_status_artifacts_expected.jsonAuthored (named anomaly files; no generated values)YesBeside mission_status_expected.json.

Contracts: what moves and what is preserved (c)

ContractVerdictReason
Existing schemas WorkPackage, WorkPackageSummary, MissionHead, ReviewResult, shared Problem, every existing path, parameter and response filePreserved, byte-identicalCL-1, C-001; proven by the byte-identity diff and the bundle comparison (research R-7).
contracts/_shared/Preserved, untouchedNothing is admitted to it (the new pieces describe one module's resources).
New: getWorkPackageDetail, listArtifacts, getArtifactContent and their schemasNew, additive, info.version stays 1.0.0-SNAPSHOTSame unreleased release as the previous API changes; no new version (operator ruling 2026-10-05). Provisional elements per AD-14.
contracts/tools/ scriptsExtended (four files)FR-015, acknowledged. Every existing contract file must still pass the extended scan (measured: it does, with the structured edit and, under OD-4 X, the masked text pass; research R-8, R-14).
Doctrine, mission step contracts, orchestrator-api, events packages, status event schemaPreservedNothing under src/ or packs/ changes.
CI workflows, router groups, module registryPreserved under OD-1 Y. Under the X-family: packs.yml gains three --deselect entries (X: four) in its one corpus-suite command line and the corpus-trigger registry three rows (X: four); both edits select architectural-heavy on the PRNo new job (D-P5); costs in OD-1.

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).

Reflexivity: Missions in flight when this lands.

1. Runtime. Unchanged; the contract describes a service that does not exist yet. 2. CI coupling. The reality check already reads every Mission on every corpus-selected PR. After this lands it also builds the three new reads for every Mission and every work package, so a data defect in an unrelated Mission can redden a stranger's PR. Mitigation as in v1: a failure naming a Mission the PR does not touch is classified first (contract gap, reader defect or data defect); the output names the Mission, the work package or file, and the rule. Known data facts are handled by design, not by exclusion: duplicate work package ids (D-P6), 168 unparseable cycle files (null members by AD-13), the 7 refused files (named in the fixture). 3. This Mission is part of its own corpus. Its own directory (spec, plan, research, contracts notes, tasks, reviews, status log) is read by the new reads once merged. The plan therefore keeps this directory free of anything the reads refuse: no credential, no NUL, valid UTF-8, no file over 256 KiB, no symlink, no yaml-fenced block in contracts/*.md (the round-trip gate collects those), and no text the human leak patterns flag. That last claim is checked, not assumed: leak_patterns.leak_codes(line, HUMAN) over every line of every file of this directory (reviews/, written by the review phase outside this plan's edit scope, and the status log excluded) returns zero hits at this revision (13 files, the scan of the plan fix). The one example that used to trip the e-mail pattern, a retina-style image file name holding an at sign, is described in words in the Mission files, never typed as a literal; a scan fixture that needs such a name builds it at run time from fragments. 4. Readers that write. The corpus fingerprint (bytes plus, new, directory names) is equal before and after the run; the reader's file-system seam records no write mode (D-P12). 5. Reader drift is not selected before merge (accepted, as v1): a PR that edits a status reader selects module shards, not the corpus job; the push-to-main run catches it. 6. History. The branch head is a merge of main. The wrap-up rebases onto upstream main (W-6; no tag; the version line is not edited by this Mission, so the conflict of research R-7 is at most the CHANGELOG section); W-7 then re-verifies every cited symbol (research R-12 is a script-checkable list), re-measures the floors and repeats the comparison against the main tree. Evidence recorded before W-6 is pre-rebase evidence and is never cited as final. 7. kitty-specs/ is archive-frozen. This Mission modifies no file already on main; its own files are add-only on the branch.

The gate set (e)

Verified on this checkout from .github/workflows/ and .github/ci-module-registry.yml; tiers as in the Branch contract note above.

Existing gates, re-verified

GateWhereApplies?Reason or how
ruff check . and ruff format --check . (hard-enforced)ci-router.yml ruff jobYesNew Python in contracts/tools/ and tests/. Rules in force: E F W C90 C4 ARG B SIM UP ASYNC S TID251 and PT010..PT031; line length 164; complexity ceiling 15; tests/** relaxes ARG, S, E402; contracts/tools/ does not. hashlib.sha256 is banned (TID251): no new code needs it (the fingerprint uses blake2b). Baseline: clean, 3,103 files formatted. Per-file: ruff format --check --force-exclude.
mypynoneNoDoes not run in any workflow; new code is annotated to pass mypy --strict as local discipline only.
TID251 import boundariesci-router.yml job import-linter (named "import boundaries (TID251)"; a separate job from ruff, router-gate input)YesRuns ruff check --select TID251 .; the same rule as inside the ruff job. Baseline: all checks passed (re-run at the plan fix).
Terminology guardci-router.yml terminology (tests/architectural/test_no_legacy_terminology.py)YesCheap; new prose lands. Mission versus feature is review-enforced, which this Mission backs with a new scan.
Router derivation and corpus-trigger gatestests/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 (plus test_no_legacy_terminology.py, the terminology row above)Yes, named filesThe first two go red for a new corpus-marked module without the registrations (OD-1, research R-6). Baseline 368 passed, 1 skipped (re-run at the plan fix: 80 s).
architectural-fastci-router.yml architectural-fast (10-minute timeout, -n 4, battery part fast; router-gate input, every pull request)YesIt censuses all of tests/: manual global-state mutation (os.chdir, os.environ, sys.path; _global_state_scan.py), the home pin, dead symbols. Real-git synthetic repositories, worktrees and git identity setup in the new test modules are exactly what those scans inspect, so the first signal must not be CI. Named files run at IC-01 (baseline), after every new or edited test module, and at W-1: test_no_manual_global_state_mutation.py, test_global_state_allowlist_sealed.py, test_spec_kitty_home_pin_census.py, test_spec_kitty_home_pin_guard.py, test_spec_kitty_home_pin_budget.py, test_home_pin_gate_verdict.py, test_no_dead_symbols.py, test_dead_symbol_allowlist_contract.py (all under tests/architectural/). Baseline: 171 passed in about 102 s locally (research R-13); in CI the job was green on the last three main runs (3.4 to 4.2 minutes). No allowlist or baseline entry may be added to make a new module pass: a new module is written to satisfy the scans (tests use monkeypatch and a scratch HOME, never a manual mutation).
layer-rulesci-router.yml layer-rules (always on; router-gate input)YesRuns tests/architectural/test_layer_rules.py and test_pyproject_shape.py. The slice adds no src/ import, but the new test helpers import specify_cli and kernel symbols. Baseline: 83 passed in about 17 s locally. Run at IC-01 and at W-1.
Architectural heavy batteryci-router.yml architectural-heavy (2 shards, 30-minute timeout, full history, --all-extras)CI-owned; run by the WP05, WP06 and WP07 implementers and re-run or verified by their reviewers on the operator's explicit request (tasks phase)Charter: no full sweep in mission work, except on that explicit operator request. Selected in CI when either router group architectural (tests/architectural/) or ci_config (.github/workflows/) sees a change: under the OD-1 X-family both do (the registry file and packs.yml), under Y neither, so Y means no heavy run on this PR. Baseline from main runs: research R-13.
Tool tests and corpus jobtests (contract tools), tests (corpus-blocking)YesThe new tests' homes (D-P5). Baselines, re-run at the plan fix: tool selection 1,016 passed and 37 skipped locally; reality module 577 passed; payload helper tests 157. The tool job runs without the project installed; the matcher parity import was proven in that environment (research R-13) and is IC-07's first step with a stated fallback (D-P3).
Contracts workflow gates (lint, structure, citation, enum-pin, example, layout, provisional, breaking, verify-pins, bundle, parity, the release dry run, negative-tests)contracts.yml, contracts/ and tests/contract/ trigger itYesThe slice changes their input. The workflow triggers only on pull_request and on push to main (workflow_dispatch appears in job if: expressions but is not a trigger), so no job of it runs before the draft PR: the breaking-change job finds no tag and accepts 1.0.0-SNAPSHOT as the first release, as on main today, so it proves nothing about additivity here (the local --baseline-root run does; no red is expected). The JVM-only jobs (validate-bundle: Gradle validation, Java view, TypeScript client smoke; resolver-parity; the lint; the release dry run; the 33 of 101 negative-tests cases that need a JVM, vacuum or oasdiff, skipped here) therefore first run on the draft PR unless the W-2 shake-out runs them earlier (risk P-10). The lint ran on a spike (0 violations).
docs-lint, tests-docsci-router.ymlNoNo docs/ file changes (the module CHANGELOG is under contracts/); no root changelog entry (departure 13).
uv lock --check, wheel build, clean-install-verification, regen --check, layer rules, archive freeze, packaging safetyCINoNo dependency, pyproject.toml, src/, packs/ or archived-root change.
Contextive glossary, Bandit and pip-audit as jobs, SonarCloud on pull requestsnone or informationalNoNo glossary change; no dependency change; ruff S is the only security lint in force; Sonar is continue-on-error. New branches get tests in the same change, as the Sonar expectations ask.
diff-cover at least 90 percent of changed critical-path linesci-aggregate.ymlVacuousThe scope is the hard-coded CRITICAL_PATHS tuple 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 of the reader has a synthetic case, and the mutation tests prove the tests can fail.
Release readiness cutover-guardrelease-readiness.ymlYesThis Mission's own directory is in its scope; baseline read: 0 un-cut-over. Re-read at wrap-up.
commitlint, markdownlintCINo (cannot fail)The commit-msg job only prints; markdownlint ends in `

New gates this Mission adds, and where each runs

New gateRuns inTierWhen it cannot do its job (FR-020)
Reality extension: detail for every work package, listing for every Mission, content for every listed file, examined equals discovered, floors, controls, leak scan, fingerprinttests (corpus-blocking)1Fails on a shortfall, a per-Mission exception, zero examined, a floor miss.
Artifact and detail reader tests with mutationstests (contract tools)1A fault that did not fire, a surviving mutation, zero cases built.
1.1 proof module: unchanged version, additive diff, bundle comparison, scope function (name-status entries; allowed set as data carrying the admitted statuses), terminology scan, CHANGELOG entry and provisional names, credential-kind descriptions, refusal examplestests (contract tools)1A surviving mutation, zero cases built. Green on every clone.
Live baseline test (VOID: OD-1 = X-pure, and with no tag there is no tag-dependent test; OD-1 X only: own module test_mission_status_baseline_live.py, markers contract and corpus only; Y: none, the W-7 command run)tests (contract tools)1Fails, never skips, while the tag is missing; the only accepted red until the tag, and only locally; also red in any clone without the tag (ACK-1 a, b). Its message names the tag, the fetch recipe and the --baseline-root recipe. Not selected by the nightly fast or unit run (ACK-1 b); retired by the 1.1.0 release Mission (ACK-1 c).
Leak scan artifact-path class and 25 planted kinds (22 red-first, 3 regression controls)tests (contract tools) and the negative-tests job1 and 2A blind scanner fails its self test; a control flagged fails.
Enum pins for four new enumspython-checks job, tests (contract tools)2 and 1A changed value set fails; a pinned title that is absent exits 2.

Where this plan departs from, or adds to, the spec's gate analysis

  • Added gate: the registrations a new corpus-marked module needs, and the heavy battery they select (OD-1). The spec's slice file set names job registration only for a new job.
  • Added gates the spec's gate analysis does not list: architectural-fast, layer-rules and the separate import-linter job, each a router-gate input on every pull request (rows above).
  • Added observation: provisional_check and structure_check are file-wide; the proof module closes both for the 1.0.0-SNAPSHOT section (D-P11).
  • Router placement was re-read on the merged tree. v1's job names changed: the corpus job is tests-corpus-blocking and the tool tests have their own job tests-contract-tools; the spec's "corpus job" is the first, and a new test module defaults to the second.

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.

Taken during planning (tree identical to origin/main for every file under test), results in research R-11 and R-5: ruff check and format clean; the nine named gate files 368 passed, 1 skipped; the tool-job selection 1,016 passed, 37 skipped; the reality module 577 passed; the eight architectural scan files of architectural-fast 171 passed; layer-rules 83 passed; ruff check --select TID251 . clean; cutover-guard 0 un-cut-over; the contract negative-case registry cases=101 ran=68 skipped=33 passed=68 (the 33 need a JVM, vacuum or oasdiff). No red found, so no tracker issue is owed.

To be taken at implement start (IC-01, before its first change), recorded in the PR body and reported to the orchestrator, which appends it to tracer-approach.md: re-sync, record the base commit, re-run the commands above including the architectural-fast files, the layer-rules files and the TID251 command of the gate set, record the latest main run of architectural-fast and architectural-heavy (job conclusions and durations, research R-13), re-count the campsite literal. Any red is binned (pre-existing known-P0, CI environment, stale install, stale venv or introduced); a pre-existing one is reported before work continues.

Campsite-clean (g)

Opening commit (distinct, behaviour-preserving, tidy-first): refactor(tests): name the git repository setup once in the reality check (#5625).

  • Surface and debt (counted, not from a Sonar report). In tests/contract/test_mission_status_reality.py the statement that initialises a scratch git repository (subprocess.run([... "init", "-q"], check=True)) occurs six times (five tests and the shared fixture; the six statements differ in form: a git -C <path> prefix list, the same prefix spelled inline with tmp_path, and one with a root variable; the hoisted helper takes the repository path and replaces one literal pair, "init" and "-q") and the commit-everything helper _commit_all has one definition and two call sites. This Mission adds real-git synthetic repositories and a corpus extension in the same module, so the duplicate is domain-matched debt in the file about to change (the Sonar S1192 class: a literal used three or more times in one module becomes a constant or helper).
  • Change. Hoist git_init(repo) and commit_all(repo) into tests/contract/_mission_status_payloads.py (the shared helper the new reader tests import) and replace the six init statements and the two _commit_all call sites (eight call sites, plus moving the definition). Nothing else is tidied.
  • Behaviour-preserving proof. The same test ids pass before and after with identical counts (577 passed for test_mission_status_reality.py, 157 for test_mission_status_payloads.py); every assertion is unchanged.
  • Write scope. The two test files above, nothing else; the baseline record is reported to the orchestrator, which appends it to tracer-approach.md ("Record-keeping and write scope").
  • Drop rule. After the implement-start rebase the occurrences are re-counted; below three, the opening commit is none, and the PR body and the orchestrator-appended record in tracer-approach.md say "no domain-matched debt found in the edit surface".
  • Not a grab-bag. The 1,273-line helper is not split here (the new builders live in new modules instead); no src/ debt is folded (NFR-007).

Project structure

Documentation (this mission)

kitty-specs/mission-status-contract-1-1-01M42XJC/
├── spec.md                              # specify output (done)
├── plan.md                              # this file
├── research.md                          # evidence ledger R-1 .. R-15
├── data-model.md                        # entities, derivation rules, decision order, invariants
├── 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     # FR-015 notes, reader modules, 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, +3 path keys
├── CHANGELOG.md                         # additions merged into the existing ## 1.0.0-SNAPSHOT section
├── paths/       missions_missionId_work-packages_wpId_detail.yaml  missions_missionId_artifacts.yaml
│                missions_missionId_artifacts_content.yaml
├── parameters/  ArtifactPath.yaml
├── schemas/     WorkPackageDetail Subtask DependencyRef ReviewCycle Workspace OwnedFile ChangeState
│                ArtifactReferences ArtifactReference ArtifactPath ArtifactKind ArtifactEntry ArtifactListing
│                ArtifactContent ArtifactRefusal ArtifactRefusalCode WorkPackageDetailRefusal
│                WorkPackageDetailRefusalCode        (.yaml, 18 files)
├── responses/   ArtifactPathRefused ArtifactNotFound ArtifactTooLarge ArtifactNotText ArtifactSecretRefused
│                ArtifactUnreadable ArtifactListingUnreadable WorkPackageDetailNotFound
│                WorkPackageDetailUnreadable          (.yaml, 9 files)
└── examples/    21 files (contracts/operations-and-schemas.md)
contracts/tools/                         # fixture_builder.py leak_scan.py negative_cases.json enum_pins.json only
tests/contract/
├── _mission_status_payloads.py          # edited (helpers, FLOORS, leak class, fingerprint)
├── _mission_status_artifacts.py         # new: the artifact reader
├── _mission_status_detail.py            # new: the detail reader
├── test_mission_status_artifacts.py     # new (OD-1 X or X-pure)
├── test_mission_status_detail.py        # new (OD-1 X or X-pure)
├── test_mission_status_contract_1_1.py  # new (OD-1 X or X-pure): the pure proofs, green on every clone
├── test_mission_status_baseline_live.py # VOID (operator ruling 2026-10-05; OD-1 = X-pure): not created
├── test_mission_status_reality.py       # edited (corpus extension)
├── test_mission_status_payloads.py  test_mission_status_examples.py  test_leak_scan.py
│   test_fixture_builder.py  test_enum_pin_check.py  test_run_negative_cases.py    # edited
└── fixtures/mission_status_artifacts_expected.json                                 # new
.github/workflows/packs.yml                                    # OD-1 X or X-pure: +3 deselect entries (X: +4; one physical command line, one writer)
tests/architectural/test_ci_corpus_trigger_completeness.py     # OD-1 X or X-pure: +3 registry rows (X: +4)

Structure Decision. Reader code beside the #5581 projector in two new helper modules (the 1,273-line payload helper is grown only by the helper edits listed under IC-06), three focused test modules under the OD-1 X-family (X adds the live module), and the minimum edits to shared tooling. Rejected: placing the readers under contracts/tools/ (the tools are Node-free scripts that never import src/), a single large test module (OD-1 Y), 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 of this module (schema about 54 lines, example about 41, path file about 37), the negative-case block the measured 23 lines a case of negative_cases.json, and code and tests the v1 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.

GroupEstimated added linesNature
Contract: 52 new files (18 schemas, 9 responses, 3 paths, 1 parameter, 21 examples) and the edits of openapi.yaml, four _index.yaml files and CHANGELOG.mdabout 2,200mechanical apart from the three path files and the CHANGELOG entry
Tooling: leak_scan.py, fixture_builder.py, enum_pins.json, and negative_cases.json (25 cases, about 575 lines)about 850the negative-case block is mechanical
Reference readers: two helper modules and the payload helper editsabout 1,550authored logic
Tests: three new modules (X-pure, about 2,250 lines; X adds the live module, four), edits of the existing test modules, the reality extension, one pinned fixtureabout 3,300authored logic
Totalabout 7,900, of which about 3,100 are mechanicalabout 4,800 lines of authored logic

Compaction shape (W-5): one commit per concern in dependency order, each commit carrying the tests of its concern: (1) campsite (or none); (2) tooling and its tests (OD-4's masked pass inside it); (3) contract A, the artifact reads; (4) contract B, the detail; (5) the 1.1 proof modules (X only: including the live module), the CHANGELOG entry and, under the OD-1 X-family, their registrations (one pair; X: two); (6) the artifact reader and the reader foundation, with its pair of registrations (X-family); (7) the detail reader and, under the X-family, its pair of registrations; (8) the corpus extension and its pinned fixture; (9) the close-out records (tracer files). The registrations for a module ride in the commit that creates the module, so the history shows why each exists (X-family: three pairs, one per pure module; X: a fourth for the live module). Under squash consolidation (W-1) this shape is rebuilt from one tree by path splitting, see W-5.

PR body, reading order and what to skip. Read in this order: the CHANGELOG entry and the three path files (the public surface); the tooling commit (the shared scanner changes); the readers; the reality extension. Skippable as mechanical: the 18 schemas, 9 responses and 21 examples (the layout_check, citation_check, example_check, enum_pin_check and lint runs in the PR body vouch for their shape) and the negative_cases.json block. If the measured total at W-5 exceeds 10,000 added lines, the orchestrator raises it with the operator before the PR (repository default stays one PR).

Architecture

Design decisions that bind the work packages

D-P1 OQ-1: the deferred-gaps record is a fifth heading. ### Deferred to the next major version inside the existing 1.0.0-SNAPSHOT section (no new version heading; the plan verifies the structure check accepts it, research R-1); structure_check accepts it unchanged (research R-1). The two behaviours of the reads sit under ### Added; ### Changed and ### Removed say "Nothing.".

D-P2 OQ-4: caps unchanged. 1000 entries and 262,144 bytes. Largest Mission tree 126 files (7.9 times headroom), 2 of 15,561 files over the size cap (research R-2). No squad note for a cap change is raised.

D-P3 The owned-files matcher is reproduced and pinned by a parity test. matches_owned_file(path, pattern) in _mission_status_detail.py normalises the path as _mt_matches_owned_file does and uses the pattern as authored (FR-007 rule 3). A parity test imports both private copies and requires equal results over every FR-022 glob case and four normalisation cases, with the two known path-normalisation divergences pinned. Signatures: the reader's matches_owned_file(path, pattern) takes one pattern, while _mt_matches_owned_file (src/specify_cli/cli/commands/agent/tasks_move_task.py) takes a tuple of patterns and _matches_any_glob (src/specify_cli/policy/commit_guard.py) a list: the parity test wraps the single pattern as (pattern,) for the first and [pattern] for the second, and calls each with the same path, so a result is compared per pattern. Reasons in research R-3 (AD-18 precedent; the copies disagree; direction of imports). A squad note: the production move-task pipeline pre-normalises patterns (./, backslash), which the reader does not, by the spec's text; 0 corpus patterns start with ./, 1 holds a backslash. Environment proof. The tool job installs with uv sync --frozen --no-install-project, and the parity test imports specify_cli.cli.commands.agent.tasks_move_task (typer, mission runtime, review, upgrade modules: the largest import chain this Mission adds to that job). Proven in a scratch checkout built that way (research R-13): the project is not installed in that environment, pytest.ini's pythonpath = src makes both private functions importable, and the 19-case table ran there with 17 agreeing and the two known path-normalisation divergences. IC-07's first task re-runs the import and the table in that environment form before any other IC-07 code; fallback if the import ever fails there: a source pin (a blake2b digest of the normalised syntax-tree dump of each of the two function bodies, failing when either changes) plus the compat-surface test that already pins _mt_matches_owned_file's presence, recorded in tracer-design-decisions.md.

D-P4 The rename seam is public. git_merge_base plus kernel.git.changed_paths(..., renames=False) (research R-4). merge_base_changed_files_checked is not called. Seam types and failures. changed_paths returns GitPath objects (frozen dataclass), not strings: the reader converts each with path.as_posix() before the matcher (which uses fnmatch and startswith). The parse of git's output raises ValueError for a path GitPath.parse refuses (an empty, . or .. component or an absolute path), and run_git wraps timeout and OS errors in GitCommandError; both GitCommandError and ValueError make every entry unknown, never unchanged and never an escaping exception. The other two seams do not go through run_git, so the same rule is stated for them explicitly (checked on this tree: git_merge_base on a vanished worktree directory raises FileNotFoundError, on a path that is a file raises NotADirectoryError; get_current_branch catches CalledProcessError and FileNotFoundError only, so the second case raises NotADirectoryError out of it): the reader wraps get_current_branch(worktree) and git_merge_base(...) so that OSError (which covers the two above and PermissionError), subprocess.SubprocessError and ValueError (an embedded NUL byte in an argument) each make every entry unknown, and it passes an explicit timeout= (a fixed constant, 30 s) to changed_paths, whose runner otherwise waits for ever (timeout=None). Accepted residual, with its reason: get_current_branch and git_merge_base have no timeout and the reader cannot add one without a src/ change (NFR-007) or replacing the seams the spec names with run_git; a wedged git process would hang the read inside those two calls. The corpus passes no worktree and never reaches them; the tests use local scratch repositories; a bound belongs to the read service Mission. Fault injection (IC-07, each with a positive fired counter and a mutation "remove the guard" that must be killed): get_current_branch raising PermissionError, git_merge_base raising NotADirectoryError, git_merge_base raising FileNotFoundError (the worktree directory removed between the exists check and the call), changed_paths raising GitCommandError with a timeout cause; each gives unknown for every entry and no exception out of the reader. Base ref input. target_branch comes from lanes.json as a bare string; it must satisfy the strict branch pattern of Workspace.laneBranch and must not start with -, else every entry is unknown and git is not invoked (git_merge_base builds its argv without an end-of-options marker).

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). New reader and proof tests live in new modules the job tests (contract tools) selects automatically (-n 4, uv sync --frozen --no-install-project; the payload helper's tests pass in that environment, research R-6). The Contracts workflow gains no job; .github/ci-module-registry.yml and docs/development/reference/ci-gate-mechanics.md need no edit. Budget: baseline 2 min 58 s and 3 min 03 s; modelled extension 23 s locally (listing 3.4 s, content 19.8 s), expected +40 to +90 s on the runner; re-plan above 6 minutes; the one timed worst-case case (1000 entries of 262,144 bytes) 6.2 s against 60 s. The advisory packs corpus suite (15 min 12 s of 20) must not gain tests: each new module (three under the OD-1 X-family; four under X) is deselected there.

D-P6 Duplicate work package ids, and the identity of a work package's files. The universe is the distinct valid work_package_id values of the files wp_task_files selects, after skipping, as bootstrap does, a file whose frontmatter raises FrontmatterError or a pydantic ValidationError or has no valid id. Two files are distinguished (spec Terminology, "two selectors"): the work package's authored file, which supplies the frontmatter, titles source and owned files, and its prompt file, which is the spec's name-keyed definition (a regular, non-symlink tasks/WP[0-9]{2,}-*.md whose name starts with the wpId and a hyphen; the first in byte order) and which alone feeds artifactReferences.prompt and the tasks/<wp-slug>/ directory of the review cycles (a work package with no prompt file has prompt: null and reviewCycles: []). The authored file is the first regular, non-symlink entry of wp_task_files for the id, in byte order of the file name. wp_task_files itself selects by name only (a symlink or a directory named like a work package file is selected), so the reader skips a symlink or a directory like an unparseable file (never followed, AD-6; never a 500; counted by an independent scan, 0 in the corpus today). For the four duplicate ids the authored file and the prompt file are the same file. Synthetic cases (each with its stated outcome): a symlinked work package file (skipped; the id is 404 when no other file holds it), a file whose name prefix and frontmatter id disagree (keyed by the frontmatter id, no prompt file unless the name qualifies), WP-notes.md carrying a valid id (a work package, prompt: null), a directory named WP01.md (skipped, never an OSError escaping). The v1 controls (subtasks[].id equals WorkPackage.authored.subtasks, and the others) compare with the v1 payload built from the same authored file. Floors: examined equals the independent count of distinct ids (3,193), ids present only in the status log (29, 404, counted separately). The four duplicates are pinned by name. (OD-3.)

D-P7 One predicate, one scanner class, three-way agreement. malformed_artifact_path lives in leak_scan.py (inside the slice file set; leak_patterns.py is outside it, a squad note), is imported by the reader (request check and eligibility) and by payload_leaks, and is compared in tests with a literal restatement of the spec's predicate and with the ArtifactPath schema pattern over 30 cases. The scanner's artifact-path class applies to the keys path and artifactPath outside an x-source or x-derived ancestor and replaces the structured-pass host-path and e-mail scan for them (AD-19); the ten strict names are added to STRICT_FIELDS and the existing contract still passes (measured). The raw-text pass is a second scan of the same lines (scan_text runs the human patterns over every non-blank line of every file, independent of the walk) and flags an at-sign name as EMAIL; how it treats artifact-path values is operator decision OD-4 (decided 2026-10-04: scope the text pass). Under the decided OD-4 (the scoped text pass; labelled X in the option table) the text pass blanks the spans of those scalar values before scanning (SECRET is not blanked, so a credential in a path value is still reported: stricter than AD-19's accepted residual); under the option not chosen (Y) no file under contracts/ would hold an at-sign path and the at-sign name would be proved at reader level only. Under the decided option no committed contract example holds an at-sign path either (a YAML file cannot be built at run time): the scan's acceptance of such a value is proved by the run-time-built scan tests (WP02) and the reader tests, and the committed examples carry only a home-segment name, a space and an accented letter. Either way the structured edit is in _Scanner.walk and the planted negatives are 25 runtime-built kinds, 22 red-first and 3 regression controls (contracts/tool-extension-and-reader.md A); each has a clean control that includes the odd legitimate names the option allows.

D-P8 Reader architecture. Two helper modules; entry points return an outcome (status and body), never raise for a refusal; one injectable FileSystem seam (scandir, lstat, open_binary) with counting and fault-injecting wrappers; HostCapability(git, worktrees) explicit, never discovered (NO_WORKTREES for the corpus). The content read implements the order of decision of the data model in one function that the listing also runs over every returned entry, so readable == (content read answers 200) holds by construction: that assertion is kept only as a labelled wiring guard (it catches a listing that stops calling the function) and is not the oracle. The oracle is independent (VERIFY-004): the reality module classifies every eligible file from its raw bytes without the reader (size over 262,144 gives 413; a NUL byte, then a strict UTF-8 decode failure, gives 415; a match of the SECRET_PATTERNS of leak_patterns in the decoded text gives 422; else 200, in that order), derives the expected status and the expected readable for each file, and asserts both against the listing entry and against the content read; a wrong order of decision, a wrong credential rule or a wrong NUL rule applied consistently in both reader places fails it. Public readers only; no private import (D-P3 aside).

D-P9 Surfaces. Artifacts, prompt files and cycle files: the primary directory (own_dir). Status lanes, verdicts, subtask states: read_dir through reconstruct_wp_view and materialize_snapshot, as the projector does. A coordination-topology fixture proves both (ledger SK-310) and a cycle held only on a coordination surface is not shown (AD-18); a stranded primary record is (ledger SK-348).

D-P10 The additive proof (operator ruling 2026-10-05: no new version). A pure function additive_proof_problems(baseline_root, candidate_root) checks (a) every baseline file of the module and _shared is byte-identical in the candidate except the allowed set (openapi.yaml, the four _index.yaml files, CHANGELOG.md), (b) the candidate bundle tree minus the new path keys, with info.version set to the baseline's, equals the baseline tree (the tree the bundle is written from, compared through contract_resolver.resolve), (c) the candidate info.version equals the baseline's (1.0.0-SNAPSHOT); there is no version-bump assertion and no 1.1.0, 1.0.1 or 2.0.0 case. The baseline is the contract tree on main (aef7cc967 or the current origin/main). Because breaking_check.py fails any bundle change against a baseline of the same version (BUNDLE_CHANGED_VERSION_SAME), the breaking_check run uses a scratch copy of the baseline whose info.version is rewritten lower and asserts the counts: line (baselines=1, breaking=0). Tests: planted mutations (a byte change to an existing schema fails (a); a 409 added to an existing operation fails (b); a changed info.version fails (c)) on scratch copies; a pure scope function scope_problems(changes, allowed) whose input is name-status entries (status, path) from git diff --name-status --no-renames (a rename is a delete plus an add) and whose allowed set is data that carries the statuses each entry admits: SLICE_ALLOWED is a list of rules (match, path, statuses); the prefix contracts/mission-status/ admits A only (the spec's item 1, new files); the exact existing files of item 2 (openapi.yaml, the four _index.yaml files, CHANGELOG.md) and of item 3 (leak_scan.py, fixture_builder.py, negative_cases.json, enum_pins.json) admit M only; the prefix tests/contract/ admits A and M; no rule admits D; an entry is allowed only when a rule matches its path and lists its status, so an edit of an existing schema file under the new-files prefix is reported, which is the spec's "for existing files under contracts/ only the edits of items (2) and (3)"; the Mission's own directory is checked separately for add-only; under OD-1 X or X-pure the data names exactly two more rules, .github/workflows/packs.yml and tests/architectural/test_ci_corpus_trigger_completeness.py, each M only, and no wildcard; the planted negatives are a path in neither set (for example a path under src/ and a path under docs/), an M entry under the prefix contracts/mission-status/ (an existing schema file edited), an M entry for a contracts/tools/ file that is not one of the four, and a D entry for a named file, and a further case proves that under Y the two extra paths are rejected (byte identity (a) remains the second, independent proof of the same bullet, since it needs a baseline); a live test (VOID under the operator ruling 2026-10-05 and under OD-1 = X-pure: no tag exists, so nothing below applies; kept as the record) in its own corpus-only module (OD-1 X only; markers contract and corpus, never fast or unit; under X-pure and Y there is no committed live test and the W-7 command run is the evidence), that takes the baseline from git archive of contract-mission-status-v1.0.0 and fails while the tag is missing, with the tag name, the fetch recipe and the --baseline-root recipe in its message (ACK-1: the only accepted red until the tag, and red in any clone without the tag; retired by its named owner at the 1.1.0 release or by the next contract-changing Mission, ACK-1 c). The scope function is also run over the real branch diff (not only on planted lists): the allowed data is a module constant (SLICE_ALLOWED, rules of directory prefixes and exact paths with their admitted statuses; EXTRA_ALLOWED_OD1_X, the two exact paths, M only; EXTRA_ALLOWED_OD2_X, the one exact path, M only) and the wrap-up steps W-1 and W-7 feed git diff --name-status --no-renames <merge-base>..HEAD through it with the operator-selected extras, together with the add-only check of the Mission directory against the base (quickstart gives the commands); the empty result is recorded in the PR body as the SC-008 evidence. A committed test over the live diff is rejected: it depends on the branch state, and after merge it would measure main against itself. The committed test cannot run oasdiff (not installed in the tool job); breaking_check.py runs in the Contracts workflow and as PR-time evidence (quickstart). Lifetime: the assertion "the tree's version equals the baseline's" belongs to this slice and is replaced by the release process or the next contract-changing Mission; its docstring says so. The live module's lifetime and retirement are in ACK-1 (c).

D-P11 CHANGELOG and provisional tests are section-scoped. One test reads the existing ## 1.0.0-SNAPSHOT section (no new heading), requires its headings, the four gap names in the Deferred part, the two read behaviours in Added, the #5533 reference and no route-by-route table, and every AD-14 element name (plus the names provisional_check reports) inside the section's own ### Provisional heading (closes research R-1 weaknesses 1 and 2).

D-P12 Read-only proof. tree_fingerprint gains the directory names under kitty-specs/ (FR-014: "no directory listing differs"); one planted control (a writing reader that only creates an empty directory) shows the old fingerprint blind and the new one not; the reader's seam counts opens and a test asserts no write mode was seen. The new readers run inside the module whose autouse fixture compares the fingerprint, so they are covered.

D-P13 Floors. The rule and every number are in research R-5 (95 percent rounded down to two significant figures; counts under 100 at 90 percent; anomaly classes by name with a subset check). Existing floors (500, 3,000, 2,800) stay. The first guard is always examined equals discovered on an independent scan.

Decision register: open questions resolved

IDQuestionDecisionReason
OQ-1Fifth CHANGELOG heading?Yes: ### Deferred to the next major version, accepted by structure_check unchanged.D-P1, research R-1 (run on a scratch copy, exit 0; control exit 1).
OQ-4Entry cap and size capKeep 1000 and 262,144.D-P2, research R-2.
OQ-2, OQ-5, OQ-6Resolved by the spec (CL-5)Built as written.AD-18; ArtifactRefusal and WorkPackageDetailRefusal with titled code enums.
PQ-1Call or reproduce the matcherReproduce, parity test.D-P3.
PQ-2Rename seamPublic seam, call it.D-P4.

Departs from the spec

Each item sharpens or departs from the spec text; none is invented beyond it.

1. Work package universe has duplicates (D-P6, OD-3): 3,193 distinct ids in 3,197 files; the spec's independent count is therefore 3,193, not the file count. 2. Rename seam and the field catalogue. The spec's catalogue and FR-007 rule 1 name merge_base_changed_files_checked. The reader uses git_merge_base and changed_paths(..., renames=False) (same determination: no merge base or a git failure is unknown); the x-derived inputs of changeState name those two. 3. fixture_builder.py is edited beyond STRICT_FIELDS: KINDS, the plant shape (_plant returns a document fragment and a per-kind control fragment; build merges them) and the plants (25 kinds: 22 red-first, 3 regression controls). Same file, inside the slice file set; contracts/tool-extension-and-reader.md A names BuiltFixture, build, and the tests that move. 4. The predicate's tooling home is leak_scan.py, not leak_patterns.py (outside the set). 5. Registrations outside the slice file set if OD-1 is X or X-pure: .github/workflows/packs.yml (three --deselect entries; X four) and tests/architectural/test_ci_corpus_trigger_completeness.py (three rows; X four). The spec's item (5) allows job registration only for a new job. SC-008 and the AC-VERSION scope bullet are read with these two paths added if OD-1 is X or X-pure (the scope function takes the allowed set as data and names exactly these two extra paths, D-P10); under Y they are read as the spec states them. Either edit also selects the architectural-heavy battery on the PR (OD-1). 6. Required examples are guarded in-test (OD-2 Y), not by an entry in required_examples.json; under OD-2 X that file is a third path outside the slice file set, read into the scope check as data and acknowledged by the operator (OD-2). 7. tree_fingerprint gains directory names to meet FR-014's "no directory listing differs". 8. CHANGELOG and provisional tests are scoped to the 1.0.0-SNAPSHOT section because the existing checks are file-wide (D-P11). 9. The FR-017 version assertion is slice-scoped, not permanent (D-P10). 10. (VOID: no tag, no live test, operator ruling 2026-10-05.) The live baseline test fails while the tag is missing, and is isolated (ACK-1). This departs from FR-017 item 1 (permanent assertions green before and after the release) and item 5 (the only accepted red is the breaking-change job): the live test (OD-1 X only; under X-pure and Y it does not exist and this item falls away) is red locally between IC-05 and the tag and in every clone without the tag, and the breaking-change job's red is never observable in CI because the Contracts workflow runs only on pull requests and pushes to main. It sits in its own corpus-only module (OD-1 X; under Y it is not committed) so the pure proofs are green before the tag, the nightly fast or unit run does not select it, and the only accepted red is exactly its ids. The PR is not opened before the tag. Recorded for the operator's acknowledgement, not only as an FR-020 reading. 11. The truncated: true example holds a short list with a comment (a real truncated listing holds exactly 1000 entries). A schema rule tying truncated to minItems: 1000 would force a 1000-entry example of about 100 KB; rejected (squad note). 12. NFR-001 baseline moved. The corpus job is 2 min 58 s and 3 min 03 s on CI today, not 3 min 43 s; the 8-minute limit is unchanged. 13. No docs/changelog/CHANGELOG.md entry. Nothing breaking ships and the root changelog is outside the slice file set (squad note: v1 added an entry). 14. Re-measured facts. 548 Missions, 80 Missions without lanes.json (spec: 77 of 544), 168 cycle files the product reader cannot parse, 29 status-log-only ids in 7 Missions; the orientation figures of the spec otherwise hold (research R-5). 15. Detail universe selector. The detail uses wp_task_files as the spec says; the existing projector builds v1 payloads from a glob plus is_file. They agree on the corpus (3,197); v1's builders are not changed. 16. Authored file of a work package. The universe follows wp_task_files as the spec says, but the reader reads the first regular, non-symlink entry for an id as the authored file and skips a symlink or directory like an unparseable file (AD-6, never followed); the prompt file stays the spec's name-keyed definition (D-P6). 17. Rename seam inputs and failures. Beyond the spec's "the base ref must resolve", target_branch must pass the strict branch pattern and not start with -; GitPath results are converted with as_posix(); ValueError from the path parse maps to unknown like GitCommandError, and so do OSError, subprocess.SubprocessError and ValueError from get_current_branch and git_merge_base; changed_paths gets an explicit timeout; the two seams without a timeout are an accepted residual (D-P4). 18. The raw-text pass of the leak scan is a second scan of artifact-path values that AD-19 does not mention; OD-4 decides whether it is scoped (X) or avoided (Y). Under either option SECRET in a path value stays detected, which is stricter than AD-19's accepted residual (credentials in file names are not detected).

Test strategy per acceptance criterion (i)

Rule for every row: the test fails when the change is reverted and its first commit is red on the planning base (C-011). Exceptions, named once: the three regression-control leak kinds (they pass on the base) (the live baseline test no longer exists, ACK-1 void). "Planted" means a value built at run time from fragments or a scratch copy; no leaking text is committed. Homes assume OD-1 X-pure (X: the same plus the live module row; Y: the matching existing module).

AC (spec)Red-first testHome
AC-DETAIL: examples validate; planted extra property, missing property, absolute feedbackReference failExamples test with three planted copies, through the resolver and the library pathtest_mission_status_examples.py
AC-DETAIL: corpus coverage, examined equals discovered, v1 controls, planted mismatch of eachReality extension: independent count of distinct ids, examined + refused equals discovered (no skip list; a named refused list derived by an independent scan, failing on a refusal outside it and on a stale entry; floors on the 200 count; spec FR-021), subtasks[].id, dependencies[].wpId, ownedFiles[].pattern compared with the v1 payload of the same file; planted mismatchestest_mission_status_reality.py
AC-DETAIL: work package file identity (D-P6)Synthetic Missions: an id held by two files (the first regular file in byte order), a symlinked work package file (skipped), a name/id mismatch, WP-notes.md carrying a valid id (a work package with prompt: null), a directory named WP01.md (skipped, no OSError); the independent scan counts symlinks and directories (0 today) and equals the reader's skip counttest_mission_status_detail.py, reality
AC-DETAIL: subtask titles (both row formats, [P], bold, empty, missing, duplicate, undecodable, null state, credential)Synthetic tasks.md fixtures; undecodable file gives 500 source_unreadabletest_mission_status_detail.py
AC-DETAIL: dependencies (title, lane, dangling, no events, whitespace title, credential)Fixture Mission with each casetest_mission_status_detail.py
AC-DETAIL: artifactReferences and artifactPath without a listing; past the cap; symlink, symlinked directory; counting opener1,001-file fixture; symlink fixtures; the counting FileSystem asserts no listing walk and no content opentest_mission_status_detail.py
AC-DETAIL: reviewCycles (order, verdict by pointer, last wins, renamed Mission, approval reference, unparseable, pointer refusal, review-cycle-0.md, two prompts, date-only and offset reviewedAt)Fixture cycle files and events; corpus floors (cycles, unparseable, verdict non-null 306 by an independent scan, date-only at least 1)test_mission_status_detail.py, reality
AC-DETAIL: feedbackReference pointer check and the absolute feedback_path pairvalidate_review_cycle_pointer on every value; planted review-cycle:///abs, two-segment pointer, event with an absolute feedback_path and its clean controltest_mission_status_detail.py, reality
AC-DETAIL: workspace matrix, corpus invariants, changeState cases and mutationsReal lane worktrees, lane-planning (the test fails if worktreePresent is true), no lanes.json, no lane, malformed lanes.json (500), every glob and state case of FR-022 through the reader and both private copies, all listed mutations applied by tests that must then fail; the end-to-end changed/unchanged cases run through the real kernel.git seam on real repositories (not through strings); a monkeypatched parse failure and a crafted refused path each give unknown (ValueError, GitCommandError); fault-injected PermissionError from get_current_branch, NotADirectoryError and FileNotFoundError from git_merge_base and a timed-out changed_paths each give unknown with the fired counter positive, and the "remove the guard" mutation is killed (D-P4); a manifest holding an option-like target_branch gives unknown with no git invocation observed through the counting seam; corpus: all unknown, all false, examined equals the independent counttest_mission_status_detail.py, reality
AC-DETAIL: leak scan by field class; determinism; absence and unreadable sourcesPlanted titles per pattern kind (credential withheld); two builds byte-identical; with and without a coordination worktree; 404 and 500 cases with codestest_mission_status_detail.py, reality
AC-LIST: examples; sort, no status record, no symlink, sizeBytes equals st_size; kinds; every kind occursReality over every Mission; classifier unit table; planted violationstest_mission_status_artifacts.py, reality
AC-LIST: readable invariant; last-byte NUL and invalid byte; bounds 1001; root-only exclusion; odd names; faults; modifiedAtthe independent byte-level oracle (D-P8) gives the expected status and readable for every eligible file and both the listing and the content read are asserted against it (the by-construction equality is a labelled wiring guard only), with floors; 262,144-byte files; 1,001 files; nested status.json; names with an at sign, a home/<x>/ segment, a backslash, a 513-character path; fault-injected scandir, stat and open (counter must be positive); sub-second mtime flooredtest_mission_status_artifacts.py, reality
AC-LIST: unknown Mission 404; determinism; no security scheme404 with not_found; two listings equal; no ordering by modifiedAt (planted); the root and the operations carry no securityartifacts, examples
AC-CONTENT: envelope; corpus 200 and refusals with codes; boundary; precedence; encoding; credentials; redaction pair; unknown Mission; bounded read; read-onlyWhole-corpus content reads with zero leaks and named refusals; 262,144 / 262,145; oversize non-UTF-8 gives 413; grow-during-read gives 413 (fault-injected opener); BOM kept; zero-byte; each credential kind 422 and clean twin 200; dirty file redacted: true and clean twin false; counting opener reads at most 262,145 bytes and opens one file; fingerprint equaltest_mission_status_artifacts.py, reality
AC-CONTENT: containment (every malformed case, doubly encoded traversal, symlinks, another Mission, directory, symlinked tasks/, missing and repeated path, ill-formed ids)Parametrised refusals with status and code, never leaking the targettest_mission_status_artifacts.py
AC-CONTENT: refusal bodies; each code with its pinned status; enum pinsLeak scan over the refusal examples; code-to-status validation fails on a mismatch; pin check on changed value setsexamples, test_enum_pin_check.py, planted kinds
AC-CONTENT/AC-LIST leak half, FR-015, FR-02322 red-first planted kinds (10 strict names, 12 artifact-path forms) and 3 regression controls registered through negative_cases.json; the scan accepts an at-sign path value built at run time (OD-4 decided: scoped text pass; no committed contract example holds one); a credential in a path value is still reported; reader-level planted pairs (payload_leaks over a built ArtifactContent with an unredacted body; a built detail with a credential title) fail if the reader stops redacting or withholdingtest_leak_scan.py, test_fixture_builder.py, test_run_negative_cases.py, negative-tests job, reality, artifacts and detail modules
AC-VERSION: version, breaking check, byte identity, bundle comparisonD-P10 pure function and mutations; PR-time --baseline-root evidence over the main tree; at W-7 the recorded comparison against the main tree (no live tag test exists, ACK-1 void)test_mission_status_contract_1_1.py (pure), test_mission_status_baseline_live.py (live, X), quickstart
AC-VERSION: CHANGELOG, citation, enum pin, example, layout, lint, provisionalD-P11 entry-scoped test; the ten Python checks over the finished tree; the lint on the bundleproof module, Contracts workflow
AC-VERSION: terminology, scope, determinism, runtime and quality, fail-loud, route coverageScan of the whole module skipping x-source and x-derived values (zero hits today; planted "feature" fails, feature_dir in a citation passes); scope function with the allowed set as data (X-family: two extra paths; the planted negative is a path in neither set), and the same function run over the real git diff --name-only of the branch against its merge base, plus the add-only check of the Mission directory, at W-1 and W-7 (D-P10, quickstart); two bundle builds equal; ruff over every changed file; zero-input cases exit non-zero; grep for the five route families and the two exclusions, no mapping tableproof module

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: the expected computed outcome"). Nine work package concerns (IC-01 to IC-09) and a wrap-up checklist (W-1 to W-7) that is not a work package; one pull request. The first commit of every work package is its red test or planted fixture (exceptions: Charter Check, ATDD row).

IC-01 Baseline and campsite (tidy-first)

  • Purpose: Take the implement-start baseline (the gate set's commands, the architectural ones included) and land the opening behaviour-preserving commit (or record "none").
  • Relevant requirements: charter Standing Order 2; NFR-006.
  • Affected surfaces: tests/contract/_mission_status_payloads.py (git_init, commit_all only) and tests/contract/test_mission_status_reality.py (the six init statements and the two _commit_all call sites).
  • Write scope: those two files; nothing under kitty-specs/ (the baseline record is reported to the orchestrator, which appends it to tracer-approach.md: "Record-keeping and write scope"). Sequencing: no dependency; the one real parallel window of the Mission is IC-01 beside IC-02.
  • Risks: the two files are edited again by IC-06, IC-07 and IC-08, which depend on this concern directly or through IC-06, so it finishes before any of them starts.

IC-02 Tool extension (FR-015)

  • Purpose: Give the new fields their leak class and the new enums their pins' tooling; add the planted negatives (22 red-first kinds, 3 regression controls); implement OD-4's decision.
  • Relevant requirements: FR-015, FR-023 (leak half), NFR-004.
  • Affected surfaces: contracts/tools/fixture_builder.py, contracts/tools/leak_scan.py, contracts/tools/negative_cases.json, tests/contract/test_leak_scan.py, test_fixture_builder.py, test_run_negative_cases.py.
  • Write scope: those six files. enum_pins.json is written by IC-03 and IC-04, not here.
  • Sequencing: no dependency; its six files are owned by no other work package, so it is a computed lane of its own; concurrent with IC-01.
  • Acceptance (named): leak_scan.py --root contracts exits 0 over the existing tree; the eight old kinds build byte-identically (pinned digests taken from the unmodified builder at the start of IC-02); the architectural-fast files of the gate set run green after the test edits.
  • Risks: negative_cases.json and fixture_builder.py have this one writer; the Unicode-escape hazard (quickstart, last section); the plant-shape change moves three test modules (contracts/tool-extension-and-reader.md A).

IC-03 Contract slice A: the artifact reads

  • Purpose: listArtifacts and getArtifactContent, their schemas, parameter, responses and examples; the version bump and the CHANGELOG entry skeleton.
  • Relevant requirements: FR-009 to FR-013, FR-018 (version), FR-019, FR-017.
  • Affected surfaces: new files under contracts/mission-status/ for the artifact reads; edits of openapi.yaml (version and two path keys), the four _index.yaml files, CHANGELOG.md (new entry with Added for these reads), contracts/tools/enum_pins.json (ArtifactKind, ArtifactRefusalCode), tests/contract/test_mission_status_examples.py, tests/contract/test_enum_pin_check.py.
  • Sequencing: after IC-02, which is another computed lane: the tooling merges IC-02's approved lane tip into IC-03's worktree before IC-03 starts, so the extended scan meets every new file as it is written.
  • Acceptance (named): leak_scan.py, example_check.py, run_negative_cases.py (JVM, vacuum and oasdiff tags excluded), layout_check.py, enum_pin_check.py run green over the IC-03 tree.
  • Risks: chokepoint: openapi.yaml, the _index.yaml files, CHANGELOG.md and enum_pins.json serialise IC-03, IC-04 and the final CHANGELOG edit of IC-05; the three share those files, so they are one computed lane and run in dependency order. The unicode-escape hazard in ArtifactPath.

IC-04 Contract slice B: the work-package detail

  • Purpose: getWorkPackageDetail and its schemas, refusal schemas, responses and examples.
  • Relevant requirements: FR-001 to FR-008, FR-013 (detail refusals), FR-019.
  • Affected surfaces: new files for the detail; appends to the shared files above; enum_pins.json (ChangeState, WorkPackageDetailRefusalCode); test_mission_status_examples.py, test_enum_pin_check.py (counts line becomes enums=7 values=43).
  • Sequencing: after IC-03.
  • Acceptance (named, the IC-02 x IC-03/IC-04 integration check, owned here): leak_scan.py, example_check.py and run_negative_cases.py run green over the tree after IC-03 and IC-04, so the ten new strict names and the artifact-path class are shown to accept every new contract file and example (an at-sign path example only under OD-4 X).
  • Risks: provisional naming (kind, code) is checked with provisional_check before the CHANGELOG is final.

IC-05 CHANGELOG additions and the 1.1 proof modules

  • Purpose: Finalise the CHANGELOG additions in the existing 1.0.0-SNAPSHOT section (Provisional, Deferred) and write the pure proof module with its planted mutations. No live baseline test.
  • Relevant requirements: FR-017, FR-018, FR-019, FR-020 (last bullet), FR-024, AC-VERSION, NFR-005, NFR-007.
  • Affected surfaces: contracts/mission-status/CHANGELOG.md; new tests/contract/test_mission_status_contract_1_1.py (X-family) and, X only, tests/contract/test_mission_status_baseline_live.py (Y: ids in test_mission_status_examples.py); with the OD-1 X-family one packs.yml deselect and one registry row for the proof module (X: two of each, the live module's too).
  • Sequencing: after IC-04 (the provisional list is final) and, under the OD-1 X-family (X or X-pure), after IC-07: IC-05 writes its registration pair (X: two pairs) in the same two files as IC-06 and IC-07, after theirs, so the writers form one dependency chain (IC-06, IC-07, IC-05). Only under Y does it depend on IC-04 alone, because Y has no registrations (there adding IC-07 would make the lane graph cyclic, see "Lanes").
  • Acceptance (ACK-1 void; the live-module steps below do not apply): the reviewer runs the pure module green; then (X) runs the live module alone and shows its stated failure message (it names the tag, the fetch recipe and the --baseline-root recipe); the ids of the live module are the only accepted red until the tag, stated in the work package prompt; checks --collect-only -m "fast or unit" over the live module collects zero ids; and runs the pure modules green and the live module red in a git clone --depth 1 scratch clone with no tag. Under X-pure and Y there is no live module: the pure module green is the whole acceptance.
  • Risks: none from a tag (void); the scope function's allowed set depends on OD-1.

IC-06 Reader foundation and the artifact reader

  • Purpose: Extend the payload helper (wanted titles, leak class, fingerprint with directories, fixture-writer arguments; the FLOORS fields were moved to IC-08 at the tasks phase, where they are consumed) and write the artifact reader and its tests.
  • Relevant requirements: FR-009 to FR-014, FR-015, FR-016, FR-020, FR-022 (listing, content, containment), NFR-002, NFR-003.
  • Affected surfaces: tests/contract/_mission_status_payloads.py, new _mission_status_artifacts.py, new test_mission_status_artifacts.py, tests/contract/test_mission_status_payloads.py, and a thin corpus smoke in tests/contract/test_mission_status_reality.py; with the OD-1 X-family (X or X-pure) one deselect and one registry row.
  • Sequencing: after IC-01, IC-02 and IC-03 (the predicate from IC-02 reaches its worktree through the dependency-lane merge, the schemas of the artifact reads from IC-03 by same-lane order).
  • Acceptance (named, PLAN-SEQ-006): a thin corpus smoke: listing and content over every real Mission, examined equals the independent discovered count, zero exclusions, no thresholds (the floors, controls and fixture are IC-08's).
  • Risks: the helper edit lands as the first sub-step so IC-07 starts from it.

IC-07 Detail reader

  • Purpose: The detail reader, the matcher with its parity test, the git change-state matrix and mutations.
  • Relevant requirements: FR-001 to FR-008, FR-016, FR-020, FR-022 (detail halves), AD-12, AD-18, AD-20.
  • Affected surfaces: new tests/contract/_mission_status_detail.py and test_mission_status_detail.py, and a thin corpus smoke in test_mission_status_reality.py; with the OD-1 X-family one deselect and one registry row; markers contract, corpus and git_repo, not fast (it drives real git; ACK-1 b).
  • Sequencing: after IC-06 and IC-04 (the detail schemas). Not concurrent with IC-06: they share files and one worktree.
  • Acceptance (named): the first task re-runs the matcher import and the 19-case parity table in a --no-install-project environment (D-P3 fallback if it fails); a thin corpus smoke over the real tree (examined + refused equals the independent count of distinct ids, no skip list, the refused work packages pinned by name both ways, no thresholds).
  • Risks: real git in tests (0.017 s a repository, measured); the host-capability rule; the duplicate-id and file-identity rules (OD-3, D-P6).

IC-08 Reality-check extension

  • Purpose: Turn the two smokes into the full check: floors, independent counts, the independent byte-level oracle, controls, leak scans, the extended fingerprint and the named anomaly fixture.
  • Relevant requirements: FR-021, FR-023, FR-016, NFR-001, NFR-003, AC-LIST, AC-CONTENT, AC-DETAIL corpus rows.
  • Affected surfaces: tests/contract/test_mission_status_reality.py, tests/contract/_mission_status_payloads.py (FLOORS values), tests/contract/fixtures/mission_status_artifacts_expected.json.
  • Sequencing: after IC-06 and IC-07. It shares no file with IC-05, so their order is the orchestrator's (IC-05 first, per the critical path; the tasks phase recommends IC-08 before IC-05, tasks.md Dispatch order step 5, because IC-08 carries the larger unknowns; both orders are legal); no dependency is added for it. Risks: runtime (budget in D-P5); the 8-Mission ratchet header must not be touched; the first case still carries a 19.8 s setup.

IC-09 Close-out evidence, pre-rebase

  • Purpose: Pre-rebase evidence for the PR body (commands and counts with the --baseline-root recipe), and the tracer assessment. Labelled pre-rebase; the final evidence is W-7.
  • Relevant requirements: SC-001 to SC-008, charter Standing Order 3.
  • Affected surfaces: this directory's tracer files only (append-only).
  • Write scope: tracer-approach.md, tracer-design-decisions.md, tracer-tooling-friction.md; the only work package with a file under kitty-specs/ in its scope. Execution mode: planning_artifact, set explicitly in its frontmatter (inference reads the body: a mention of tests/ or a .py file makes it code_change, which would also put it in a lane with a code lane's cost for planning files). Expected placement: lane-planning (the repository root checkout), which depends on the code lanes ("Lanes"). Sequencing: after IC-05 and IC-08. It needs no tag. It appends the close-out assessment and every record the other work packages reported ("Record-keeping and write scope").

Lanes: the expected computed outcome

Lanes are computed, not chosen. compute_lanes (src/specify_cli/lanes/compute.py) works in this order: work packages whose execution_mode is planning_artifact go to lane-planning (the repository root checkout); among the code work packages, rule 1 unions every pair whose owned_files overlap (_globs_overlap: equal globs, or the wildcard-stripped prefix of one glob is a string prefix of the other's, so a directory glob such as tests/contract/ overlaps every file under it); rule 2 (shared surface keywords in the work package body) merges only pairs whose ownership is not provably disjoint, and _are_disjoint uses the same predicate as rule 1, so with an ownership manifest on every work package rule 2 adds nothing beyond rule 1; dependencies only become edges between lanes (depends_on_lanes), and the resulting lane graph must be acyclic (LaneDependencyCycleError otherwise). At implement the tooling merges the approved tips of the dependency lanes into the dependent lane's worktree (_merge_dependency_lane_tips; a lane counts only when all its work packages are approved or done). So the plan states dependencies and write scopes, names no lane letter, and has no manual merge point**: the lane work reaches the Mission branch at one place only, the orchestrator's lane consolidation at W-1 (ii).

Expected outcome, from running compute_lanes on the planned write scopes (a scratch script and scratch data, not committed; every file listed explicitly, no directory globs; wp_bodies empty, which rule 2 cannot change). Dependencies as in the table below.

CaseComputed lanes
OD-1 X-family (X or X-pure; the plan is written for X-pure, X adds the live module and moves no work package between lanes), IC-09 planning_artifactone lane holding IC-02 alone; one lane holding IC-01, IC-03, IC-04, IC-05, IC-06, IC-07, IC-08 in dependency order, depending on the IC-02 lane; lane-planning holding IC-09, depending on the big lane
OD-1 X-family, IC-09 left code_changeIC-09 is a lane of its own depending on the big lane (and finalisation warns: a code_change work package owning no file under src/ or tests/); had IC-01 still owned tracer-approach.md, IC-09 would have been pulled into the big lane by that overlap. The explicit planning_artifact mode is what yields lane-planning
OD-1 Y (no registrations, proof tests in the examples module, reader tests in the payload test module), IC-05 depending on IC-04 and IC-07LaneDependencyCycleError: the group {IC-03, IC-04, IC-05} and the group {IC-01, IC-06, IC-07, IC-08} depend on each other (IC-06 on IC-03, IC-05 on IC-07)
OD-1 Y, IC-05 depending on IC-04 onlyfour computed lanes: {IC-02}, {IC-03, IC-04, IC-05}, {IC-01, IC-06, IC-07, IC-08}, lane-planning {IC-09}

Why the big lane under the X-family: IC-03, IC-04 and IC-05 share CHANGELOG.md, openapi.yaml and the four _index.yaml files; IC-05, IC-06 and IC-07 share the two registration files; IC-01, IC-06, IC-07 and IC-08 share the payload helper and the reality module; IC-03 and IC-04 share enum_pins.json and two test modules. IC-02's six files are owned by nobody else. Consequences stated plainly: there is no lane T and no lane R, no merge points M1 and M2 (the tooling merges the IC-02 lane tip when the next work package of the big lane starts after IC-02 is approved), and the one real parallel window is IC-01 beside IC-02; everything else is sequential, one work package at a time per lane worktree (the orchestrator's discipline: the lane worktree is shared). The dependency column below is the only ordering the plan imposes; a lane order the plan wants beyond a dependency (IC-05 before IC-08) is the orchestrator's dispatch order. /spec-kitty.tasks re-derives the real owned_files per work package and finalisation prints the real manifest; two things it 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, removing the one parallel window); (2) under Y the dependency table must not make the two groups depend on each other. A finalised manifest that differs from the table above is reported to the orchestrator and recorded; it is not a plan defect unless a gate below depends on it.

WPDepends onWrite-scope notes
IC-01nonepayload helper (two functions), reality module
IC-02nonesix tool and tool-test files
IC-03IC-02shared contract files, enum_pins.json, examples and enum-pin tests
IC-04IC-03same shared files
IC-06IC-01, IC-02, IC-03payload helper, reality module, artifact reader, its tests; X-family: registrations
IC-07IC-06, IC-04detail reader, its tests, reality module; X-family: registrations
IC-05IC-04; X-family also IC-07 (Y: IC-04 only)CHANGELOG, proof module (X-family: and its registrations; X also the live module; Y: examples module)
IC-08IC-06, IC-07reality module, payload helper (floors), pinned fixture
IC-09IC-05, IC-08the three tracer files (planning_artifact)

Declared shared-file exceptions (each is a dependency chain, so the overlap is never concurrent). (1) openapi.yaml, the four _index.yaml files, CHANGELOG.md, contracts/tools/enum_pins.json: IC-03, then IC-04, then IC-05 (CHANGELOG only). (2) With the OD-1 X-family (X or X-pure), the built-in-corpus-suite command in .github/workflows/packs.yml (one physical line) and _CORPUS_MARKED_MODULES: IC-06, then IC-07, then IC-05 (the IC-05 to IC-07 dependency exists for this chain); each appends its own entries in sorted position in the same commit as the module, so the line is never edited from two places at once. The registration pair is part of the module-creating red-first commit (tasks phase decision), so the module's own gates (same-tier uniqueness, corpus trigger) are green on every commit and each concern runs the named gate files before it is submitted. (3) tests/contract/_mission_status_payloads.py and test_mission_status_reality.py: IC-01, then IC-06, then IC-07, then IC-08 (the reality module); IC-01, IC-06, IC-08 (the helper). (4) test_mission_status_examples.py and test_enum_pin_check.py: IC-03, then IC-04 (Y: the examples module also IC-05, after IC-04).

Record-keeping and write scope (Standing Order 3). No code work package has a file under kitty-specs/ in its write scope, and none edits the planning notes under contracts/*.md of this directory. What a work package must record (a refinement of a contract note, the D-P3 fallback decision, the IC-01 baseline, a friction observation) goes into its hand-off report; the orchestrator appends it, add-only, to the right tracer file as its own step between dispatches (never while IC-09 is in progress), and IC-09 appends the close-out assessment. This keeps every lane's write scope disjoint from the tracer files and leaves one writer at a time.

Critical path. IC-02, then IC-03, IC-04, IC-06, IC-07, IC-05, IC-08, IC-09, all in dependency order after IC-02 (the big lane is sequential); IC-01 runs beside IC-02 and is the only work that overlaps in time.

No tag point (operator ruling 2026-10-05). Nothing waits for a release tag: IC-01 to IC-09 and W-1 to W-5 use the pure functions and the --baseline-root recipe against the contract tree on main, W-6 rebases onto upstream main, and W-7 repeats the comparison against the main tree.

Wrap-up checklist (orchestrator-owned steps, not work packages)

StepWhatNotes
W-1(i) Accept (after the acceptance-criterion verdicts are set); (ii) lane consolidation; (iii) registration and wiring check; scope check over the real diff; #5625 issue-matrix row to a terminal verdict (the other rows were set before the first approval), in this order(i) Accept on the repository-root checkout: spec-kitty accept --mission mission-status-contract-1-1-01M42XJC. It reads the Mission directory, the status log and the matrices, not the lane code. (ii) Lane consolidation, through the CLI, never by hand: spec-kitty consolidate --mission mission-status-contract-1-1-01M42XJC --dry-run for the conflict forecast, then the same command without --dry-run; it works in two hops, both local, and never publishes (--push is not used, git push origin main is never run; CLAUDE.md, Git Workflow). Hop 1 merges each lane branch into the integration branch kitty/mission-mission-status-contract-1-1-01M42XJC, the mission_branch of lanes.json (LanesManifest.mission_branch in src/specify_cli/lanes/models.py; it is not recorded in meta.json). Hop 2 integrates that branch into the target branch, issue-5625-mission-status-contract-1-1, the target_branch of meta.json and of lanes.json and the branch the repository-root checkout is on, with the --strategy step (--target is auto-detected; consolidate --help calls --strategy the branch-integration step, mission to target). HEAD after the run is the tip of the target branch (the repository-root checkout), not of kitty/mission-<slug>, and the dry-run forecast, the recorded HEAD and the tree W-1 (iii) to W-4 read are all against that target branch. Recommended strategy: rebase (merge is the acceptable second choice; not the default squash): squash makes hop 2 one commit for the whole Mission, flattening the per-work-package commits whose red-first, then green order W-4 (the aggregate squad) and W-5 (compaction) read; rebase keeps them, linear on the target branch. Under squash the lane branches (--keep-branch, honoured until W-5 ends) are the only copy of that history, and W-5 must rebuild the nine-commit shape from one tree by splitting it by path per concern (git reset --soft to the merge base, then one commit per concern), which is not the reordering of existing commits and cannot show red then green per work package; the red-first evidence is then cited from the retained lane branches. The strategy and the retention flags are the orchestrator's choice, recorded in the PR body, and the --dry-run forecast is read before the choice is final. The orchestrator records the resulting HEAD. Why accept comes first (read from the tooling, not assumed): the accept gate is the acceptance matrix's "single pre-consolidation reader" and records invariants about lane-only content as deferred_to_consolidation (src/specify_cli/acceptance/post_consolidation.py docstring), and consolidate itself asks only that every non-cancelled work package be approved or done (_assert_mission_terminal_ready in src/specify_cli/consolidation/executor.py), never an accepted Mission; the order is the tooling's, and if accept or consolidate reports a different precondition at run time, the tool's message wins and the deviation is recorded in the PR body. (iii) Named gate files (quickstart), including the architectural-fast files, layer-rules and TID251; every new module has its registrations (X-family) and is selected by the router marker expression (--collect-only), and the live module (X) is not selected by the nightly expression (--collect-only -m "fast or unit" collects zero of its ids); leak_scan.py, example_check.py, run_negative_cases.py over the merged tree. Scope check (SC-008, the AC-VERSION scope bullet): the scope function of D-P10 over git diff --name-status --no-renames <merge-base>..HEAD with the allowed data of the options the operator chose, and the add-only check of the Mission directory against the same base (quickstart); both must come back empty, and the output is kept for the PR body. Orchestrator steps (Charter Check, Standing Order 8). (1) Issue-matrix rows: set BEFORE the first approval, not at wrap-up. The approval gate (_issue_matrix_approval_blocker) refuses every work package approved transition while a row of issue-matrix.json is unknown, so the six scaffolded rows were set at the end of the design phase with spec-kitty agent issue-verdict (#5625 in-mission; #5326, #5533, #5558, #5581, #5582 not-applicable, each with a plain-language evidence reference). W-1 only re-reads the rows (re-count them; the file is never regenerated or hand-edited) and moves #5625 from in-mission to a terminal verdict (fixed) with spec-kitty agent issue-verdict --mission mission-status-contract-1-1-01M42XJC --issue "#5625" --verdict fixed --actor <orchestrator> --evidence-ref <text or link>, because in-mission passes approval but not the done/merge completeness gate. Closed verdict set; deferred-with-followup needs a #NNN handle. (2) Acceptance criteria and verdicts before accept. The acceptance matrix (acceptance-matrix.json) is the finalisation scaffold (placeholder criteria, overall verdict pending). Before W-1 (i), the orchestrator authors real criteria and a verdict for every criterion from the work package evidence with `spec-kitty agent mission acceptance-verdict --mission mission-status-contract-1-1-01M42XJC --criterion <id> --result <pass
W-2JVM shake-out (P-10), on the consolidated tree (W-1 ii), after the contract tree is final (no work package after IC-05 touches contracts/). The JVM replay of the contract tree is first run after WP04 is approved (IC-04: schemas, paths and examples are then final; only the IC-05 CHANGELOG entry follows), on the WP04 lane tip, before WP05 is dispatched (tasks.md, step J-1); a defect found there is fixed in a WP03 or WP04 follow-up before any reader is built on the schemas. W-2 is run again at wrap-up for the CHANGELOG-only delta.The Contracts workflow has no workflow_dispatch trigger and runs only on pull_request and push to main, so pushing a branch does not run it. Mechanism (a), default: replay the job steps of contracts.yml on a JVM-capable machine (the operator's, or a container with a JDK): pinned Gradle install, bundle, JVM validation, Java view, TypeScript client smoke, resolver parity, lint, release dry run, and negative-tests without the skip tags; breaking-change with --baseline-root over the main tree (scratch baseline with a lowered version). Mechanism (b), only on the operator's explicit word: a throwaway draft PR from a scratch branch against a scratch base in the contributor fork, closed unmerged (an extra PR; the operator decides). Defects found are fixed here, before W-3 and W-5, so none lands after compaction. If neither is available, P-10 stays open, the operator is told before the draft PR, and a second compaction is budgeted.
W-3Dev-assist cleanup
W-4Aggregate adversarial squadOn the pre-rebase tree; report-only; not re-run after the rebase.
W-5History compactionLeading rule (by path): the first commit is mission planning artifacts and status log, built by path from the final kitty-specs/mission-status-contract-1-1-01M42XJC tree (spec, plan, tasks, matrices, tracers, the status log and every planning and status commit the tooling wrote, tracer F-27 and F-29), worded without the word feature (F-27: the finalisation message cannot be reworded in place, so this commit replaces it); the add-only check against the merge base is re-run on it, and status.events.jsonl is compared byte for byte before and after compaction (same bytes, same order, no line added, dropped or re-timed). The commits named in F-27 and F-29 (finalisation, the nine status transitions) and every later status commit are absorbed there. Then one commit per code concern, in the order under "The one pull request"; measured diff size recorded. Under the recommended rebase (or merge) strategy of W-1 (ii) this regroups existing per-work-package commits; under squash it rebuilds the shape from one tree by path splitting, and the red-first evidence is cited from the lane branches kept by --keep-branch.
W-6Rebase onto upstream mainNo tag needed. No version conflict is expected (this Mission leaves info.version alone); a CHANGELOG conflict is possible and is resolved by keeping both sides (research R-7).
W-7Final evidence: the baseline comparison against the main tree (aef7cc967 or the current origin/main; the recorded command, the FR-017 evidence, with the scratch baseline's version lowered), breaking_check.py --root contracts without --baseline-root (no tag: it accepts 1.0.0-SNAPSHOT as the first release), floors re-measured on the final tree, the research R-12 symbol re-check, the named gate files, ruff, cutover-guard, the scope check over the real diff and the Mission-directory add-only check re-run on the rebased tree, and a git clone --depth 1 scratch run (pure modules green)The only evidence cited as final in the PR body. Then the draft PR; then the orchestrator or operator posts the closing tracker comment with the PR link (Charter Check, Standing Order 8).

Which tree each step reads. The lane work is not in the repository-root checkout's HEAD before W-1 (ii): until then HEAD holds only the Mission directory (the planning-lane work), so W-1 (i) reads the Mission directory and status log only. From W-1 (ii) on, every step reads the consolidated HEAD of the target branch (the branch of meta.json, after both consolidation hops) in the repository-root checkout: W-1 (iii) (gate files, scope check, add-only check), W-2 (the JVM replay needs the consolidated working tree), W-3 and W-4 (pre-rebase). W-5 rewrites that history; W-6 and W-7 read the rebased tree, and only W-7 is final evidence.

Reality-check design notes (FR-021 anchors for IC-08)

  • Reader chain per Mission: load_source as today; the new builders take own_dir and read_dir explicitly; the host capability is NO_WORKTREES; the injectable file-system seam is the real one, wrapped by a counting layer for the bounded-read and read-only assertions.
  • Independent byte-level oracle (D-P8): per eligible file the expected status (413, 415, 422, 200) and the expected readable are computed from the raw bytes by the module itself (size, NUL, strict UTF-8 decode, the credential patterns) and asserted against both the listing and the content read; the by-construction equality is a wiring guard only. IC-06 and IC-07 each ship a thin smoke first (examined equals discovered, no thresholds); this concern adds floors, controls, the oracle and the fixture.
  • Independent counts: Missions (a scan of meta.json), work package ids (plain YAML read of the selected files), eligible files (a separate walk), owned-file entries, tasks.md rows, cycle files and pointer-equal events; each compared with the examined count (examined == discovered), the numeric floors of research R-5 second.
  • Controls on one shared fixture Mission: a real payload validates; the same payload with a planted host path, e-mail address, credential, unknown lane, extra property or absolute pointer fails; the wiring guard (_wiring_problems) is extended so a removed check turns every case red.
  • The case budget: per Mission about 0.04 s modelled; slowest observed case in the existing run 2.02 s; the 60 s per-case limit holds with a wide margin.

Risks added or sharpened by planning

#RiskMitigation
P-1A new corpus-marked module is red in two gates the spec does not nameOD-1; the registrations are listed per concern and written by work packages that form one dependency chain in one computed lane ("Lanes"); the gate files are run after every module (quickstart).
P-2The corpus job growsMeasured and budgeted (D-P5); re-plan above 6 minutes.
P-3Duplicate work package ids (4) break "one payload per id"D-P6, OD-3; pinned by name.
P-4The extended scanner flags existing content or misses the newMeasured clean over the existing tree; 25 planted kinds with controls (22 red-first, 3 regression controls); the artifact-path class skips x-source and x-derived.
P-5(VOID, operator ruling 2026-10-05) The live baseline test is red until a tag existsNo tag and no live test exist; nothing is red by design.
P-6A data defect committed through meta.json or an event log selects no corpus run and surfaces later on the wrong authorAccepted as in v1; push-to-main and nightly runs are the net; triage rule as in Reflexivity item 2.
P-7A credential, a NUL or an oversized file enters this Mission's own directory and the new reads refuse itSection Reflexivity item 3; the planning artifacts are scanned before commit.
P-8Private matcher copies drift from the readerThe parity test; the compat-surface test already pins _mt_matches_owned_file's presence.
P-9The rebase at W-6 conflicts in the CHANGELOG 1.0.0-SNAPSHOT sectionKeep both sides at W-6 (research R-7); no release commit exists.
P-10The JVM-only contract checks (Gradle validation, Java view, TypeScript client smoke, resolver parity, lint, release dry run, the negative-tests cases tagged jvm, vacuum or oasdiff (counted at J-1 time, since WP02 adds cases) that are skipped here) first run on the draft PR, after compaction, because the Contracts workflow runs only on pull requests and pushes to main and the PR opens without waiting for a tag; three new operations and 27 new schema and response files are otherwise exercised only through the Python resolver. A failure there forces fix commits after W-5 or a second compaction.The first replay runs after WP04 (IC-04) is approved and before WP05 is dispatched (tasks.md step J-1), because the schemas, paths and examples are final then and the readers are built on them; the W-2 shake-out re-runs it at wrap-up for the CHANGELOG-only delta, before the aggregate squad: replay the job steps on a JVM-capable machine (or, on the operator's word, a throwaway draft PR). If neither is possible the risk stays open and the operator is told before the PR.
P-11Under the OD-1 X-family (X or X-pure) the two edits select the architectural-heavy battery (two shards, 30-minute timeout), and a pre-existing red there would land unattributedOD-1 prices it and records the main baseline (research R-13); IC-01 re-records it; a red is compared with the baseline before it is attributed. Under Y it does not run.
P-12(VOID, operator ruling 2026-10-05) The tag was the one external unknownNo tag is needed; there is no external unknown.

Complexity Tracking

ViolationWhy neededSimpler alternative rejected because
Registrations outside the slice file set: three --deselect entries and three registry rows, selecting the heavy battery (OD-1 X-pure; X: four and four)A new corpus-marked module is red in two gates without themPutting every test in existing modules (OD-1 Y) grows two modules to an estimated 2,800 and 1,250 lines; offered to the operator as the fallback.
A third path outside the slice file set, contracts/tools/fixtures/example_check/required_examples.json (OD-2 X only)The guard would then also fail in the workflow job python-checksOD-2 Y keeps the guard in the in-test cases inside the slice file set, equally non-vacuous.
Reproducing the matcher a third timeThe spec and AD-18 require a reader built from public seamsCalling a private copy reintroduces the exception AD-18 refused; the parity test bounds the duplicate.
A scanner class by key name, and (OD-4 X) a masked text passThe shared scanner chooses a class by key name; AD-19 needs a different rule for artifact paths, and the raw-text pass would otherwise flag a legitimate at-sign nameA reader-only scan would weaken FR-015; avoiding the pattern (OD-4 Y) leaves AD-19 undelivered in the scanner.

Traceability: spec identifiers to plan elements

Spec idPlan element
FR-001, FR-002IC-04; D-P6, D-P9; contracts/operations-and-schemas.md; AC-DETAIL rows
FR-003, FR-004IC-04, IC-07; data-model "Detail field sources"; research R-5 (rows, subtasks)
FR-005IC-04, IC-07; D-P9; data-model; AC-DETAIL review-cycle rows
FR-006IC-04, IC-07; data-model (workspace); HostCapability (D-P8)
FR-007IC-07; D-P3, D-P4; research R-3, R-4; data-model "changeState"
FR-008IC-04, IC-06 (eligibility), IC-07; data-model
FR-009, FR-010, FR-011IC-03, IC-06; data-model (walk, classifier); D-P2
FR-012, FR-013IC-03, IC-06; data-model (order of decision, status pairing)
FR-014IC-06; eligibility; D-P12 (fingerprint); containment tests
FR-015IC-02; D-P7; OD-4; contracts/tool-extension-and-reader.md A
FR-016IC-07, IC-08; D-P8 (host capability); corpus invariants
FR-017IC-05; D-P10; ACK-1; research R-7; W-7
FR-018IC-03 to IC-05; D-P1; D-P11
FR-019IC-03, IC-04, IC-05; examples list; D-P11; credential-kind test
FR-020D-P8; every new check's fail-loud column; IC-08
FR-021IC-08; D-P6, D-P13; research R-5
FR-022IC-06, IC-07; test map; mutation tests
FR-023IC-02 (leak half), IC-08; 22 red-first plants and 3 regression controls plus reader-level planted pairs (contracts/tool-extension-and-reader.md A; Test strategy)
FR-024IC-05 (CHANGELOG reference, route-coverage grep); no map
NFR-001D-P5; research R-5, R-6; IC-08, W-7
NFR-002D-P8 (counting seam); detail and listing and content bounded-read tests
NFR-003D-P12; IC-08
NFR-004IC-02; reality leak scans
NFR-005determinism tests; two bundle builds (D-P10)
NFR-006Gate set (ruff, format); fail-loud floors; mutation tests
NFR-007Seam; D-P4; scope function (D-P10)
C-001D-P10 (byte identity, bundle comparison, planted mutations)
C-002Contracts table; the bundle comparison
C-003Reflexivity items 3 and 7; add-only directory
C-004Charter Check; the leak scan before commit: leak_patterns.leak_codes(line, HUMAN) over every line of the Mission directory (reviews/ and the status log excluded) must report zero hits; no literal at-sign file name is written in the Mission files (Reflexivity item 3)
C-005Primary Dependencies: none new
AC-DETAIL, AC-LIST, AC-CONTENT, AC-VERSION"Test strategy per acceptance criterion"
SC-001 to SC-008SC-001 IC-03 to IC-05 and the ten checks; SC-002 IC-05, W-7 and research R-7; SC-003 IC-08; SC-004 IC-07 and IC-08; SC-005 IC-02 and IC-08; SC-006 D-P5 and D-P12; SC-007 D-P1 and D-P11; SC-008 D-P10 scope function (read with the two extra paths under the OD-1 X-family (X or X-pure); the third path under OD-2 X) run over the real branch diff at W-1 and W-7, and the Seam table
CL-1 to CL-5, AD-1 to AD-22Built as written; plan decisions D-P1 to D-P13 are the only places the plan adds a choice, each listed under "Departs from the spec" where it differs
OQ-1, OQ-4Decision register
A-5research R-5

Final report notes for the next command

Branch contract, repeated for the report: current branch issue-5625-mission-status-contract-1-1; planning/base branch issue-5625-mission-status-contract-1-1; final merge target main through one draft pull request after the wrap-up sequence. Operator decisions OD-1 to OD-4 above are decided by the operator 2026-10-04 (X-pure, Y, first-in-byte-order, scoped text pass). ACK-1 is void (it applied to OD-1 X only). The plan text is written for X-pure; statements marked "X only" are the record of the option not chosen for OD-1 (the live module) and OD-2; the OD-4 statements describe the chosen option (see "Decided reading" under Operator decisions). 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).