Implementation Plan: Mission-Type Creatability via Rich Step Model
Branch: feat/mission-step-creatability | Date: 2026-07-17 | Spec: spec.md Input: Feature specification from kitty-specs/mission-step-creatability-01KXQA6R/spec.md
Summary
Complete the step-authority cutover for template_set and make the documentation/research/plan mission types creatable again, then graph-back the mission_type → step → template chain. Three sequenced concerns, tidy-first: (A) retire the persisted MissionType.template_set field and read the step authority at the consumption boundary (behavior-preserving, atomic); (B) author the three types' content on their own step names so they project a non-null template mapping (Q1-gated); (C) mint template: DRG nodes + step → template instantiates edges and add a resolve-by-URN lane. Design is locked by the ADR (2026-07-16-2 D3 + 2026-07-17 Amendment), a research squad, an adversarial verification squad (LAND), a Q1 code trace, and a spec-review squad (plan-ready). Follow-up #2751 carries the action_sequence symmetry.
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: pydantic (frozen models, extra="forbid"), ruamel.yaml (doctrine YAML), typer/rich (CLI) — no new dependencies Storage: doctrine YAML on disk (src/doctrine/missions/, src/doctrine/*.graph.yaml); no database Testing: pytest; ATDD red-first through pre-existing entry points; tests/doctrine/, tests/charter/, tests/architectural/, tests/doctrine/drg/ freshness; PWHEADLESS=1 for any UI-adjacent runs (none here) Target Platform: cross-platform CLI (Linux/macOS/Windows) Project Type: single project (doctrine layer + charter/runtime consumers) Performance Goals: no regression to the FSM <100ms hot path (unaffected — template_set is off it); FR-002 seam performs exactly one mission-steps/ resolution per (mission_type, pack_context) (NFR-003) Constraints: behavior-preserving except the intentional field retirement + new content + new instantiates edges; ruff + mypy --strict clean, zero new suppressions; complexity ≤15; hoist ≥3× literals; DRG baseline 280/757/10 fresh Scale/Scope: ~4-5 code sites for the cutover + ~6 test-file migrations (Concern A); 16 authored prompts + per-type template refs (Concern B); 1 new extractor pass + 1 resolver lane + DRG re-baseline (Concern C)
Charter Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Single canonical authority ✅ — the mission collapses a duplicate projection onto the one step authority (net improvement).
- Architectural alignment / DDD-tiered rigour ✅ — changes stay in the doctrine layer + its charter/runtime consumers; the C-002 scalar fence prevents cross-domain leakage.
- ATDD-first / red-first ✅ — every concern lands a red test through the pre-existing entry point first (creation path, projection, extractor, resolver).
- Terminology canon ✅ — no
feature/legacy terms; Mission canon respected (terminology guard run on prose). - Canonical sources ✅ — content authored on the doctrine step surface; no improvised substitutes.
- Git/workflow ✅ — PR-bound, operator merges; no direct origin/main push.
No violations → Complexity Tracking left empty.
Project Structure
Documentation (this mission)
kitty-specs/mission-step-creatability-01KXQA6R/
├── plan.md # This file
├── spec.md # committed
├── research.md # 2-squad grounding + Q1 (committed)
├── data-model.md # schema/graph deltas (this command)
├── quickstart.md # validation scenarios (this command)
├── contracts/ # creation artifact_key + name↔URN + DRG-delta contracts (this command)
├── checklists/requirements.md # spec-quality checklist (committed)
├── traces/ # 3 tracer files (this command)
└── tasks.md # /spec-kitty.tasks output — NOT created here
Source Code (repository root)
src/doctrine/missions/
├── models.py # MissionType (retire template_set field) [Concern A]
├── mission_type_repository.py # _inject_projected_fields (drop template_set overlay, keep action_sequence) [A]
├── mission_step_repository.py # memoise default() [A]
├── step_projection.py # order steps by sequence_index (determinism) [A — sole owner]
├── mission_types/*.yaml # unchanged (already carry neither field)
└── mission-steps/
├── documentation/<step>/{prompt.md,step.yaml} # author 7 prompts + template refs [Concern B]
├── research/<step>/{prompt.md,step.yaml} # author 5 prompts + template refs [B]
└── plan/<step>/{prompt.md,step.yaml} # author-fresh 4 prompts + scaffolds [B]
src/doctrine/missions/{documentation,research,plan}/templates/ # per-type template files [B]
src/doctrine/drg/migration/extractor.py # new instantiates pass [Concern C — sole owner]
src/doctrine/*.graph.yaml # regenerated (action.graph.yaml gains edges) [C]
src/doctrine/template_catalog.py # URN authority (consumed) [C]
src/charter/mission_type_profiles.py # _resolve_template_set_slot :744 (in-scope slot only) [A]
src/specify_cli/runtime/resolver.py # add resolve-by-URN lane (signature stable) [C]
src/specify_cli/cli/commands/mission_type.py # migrate :1491/:1509 reads [A]
tests/doctrine/missions/test_prompt_emptiness.py # coupled edit + scaffold retirement [Concern B — sole owner]
tests/doctrine/drg/ + test_extractor_projection.py # DRG count bump + instantiates assertion [C]
Structure Decision: single-project doctrine layer. Ownership is partitioned along the A→B→C sequence with the shared seams assigned to a single concern each (see C-009..C-012 in the IC map).
Implementation Concern Map
> Concerns are NOT work packages. /spec-kitty.tasks translates these into WPs. Sequencing: A (IC-01) → B (IC-02/03/04, then IC-05) → C (IC-06/07, IC-08 verifies).
IC-01 — Atomic structural cutover (Concern A)
- Purpose: Retire the persisted
MissionType.template_setfield and source the projection from the step authority, behavior-preserving — a single atomic change (C-009) so no intermediate tree has a dangling.template_setread underextra="forbid". - Relevant requirements: FR-001, FR-002, FR-003, FR-011 (step ordering), NFR-001, NFR-003, C-001, C-002, C-005, C-006, C-007, C-009.
- Affected surfaces:
models.py(remove field),mission_type_repository.py:_inject_projected_fields(drop thetemplate_setoverlay:200-202; keep theaction_sequenceoverlay:199),mission_step_repository.py(memoiseresolve_all_for_mission_typekeyed by(mission_type, pack_context)— NOT justdefault(); the filesystem walk lives inresolve_all_for_mission_type, and after the cutover two consumers hit it per resolution [the retainedaction_sequenceoverlay + the newtemplate_setslot], so the cache must be shared, with a.cache_clear()test seam — paula d3),step_projection.py(sole owner — order steps bysequence_index, mirroringproject_action_sequence; expose a publiciter_template_refs(steps) -> list[(MissionStep, MissionStepTemplateRef)]promoting the private_step_template_ref:111, consumed by BOTHproject_template_setAND IC-06's pass so there is one traversal ofstep.template, not two — paula fold-in),charter/mission_type_profiles.py:744(_resolve_template_set_slot→project_template_set(steps); never the scalar:145/:1001),cli/commands/mission_type.py:1491,1509-1511(migrate to resolved context +dict()-wrap). Tests: fromTestMissionTypeRepositoryLiveProjection(class:115) retire onlytest_default_resolves_software_dev_template_set(:131-135) — KEEPtest_default_resolves_software_dev_action_sequence(:125-129, validates the C-007-retained overlay); migrate every.template_setread on aMissionTypeinstance (grep-driven — explicitly includestest_mission_type_repository.py:47, outside the:89-105/:181-197ranges — paula d1) →project_template_set/ResolvedMissionType; keepTestSoftwareDevProjectionParity; add the missing CLI test, the one-walk shared-cache call-count test (NFR-003), the pack-authored-template_set-fails-loudly regression, and the software-dev filename + canonical---json-order parity. Optional campsite: freshen the staletests/runtime/test_runtime_seam.py:18-24docstring if touched. - Sequencing/depends-on: none (lands first).
- Risks: atomicity (C-009 — one WP or strictly-ordered same tree); the scalar co-habits
mission_type_profiles.py(C-002 — no blind grep-replace); must not rename the resolved property (C-006); must not touch theaction_sequenceoverlay (C-007). Behavior-preservation is provable today (software-dev steps already carry refs → identical projection).
IC-02 — Documentation content authoring (Concern B)
- Purpose: Make
documentationcreatable by authoring its 7 step prompts + template refs on its own step names. - Relevant requirements: FR-004, FR-007 (doc), NFR-004, C-003, C-010.
- Affected surfaces:
src/doctrine/missions/mission-steps/documentation/{discover,audit,design,generate,validate,publish,accept}/{prompt.md,step.yaml};src/doctrine/missions/documentation/templates/**(rename/replace the existing software-dev-shaped files to documentation vocabulary). Promote content from the existingguidelines.md. - Sequencing/depends-on: IC-01 (clean surface); Q1 contract (C-010) — author
artifact_key: "spec"on a step +"plan"on another. - Risks: genuine content not gamed (NFR-004); do not touch
test_prompt_emptiness.py(IC-05 owns it).
IC-03 — Research content authoring (Concern B)
- Purpose: Make
researchcreatable by authoring its 5 step prompts + template refs on its own step names. - Relevant requirements: FR-005, FR-007 (research), NFR-004, C-003, C-010.
- Affected surfaces:
mission-steps/research/{scoping,methodology,gathering,synthesis,output}/{prompt.md,step.yaml};research/templates/**(rename/replace to research vocabulary). Promote fromguidelines.md. - Sequencing/depends-on: IC-01; Q1 (C-010).
- Risks: as IC-02.
IC-04 — Plan content authoring (Concern B — HEAVIEST, author-fresh)
- Purpose: Make
plancreatable — author 4 step prompts and its scaffold template files from scratch (noguidelines.md, emptytemplates/). - Relevant requirements: FR-006, FR-007 (plan), NFR-004, C-003, C-010.
- Affected surfaces:
mission-steps/plan/{specify,research,plan,review}/{prompt.md,step.yaml};plan/templates/(author-fresh scaffolds). Plan-domain (decomposition/decision, no code); do not** clone the software-dev shape despite thespecify/planname collision. - Sequencing/depends-on: IC-01; Q1 (C-010) — Q1 fixes plan's requested
artifact_keys (spec/plan) and thus the scaffold set. - Risks: largest concern — do not size symmetric with IC-02/03; name-collision contamination (C-003 — per-type
template_file, NFR-006 guard).
IC-05 — Emptiness-test ownership + retirement (Concern B, single owner)
- Purpose: Keep the emptiness guard truthful as prompts fill, then retire the seeded-blank scaffold.
- Relevant requirements: FR-008, NFR-004, C-011.
- Affected surfaces:
tests/doctrine/missions/test_prompt_emptiness.py(sole owner) — shrink_SEEDED_BLANK_STEPS, drop eachxfail, decrement golden16, update_SEQUENCE_STEPS_BY_TYPE; once empty, retire the scaffold and add a positive "every sequence step has a non-empty prompt" assertion + the reviewer-checklist substance gate hook. - Sequencing/depends-on: IC-02, IC-03, IC-04 (their authored prompts are inputs).
- Risks: C-011 — a single owner prevents three WPs merge-conflicting on the golden count.
IC-06 — Graph-back extractor pass + DRG re-baseline (Concern C)
- Purpose: Emit the
mission_type → step → templatechain into the shipped graph. - Relevant requirements: FR-009, FR-011 (edge-emission sort), NFR-002.
- Affected surfaces:
src/doctrine/drg/migration/extractor.py(sole owner — new pass modeled onextract_mission_type_edges, consuming IC-01's sharediter_template_refs(steps)helper — NOT a second private step-iteration — because the ref is a structuredMissionStepTemplateReffield, not areferences:entry); regeneratedaction.graph.yaml(gains edges) +template.graph.yaml(nodes-only; 16 bare exemplars untouched);tests/doctrine/drg/+test_extractor_projection.py(bump_EXPECTED_NODE/EDGE_COUNTto280+N/757+N, orphans stay 10) + positive instantiates assertion; sweep every arch marker (orphan-residual,_ARCH_SHARD_N_FILES, cardinality golden-counts). - Sequencing/depends-on: IC-02/03/04 (refs exist) — N is computed here, after authoring, never pinned upfront. Reads
step_projection.py(owned by IC-01, read-only). - Risks: determinism (relies on IC-01's
sequence_indexordering); arch-marker sweep completeness.
IC-07 — Resolve-by-URN lane (Concern C)
- Purpose: Add URN-addressed resolution as a second lane alongside resolve-by-name.
- Relevant requirements: FR-010, C-002, C-004.
- Affected surfaces:
src/specify_cli/runtime/resolver.py(add a URN function converging ontemplate_catalog.resolve_template_by_id, mission-qualified form; signature ofresolve_configured_templateunchanged) + a by-URN==by-name equivalence test incl. override-wins (US3.3). - Sequencing/depends-on: IC-01 (cutover); IC-06 (qualified nodes exist).
- Risks: C-002 —
resolver.pyneighbours the scalar surfaces; the fence is on referencing the scalar, not on importingResolvedMissionType. C-004 — two lanes, do not collapse; do not re-wire the name-based creation path.
IC-08 — Cross-type uniqueness + NFR guards (verification)
- Purpose: Assert the cross-cutting invariants that no single authoring WP owns.
- Relevant requirements: NFR-006, C-003; SC-004 shipped-graph assertion.
- Affected surfaces: a guard test asserting no two mission types project the same
template_file; may fold into IC-05/IC-06 at /tasks. - Sequencing/depends-on: IC-02/03/04 (refs authored).
- Risks: minor; keep as a standalone guard so ownership is clear.