Implementation Plan: Every Mission-file writer takes the lock, and the runtime never cuts a log it cannot prove is its own
Branch: issue-5883-mission-writer-followups | Date: 2026-10-08 | Spec: spec.md Input: Mission specification from kitty-specs/mission-writer-followups-01M4CYWW/spec.md
Summary
This Mission finishes what PR #5890 started, in three areas.
1. Every Mission-file writer goes through one lock door with one key. mission_write_lock is rekeyed onto one pure, git-free key function so that every door and the status transaction resolve the same lock file for a Mission. The meta.json, work-package frontmatter and matrix writers then run their read-modify-write inside that door (FR-001..FR-005, FR-020). The architectural gate gains Rule 4 and closes the three documented holes (FR-006, FR-007, FR-008, FR-019). 2. The runtime never cuts a log it cannot prove is its own. The legacy next path runs the retrospective gate through the engine's existing abort-only before_run_completed hook, on every path including the stale-plan fallback, and the speculative capture/rollback/buffer is deleted (FR-009..FR-011). 3. The planning flow offers every step it enforces, from the canonical template. next reads runtime templates from packs/built-in/missions and the software-dev step order gains an analyze step guarded by an injected analysis-currency check (FR-016..FR-018). The shipped software-dev prompts and step definitions are cleaned up (FR-015, FR-022), operator text says "mission" (FR-012..FR-014), and the glossary is made consistent (FR-021).
Engineering Alignment (confirmed by the operator, 2026-10-08, Decision Moment 01M4D0F447AFXMSGFDJ661P8A2)
- Rekey
mission_write_lock. The lock key becomes the canonical Mission key, computed once before the lock is entered. On a coordination-routed Mission with a recorded mid8, the canonical key is the coordination directory name (coord_mission_dir_name). Otherwise it is the Mission directory name. The status transaction and every door use the same function, so a legacy060-testprimary directory and its060-test-<mid8>coordination surface lock one file. - Only the runtime step order moves to the pack.
mission.yaml, the per-typetemplates/and the Python modules stay undersrc/specify_cli/missions(#2652's later slices own them). Only the fourmission-runtime.yamlcopies are retired fromsrc(C-008). - In-flight runs keep their frozen order. A software-dev run started before the change keeps its frozen step order with no analyze step. Its recorded template path disappears when the
srccopy is retired, so the drift check is skipped (FR-017). The existing implement-timeanalysis_report_requiredrefusal stays the backstop. - Operator change: the pack's software-dev prompts and steps are cleaned up in this Mission (new FR-022, SC-008).
- Operator change, 2026-10-08: the pack's Mission config must match the one the CLI runs today (new FR-023, SC-009). Today the CLI reads
src/specify_cli/missions/<type>/{mission.yaml,mission-runtime.yaml}, while the charter repository reads the pack copies. The pack copies diverge:agent-profilekeys on runtime steps,task_typesblocks, documentationdeliverables: docs/output/vsdocs/, a plan runtime template that does not load, and feature/mission wording. The running value wins in every case. Wording fixes go to both copies.
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: typer, rich, ruamel.yaml; the existing specify_cli.status.mission_write primitive; the runtime engine src/runtime/next/_internal_runtime/engine.py Storage: Mission files in git (meta.json, tasks/WP*.md, issue-matrix.json, acceptance-matrix.json); runtime run directory (state.json, run.events.jsonl) Testing: pytest; deterministic cross-thread interleavings with injected pause points (NFR-001); red-first per ADR 2026-07-17-1 (C-006); synthetic offender + near-miss + self-mutation for every gate rule (NFR-004) Target Platform: Linux, macOS, Windows Project Type: single project Constraints: the runtime→specify_cli ledger does not grow (C-001); one lock door (C-002); source prompts only (C-003); only mission-runtime.yaml moves (C-008) Scale/Scope: about 40 production modules, 1 new gate rule plus three extended ones, about 15 prompt or step files, 4 glossary surfaces
Charter Check
- Single canonical authority. One lock door, one key function and one runtime template per type; the
srcruntime-template copies are retired. PASS. - Architectural alignment. The runtime gets the analysis-currency check injected by its
specify_clicaller, and the ledger shrinks from 23 to 22 because both bareimport specify_cliedges are removed (C-001). PASS. - ATDD / red-first. Every "no-op passable: no" requirement has a reproduction shown failing first; compound requirements are proven part by part (C-006). PASS.
- Gate discipline. Empty allowlist; every rule gets a synthetic offender, a near-miss and a self-mutation proof (NFR-004). PASS.
- Terminology canon. FR-012..FR-014 and FR-021 are the terminology work itself. PASS.
- Locality. The pack cleanup (FR-022) is bounded to software-dev step files and the files they reference. Other Mission types are untouched. PASS.
Design decisions
D1 — Canonical Mission lock key (rekey)
specify_cli.status.mission_write.mission_lock_key(feature_dir) -> str is pure and git-free. It reads meta.json from the given directory: if the Mission is coordination-routed (coordination_branch recorded) and a mid8 is recorded, it returns coord_mission_dir_name(slug, mid8). Otherwise it returns feature_dir.name.
mission_write_lock(feature_dir)keys onmission_lock_key(feature_dir).mission_write_lock_dir(repo_root, handle)resolves the directory with the existing read-only resolver and returnsrepo_root/kitty-specs/<key>.BookkeepingTransaction's_mission_specs_dir_name(incoordination/legacy_resolution.py) is routed through the same function (amendment A1/A3 supersede the earlier "only a test" wording).- The key is computed before the lock is entered and is passed down; it is never resolved inside the lock.
D2 — Locked meta and frontmatter helpers
mission_metadata.locked_update_meta(feature_dir, mutate, *, repo_root=None, timeout=BOUNDED)takes the lock, re-readsmeta.json, appliesmutate, and writes atomically. Every setter in FR-001 and every writer in FR-020 uses it.set_change_mode,clear_coordination_metadataandset_purpose_summaryhave no production caller and are removed, along with their dead-symbol allowlist entries and tests.frontmatter.locked_update_frontmatter(wp_path, mutate, *, feature_dir, repo_root=None)does the same for work-package frontmatter. map-requirements and the finalize flush use it.- The finalize write-scope restore becomes a compare-and-swap: a file is restored only while its bytes still equal what finalize itself wrote.
- The lane mirror is already locked at runtime, through the emit lock or the transaction. It stays as it is, and the gate recognises both regions (D5).
D3 — Matrix writers
scaffold_issue_matrixruns its existence check and its write undermission_write_lock.- The acceptance-matrix and issue-verdict helpers and the acceptance verdict guard lock through
mission_write_lock, keyed via D1. - The FR-005 red test is written first and must show which of the two divergences causes the failure, key or root (research R3).
D4 — Runtime terminal gate (FR-009..FR-011)
- The legacy
_dn_advance_enginepassesbefore_run_completed=retrospective.before_run_completedtocommit_advance. engine.next_stepgains the same keyword argument, so the stale-plan and no-plan fallbacks run the gate before anything is appended._run_retrospective_learning_captureraises the typedMissionCompletionBlocked. The bridge turns it into a decision that reads as a retrospective-gate refusal._dn_capture_pre_speculative_state,_dn_rollback_buffered_run_state,_BufferingRuntimeEmitterand their tests are deleted.- A run that is already terminal is not re-gated on a later poll, because the engine hook fires only on the transition into terminal.
D5 — Gate extensions (FR-006..FR-008, FR-019)
- Rule 1 also flags
open(..., "w"),write_textandwrite_byteswhose target is a status or run log. It scanssrc/runtimewith the run-log and run-state names. Every newly seen site is fixed: the consolidation bookkeeping projection rewrites the status log under the status lock; the lane auto-rebase's create-if-missing becomes an exclusive create. The git merge driver's output write is excluded by a stated structural rule (it writes the path git hands it). - Rule 2 accepts a callable passed into a lock region only if its parameter is only ever called, never stored, returned or assigned.
- Rule 3 checks
mission_write_lock's first argument as well. It accepts only known Mission-directory-name expressions (.nameof a directory,mission_lock_key(...),mission_write_lock_dir(...)) and reports subscripts, calls and attributes containing "slug". - Rule 4 (new) treats these as sinks:
write_meta,restore_meta_text,write_frontmatter,update_fields, andwrite_text/write_bytes/atomic_writeonmeta.jsonortasks/WP*.md. A sink must be inside a lock region, inside a registered locked helper (D2), or inside a status-core lock region. The status-core regions areBookkeepingTransactionand the emit lock, recognised structurally. - Migrations get no exemption. They write through the helpers.
D6 — Canonical runtime templates (FR-018, NFR-006)
- The built-in tier resolves through
charter.activation.mission_type_profile_repository.builtin_missions_root(), whichruntimemay already import. The two bareimport specify_cliedges inruntime_bridge_iogo away, the""ledger entry is removed, and the cap drops to 22. - The four
src/specify_cli/missions/<type>/mission-runtime.yamlcopies are reconciled into the pack and deleted. The deprecation banner goes. - Reconciliation keeps behaviour.
- The pack's
plantemplate takes thesrcshape, because today the pack copy does not load. - An
agent-profilestays only on a step that already dispatches through composition. Discovery and documentation-accept lose theirs, so routing does not widen. - A per-type test asserts that the planned step sequence and the dispatch route of every step equal today's, apart from the software-dev analyze step.
- Config parity (FR-023). Each pack
mission.yamlis made byte-equal to its src copy. The pack-onlytask_typesand the documentationdeliverables: docs/output/are dropped, because the CLI never ran them; the wording fixes are applied to both copies. A parity test over the four types pinsmission.yamlequality while the src copy exists. Before a pack-only key is dropped, check whether any charter reader of the pack copy (charter/offering/missions/repository.py,charter/activation/*,dossier/manifest.py) consumes it; if one does, stop and raise it as an owner decision rather than dropping it.pack-manifest.yamlhashes are refreshed withspec-kitty doctrine regenerate-graph.
D7 — Analyze step (FR-016, FR-017)
- The software-dev runtime template becomes
discovery → specify → plan → tasks → analyze → implement → review → accept. - A new
analyzeguard in_evaluate_software_dev_guardsreads an injectedanalysis_currencyfact. That fact is a callable passed as a keyword argument fromnext_cmd.decide_nextthroughdecide_nextintoDecideNextContext, so the runtime gets it without importing specify_cli. The callable wrapsanalysis_report.check_analysis_report_current. - The callable is evaluated only when the current step is
analyze. - On refusal the step is re-issued with
error_codeset toANALYSIS_REPORT_MISSINGorANALYSIS_REPORT_STALE, andguard_failuresnaming each stale input. - The finalized-board override (
_dn_finalized_board_override) must not jump past analyze. It gets a red test and a fix if it does (research R6). - In-flight runs: see Engineering Alignment.
D8 — Pack software-dev cleanup (FR-015, FR-022)
Concrete list in research R7. All edits are to source files under packs/built-in/missions. The provenance ratchet baseline is lowered with its refresh command, spec-kitty doctrine regenerate-graph is run after the step.yaml and action-index changes, and every pinning test listed in R7 is updated.
D9 — Terminology and commitlint (FR-012..FR-014)
- All five commit builders say "for mission". The finalize drift check accepts the old and new subjects (FR-013).
- The CLI errors listed in R8 say "mission".
- The FR-014 scan uses
astover commit-message arguments and error strings. - commitlint's planning-subject rule is extended to cover the scaffold, gap-analysis and generator-config subjects for both words. That rule rejects them today.
D10 — Glossary (FR-021)
- topic branch is added. Mission and Mission Run are rewritten. feature branch becomes an alias of topic branch.
- The changes go to
docs/context/orchestration.md, the YAML seed, the built-in glossary pack and the generated contextive glossary, which is regenerated. - The seven inconsistencies listed in R9 are fixed.
- The parity tests and the regenerate-graph check run after the pack edit.
Project Structure
src/specify_cli/status/mission_write.py # mission_lock_key; mission_write_lock rekeyed (D1)
src/specify_cli/missions/_read_path_resolver.py # mission_write_lock_dir via the key (D1)
src/specify_cli/mission_metadata.py # locked_update_meta; setters; dead setters removed (D2)
src/specify_cli/frontmatter.py # locked_update_frontmatter (D2)
src/specify_cli/acceptance/__init__.py, acceptance/matrix.py, cli/commands/agent/issue_verdict.py, tasks/issue_matrix.py # D2/D3
src/specify_cli/cli/commands/agent/tasks_map_requirements.py, mission_finalize*.py # D2
src/specify_cli/{core/mission_creation_meta.py, tracker/origin.py, cli/commands/mission_type.py, doc_analysis/doc_state.py, consolidation/{phase_teardown,baseline}.py, consolidation/mission_number/bake.py, mission_loader/command.py, migration/*.py, upgrade/*.py} # FR-020 writers
src/runtime/next/runtime_bridge.py, runtime_bridge_io.py, runtime_bridge_retrospective.py, runtime_bridge_cores.py, runtime_bridge_engine.py, decision.py, _internal_runtime/engine.py # D4/D6/D7
src/specify_cli/cli/commands/next_cmd.py # inject analysis currency (D7)
src/specify_cli/missions/*/mission-runtime.yaml # deleted (D6)
packs/built-in/missions/{software-dev,documentation,research,plan}/mission-runtime.yaml # reconciled (D6/D7)
packs/built-in/missions/software-dev/**, packs/built-in/missions/mission-steps/software-dev/** # cleanup (D8)
commitlint.config.cjs; status/uninitialized_hint.py; task_utils/support.py; plan_validation.py; validate_*.py; mission_setup_plan.py; mission_finalize_planning_pin.py; core/mission_creation_commit.py # D9
docs/context/*.md; .kittify/glossaries/spec_kitty_core.yaml; packs/built-in/glossary_packs/spec-kitty-core.glossary-pack.yaml; src/specify_cli/.contextive/*.yml # D10
tests/architectural/test_mission_write_discipline.py # Rules 1-4 (D5)
tests/architectural/test_layer_rules.py, _baselines.yaml # ledger 23 -> 22 (D6)
Implementation Concern Map
IC-01 — Canonical lock key and meta.json writers
- Purpose: one key for every door; every
meta.jsonread-modify-write under it. - Relevant requirements: FR-001, FR-020 (meta families), US1, NFR-002, NFR-003
- Affected surfaces: mission_write, _read_path_resolver, mission_metadata, acceptance, the FR-020 writer modules
- Sequencing/depends-on: none
- Risks: nesting under
ensure_vcs_lockedand the acceptance guard; a subprocess holding the parent's lock waits for the bounded timeout (edge case).
IC-02 — Frontmatter and matrix writers
- Purpose: map-requirements, finalize and the matrix scaffold/helpers locked.
- Relevant requirements: FR-002..FR-005, US2, US3
- Affected surfaces: tasks_map_requirements, mission_finalize*, issue_matrix, acceptance/matrix, issue_verdict
- Sequencing/depends-on: IC-01
- Risks: finalize's long in-memory window; the restore must be compare-and-swap.
IC-03 — Gate Rules 2, 3 and 4
- Purpose: close the writer class by construction.
- Relevant requirements: FR-006, FR-007, FR-019, NFR-004
- Affected surfaces: tests/architectural/test_mission_write_discipline.py
- Sequencing/depends-on: IC-01, IC-02
- Risks: false positives on status-core regions; must be structural, not allowlisted.
IC-04 — Runtime terminal gate and Rule 1
- Purpose: the gate runs before completion on every path; the rollback is gone; Rule 1 sees whole-file rewrites and
src/runtime. - Relevant requirements: FR-008..FR-011, US5
- Affected surfaces: runtime_bridge*, engine, consolidation bookkeeping projection, lane auto-rebase, the gate
- Sequencing/depends-on: none
- Risks: behaviour change: an already-terminal run is not re-gated (accepted, US5-AS6).
IC-05 — Canonical runtime templates and the analyze step
- Purpose:
nextreads the pack, and the analyze step is guarded. - Relevant requirements: FR-016..FR-018, FR-023, NFR-006, SC-009, C-001, C-004, C-008
- Affected surfaces: runtime_bridge_io, runtime_bridge_cores, decision, next_cmd, pack mission-runtime.yaml files, layer-rule ledger
- Sequencing/depends-on: IC-04 (same runtime files)
- Risks: agent-profile routing widening; the finalized-board override skipping analyze; in-flight drift.
IC-06 — Pack software-dev cleanup
- Purpose: prompts and step definitions match the CLI.
- Relevant requirements: FR-015, FR-022, SC-008, C-003
- Affected surfaces: packs/built-in/missions/software-dev/, mission-steps/software-dev/, ratchet baseline, the pinning tests in R7
- Sequencing/depends-on: IC-05 (prompts describe the new step)
- Risks: shrink-only ratchet tightness; the regenerate-graph check.
IC-07 — Terminology and glossary
- Purpose: "mission" in operator text; one glossary.
- Relevant requirements: FR-012..FR-014, FR-021
- Affected surfaces: see D9 and D10
- Sequencing/depends-on: none
- Risks: goldens and fixtures that assert the old subjects.
Complexity Tracking
None.
Follow-ups (not in scope)
- Retiring the remaining
src/specify_cli/missionstype directories (mission.yaml,templates/): #2652 slice 2+, #2661, #4822. - Serialising two concurrent runtime commits (#5854); git
index.lockcollisions (#5515, #5443).
Amendments after the post-plan squad (binding; they override D1–D10 where they disagree)
Lens A: lock key, helpers, matrix, gate
- A1 (blocker): one key for every lock caller. The rekey covers every per-Mission lock caller, not only
mission_write_lock. That means every directfeature_status_lock(root, X.name)caller: emit:977/:1153/:1263, work_package_lifecycle:312/:468, move-task executor, mark-status, agent status, decisions emit, finalize status surface, retrospective lifecycle events and the migrations. Each passesmission_lock_key(...).BookkeepingTransactionandcoord_status_lockcall the same function, so there is a single authority. The bare-directory coordination fixture (a060-testprimary directory and a060-test-<mid8>coordination directory) is the fixture for every key test and for a lock-order test. That test runs the lifecycle path and the implement claim path on two threads and must not deadlock. - A2 (blocker).
_holds_mission_lockandcapture_rollback_pointresolve the key throughmission_lock_key. A test covers the bare-directory coordination shape. - A3.
mission_lock_keyuses the transaction's mid8 cascade (resolve_transaction_mid8:meta.mid8, thenmission_id[:8], then the slug tail) through one shared helper. An empty mid8 on a coordination-routed Mission makesmission_lock_keyraise a typed error, and the transaction's trailing-dash key is fixed to use the same function. - A4: key stability within a hold. The key is read from the canonical primary
meta.json(the read-path resolver), never from a lane worktree's copy. While a thread holds a Mission lock, nested entries for the same Mission reuse the held key from a thread-local, so a writer that changescoordination_branchormid8inside the hold never takes a second lock. A test pins this withflatten_coordination_metadatainside a hold. - A5: Rule 4 regions. A region is a lexical
withof a lock context manager, extended toExitStack.enter_context(<lock cm>),<lock cm>.__enter__()up to the matching__exit__, awith <name>whose name was assigned from a lock context manager, andlocked_acceptance_verdict_guard. - A sink inside function F is accepted when F is a registered locked helper, or when every same-module call site of F sits in a region.
- A cross-module callee whose call sites cannot all be resolved fails closed.
- There are no class or function-name exemptions.
- A6: Rule 3.
- Key arguments (
feature_status_lock) are accepted only asmission_lock_key(...). - Path arguments (
mission_write_lock,hold_mission_write_lock) are accepted as amission_write_lock_dir(...)result or a function parameter. - A bare
.nameis no longer accepted. - A7: Rules 1 and 4, target tracking.
- Within a function, names assigned from
EVENTS_FILENAME,"status.events.jsonl","meta.json",META_FILENAME,"run.events.jsonl"or"state.json"are tracked through assignments and/joins. - The sinks are truncate,
write_text,write_bytes,open(..., "w"/"x"/"r+"),shutil.moveonto a target, and a whole-fileos.replace/atomic_writeonto a target. - Structural non-sinks (amended after the analysis, binding): an append-only
open(..., "a")(the engine's_append_event,status/store.py's canonical append) and the tmp-then-os.replacepublish of a freshly built snapshot whose content is not derived from re-reading the target (the engine's_write_snapshot). Each gets a near-miss test; a replace whose source was read from the target is still a sink. rebuild_state.py:777andmigrate_lifecycle_envelope.py:250-272are dispositioned explicitly: they are locked, or excluded by a stated structural rule.- Each variant gets a synthetic offender.
- The merge-driver exclusion applies to Rule 4 as well.
- A8: compare-and-swap restore. Every branch of the finalize write-scope restore, rewrite and unlink alike, plus
restore_meta_text, runs inside the lock and acts only while the current bytes equal what finalize wrote (or, for an unlink, the file finalize created). Files that changed are reported as kept. - A9: red tests that fail today.
- FR-005 uses the bare-directory coordination fixture.
- FR-003's finalize applies its field delta to the freshly read frontmatter and body. Its red test covers a concurrent frontmatter field (a map-requirements ref) and a concurrent body note.
- A10: FR-020 additions.
- New writers:
task_metadata_validation.py:178andimplement_support.py:491(update_fields). - The body of
set_vcs_lockmoves to the locked helper. tasks.mdbecomes a Rule 4 sink.- Line numbers are re-derived at implement time (research line numbers are indicative).
- A11: Rule 2 escapes. A reference to the parameter other than as the function of a call counts as an escape. That includes passing it as an argument or keyword (
Thread(target=f),submit(f),partial(f)) and capturing it in a nested def or lambda. - A13: matrix verdict guard ownership.
locked_acceptance_verdict_guardlives inacceptance/matrix.py, so its rekey is done in WP04 with the other matrix helpers. - A12: NFR-003 measurement. Warm the
git_common_dircache first, then count the change in subprocess calls.mission_write_lock_dirstays off the hot path of a writer that already hasfeature_dir.
Lens B — runtime, templates, analyze, config parity
- B1 (blocker, FR-017). Query mode (
runtime_bridge_query.py:449-450) loads the run's frozen copy (run_dir/mission_template_frozen.yaml), as the ephemeral branch already does. The recorded live path is used only for the drift check. Red test: a persisted run whose recordedsrctemplate path no longer exists answers a barespec-kitty nextwithoutQueryModeValidationError. - B2 (blocker, FR-017/FR-023/NFR-006): "what runs today" is decided per type, by what actually resolves.
- Documentation and research resolve from the user-global tier, which
ensure_runtimeoverwrites from the pack at startup. So for these two types the packmission-runtime.yamlis already the template that runs, including documentation's acceptagent-profile. Their pack runtime bytes stay untouched: no wording or comment edits. A byte change would be re-copied to the global tier and would trip the drift check for in-flight runs. - Software-dev and plan resolve to the
srccopy today. Their pack copy takes thesrccontent: noagent-profilekeys beyond whatsrccarries, and the loadableplanshape. Software-dev also gains the analyze step. - Tier order is unchanged.
- NFR-006/SC-009 baseline. The baseline for each type is the template that resolves today, recorded by a test helper that runs the current resolver before the change.
mission.yaml. The CLI pins built-ins tosrc(mission.py:649-663), sosrcis what runs and the pack copy becomes byte-equal to it.task_typesand the documentationdeliverables: docs/output/have no reader, so dropping them changes no CLI behaviour. Wording fixes land in both copies. The charter compiler embeds the whole packmission.yamlin the compiledtemplate-set-*.md(compiler.py:1992-2005), so the charter-bundle goldens are regenerated and the change is called out in the PR.- B3 (blocker, D7).
_dn_finalized_board_override(runtime_bridge.py:575-629) and its query-mode twin (runtime_bridge_query.py:98-123) check analysis currency for software-dev before they hand out implement. When the report is missing or stale they issueanalyzewith the error code. Red tests cover both paths with a hand-run specify, plan and tasks and a finalized board. - B4 (D7): no caller skips the check.
- The currency callable is injected inside the shared
next_cmd.decide_nextwrapper, andorchestrator_api/decision_verbs.py:712is routed through that wrapper. - On the analyze step and in the board override, a missing callable fails closed with
ANALYSIS_CURRENCY_UNAVAILABLE, never "not evaluated". data-model is amended to match. - B5 (D7): plumbing.
- The verdict is computed by the bridge only when the step is
analyze, or when the board override would issue implement. It is stored in the snapshot'sstatus_facts, so the cores module stays a pure leaf. - Precedence: a prompt-resolution failure's error code wins over the analysis code.
- Tests assert that
_state_to_action("analyze")and_build_prompt_or_errorresolve for the new step, and that_with_guard_failure_pathsrenders the stale-input entries. - B6 (D4): refusal type.
MissionCompletionBlockedkeeps its real shape,(decision: GateDecision).- One bridge-level adapter wraps every failure of the hook in one typed refusal on both the legacy and the composition paths:
MissionCompletionBlocked, the policy error, and an arbitrary capture exception. The adapter is caught before the generic "Runtime engine error" and_advance_failed_decisionhandlers. - The hook signature is
Callable[[], None]. data-model is amended to match. - B7 (D4). A test pins that a terminal re-poll does not re-run the gate. The non-blocking learning capture also fires only on the transition into terminal.
- B8 (D6).
PackRootNotFoundfrombuiltin_missions_root()fails closed with a named error and is covered by a test.mission_loader/command.py:224-226switches to the same accessor, because itsruntime/missionsroot does not exist.- The
_baselines.yaml:22justification text is updated. - The ten test files that hard-code the
srcruntime path move to the pack path in the same WP. Among them aretests/next/test_plan_mission_runtime.py,tests/contract/test_plan_mission_yaml_validates.pyandtests/specify_cli/missions/test_mission_template_consistency.py.
Lens C — pack cleanup, wording, glossary
1. checking each --option in the same code span against the command's Click params; 2. scanning step-contract command: values rendered through _render_declared_command; 3. checking each "next advances to X" claim against the runtime template order; 4. resolving consumer paths against a spec-kitty init fixture, with an explicit list of placeholder forms.
- C1 (blocker): step-contract bootstrap command. Every built-in step contract declares
--profileand--toolinputs on its bootstrap step, and the executor appends them tospec-kitty charter context, which refuses both options. Decision: remove those two inputs from the bootstrap step of every built-in step contract, in all Mission types, because the CLI refuses them for every type. Updatetests/doctrine/mission_step_contracts/test_shipped_contracts.py:61-70and add a test that renders each contract's bootstrap command and parses it against the Click command. This is the one deliberate exception to the Locality claim; the PR calls it out. Adding the options tocharter contextis not in scope. - C2: R7 inventory additions.
- In the step contracts:
kitty-specs/{feature}/(specify:47, plan:49); "Commit … to main branch" (specify:51, plan:53), reworded to the planning branch the CLI reports; "Merge later records done" (review:78,review/guidelines.md:28), which becomes consolidate; "Include WP ID in commit scope" (implement:64); andmove-task {wp_id}with no--mission(implement:68, review:75). - The software-dev contracts are edited, and so is the C1 bootstrap input in every type.
- C3:
--missionboilerplate. Nine prompts tell the agent to pass--missionto every command. They are reworded to "every command that accepts--mission", andtest_has_feature_flag_guidancestays green. - C4: more false "next advances" claims.
- Fix accept:119 (there is no consolidate step), specify:504 (after tasks,
nextnow issues analyze), and review:323 and implement:360 (add--mission). - "Merge" becomes "consolidate" where that is what is meant (accept:10-12, :77).
- review:317-318 loses its hosted-sync wording.
- C5: repo-local residue in implement.
- Remove the spec-kitty-repo "authority pointers" (:95-101).
- The mandatory
.venv/bin/ruffcheck becomes "the project's own lint and format commands" (:188-190). git merge-base HEAD mainuses the mission's target branch (:197).- The agent directory list is replaced by a pointer to
spec-kitty agent config list(:291-295). - C6: analyze stays off the action sequence.
analyze/step.yamlkeepsin_action_sequence: false, so no action index and no graph edge are added, andtest_softwaredev_roundtrip.pystays as it is. The runtime template gains the step; the prompt resolves from the step directory (B5 asserts this). The tasks prompt's "undefined template" is fixed by referring to the tasks template through the existing template resolver wording, not by addingtemplate:totasks/step.yaml. - C7: more pinning tests to update in the same WP.
- Snapshots: the rendered
tests/specify_cli/regression/_twelve_agent_baseline/{claude,gemini}/specify.*andtests/specify_cli/skills/__snapshots__/codex/specify.SKILL.md, regenerated with the repository's snapshot update flag. - Correction:
test_command_template_cleanliness.py:396-408pins the tasks prompt, not analyze. The same file's:193limits the tasks-template fix. - Wording pins:
test_mission_creation_probe_order.py:107/:270,test_mission_creation_fanout_commit_boundary.py:164andtests/next/test_next_command_integration.py:556. - C8: provenance ratchet.
- The baseline is edited by hand, entry by entry, and the diff may only lower counts. The census dump is not redirected over the file.
- C-003 is reworded from "no
kitty-specs/paths" to "no concrete Mission slugs, WP ids, issue numbers, requirement ids orsrc/specify_clipaths". Placeholders such askitty-specs/<mission>/analysis-report.mdare allowed. - tasks-finalize gains a baseline entry only if it is at zero.
- C9: more "feature" sites (FR-012/FR-014).
- Errors to fix:
lanes/consolidation.py:273/:377,implement.py:379,implement_phases.py:145,task_utils/support.py:606,validate_tasks.py:78/:125,validate_encoding.py:50,core/mission_creation_identity.py:47,cli/helpers.py:437,mission_type.py:1666,mission_branch_context.py:149. - Commitlint covers the "origin-ticket binding" subject as well.
- The FR-014 scan covers every non-docstring string constant under
src/specify_clithat contains "for feature" or starts with "Feature:", not only call arguments. - The error code
FEATURE_CONTEXT_UNRESOLVEDis a machine contract and stays as it is. It is filed as a follow-up. - C10: analyze recovery recipe. The recipe writes the report to a path outside the checkout (the scratch or temp directory) and passes that path to
record-analysis. Writing into the checkout makes the worktree dirty, andrecord-analysisthen refuses withDIRTY_WORKTREE. - C11: glossary. Fix
docs/context/spec-driven.md:329. The contextive regeneration is expected to widen thetest_no_legacy_terminologybaseline diff fororchestration.yml; it may only shrink. - C12: SC-008 gate.
- SC-008 is a gate file,
tests/doctrine/test_software_dev_prompt_walk.py. It extendstest_builtin_cli_command_references.pywith: - It covers the CLI-driven implement, review, accept and tasks-finalize prompts.
- C13. Run
pip install -e .(oruv sync --frozen) before trustingtest_doctrine_regenerate_graph_roundtrip.py. The glossary pack is hashed, so run regenerate-graph after D10.