Implementation Plan: Mission-Type Doctrine Authority
Branch: mission/883-mission-type-governance-profiles | Date: 2026-07-14 | Spec: spec.md Input: Feature specification from kitty-specs/mission-type-doctrine-authority-01KXH6GE/spec.md Design authority: ADR 2026-07-14-2 · mission brief · research dossier
Summary
Make the doctrine MissionType artefact the single, load-bearing source of truth for a mission type's governance, gates, and steps, resolved through one charter-mediated doctrine → charter → core seam keyed off meta.json. Close the software-dev-default leak on the governance and action paths, demote software-dev to a peer mission type, author governance for documentation/research/plan, and swap the dossier gate reader onto the doctrine tree (detachable lane) — slice 1 of the specify_cli/missions retirement. The architecture is fixed by the ADR; this plan decomposes it into implementation concerns and design contracts.
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: pydantic v2 (models + extra="forbid"), ruamel.yaml (doctrine YAML), typer (CLI); internal subsystems charter, doctrine, runtime.next, specify_cli.dossier. Storage: Doctrine YAML files under src/doctrine/missions/ + the DRG (src/doctrine/graph.yaml); no database. N/A for runtime persistence. Testing: pytest at two tiers — doctrine-module unit tests (no specify_cli import) and integration tests exercising a real mission resolution; ruff + mypy --strict; parallel -n auto --dist loadfile. Transitional parity scaffolds are added per swap and deleted before merge. Target Platform: Linux CLI / library (Spec Kitty itself). Project Type: single (Python package). Performance Goals: N/A (governance resolution is not throughput-critical); determinism is required (identical inputs → byte-identical resolved governance, NFR-007). Constraints: cyclomatic complexity ≤ 15 (ruff C901 / Sonar S3776); charter/ must not import specify_cli (tests/architectural/test_layer_rules.py); no # noqa/# type: ignore additions; no shim/compat code kept solely to avoid test churn; no surviving parity ratchet (parity scaffolds are transitional). Scale/Scope: 4 built-in mission types; ~10–12 WPs across 4 lanes; touches charter, doctrine, runtime.next, specify_cli.dossier, and src/doctrine/missions/*.
Charter Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Governing principle | This mission | Verdict |
|---|---|---|
| Single canonical authority | This mission creates the single authority (the doctrine MissionType artefact) and retires the duplicate tree; it does not add a competing surface. | ✅ Advances |
| Architectural alignment | Follows ADR 2026-07-14-2 (Accepted); the resolver seam reuses existing charter/doctrine machinery (extend-not-invent). | ✅ Pass |
| DDD + tiered rigour | Core resolution logic (charter/doctrine) is core-tier (full rigour); YAML authoring is glue. | ✅ Pass |
| ATDD-first / red-first | Leak closure and each swap begin with a RED behavioural test through the pre-existing entry point (DIRECTIVE_041). | ✅ Pass (enforced in ICs) |
| Architectural gate discipline | New enduring gate is the non-leakage + non-vacuity test (NFR-006); layer rule (C-001) respected. | ✅ Pass |
| Canonical sources & unification | Chases unification (one authority), not parity with the dead governance_refs quirk. | ✅ Pass |
| Terminology canon | "Mission", never "feature"; pre-push terminology guard runs on doctrine/prose. | ✅ Pass |
No violations. Layer-boundary note: the mission-type canonicalizer (IC-02) must live where both charter and specify_cli may consume it without charter → specify_cli import (C-001).
Project Structure
Documentation (this mission)
kitty-specs/mission-type-doctrine-authority-01KXH6GE/
├── plan.md # This file
├── research.md # Phase 0 output (design decisions, resolved)
├── data-model.md # Phase 1 output (entities/contracts of the resolver bundle)
├── quickstart.md # Phase 1 output (how to verify behaviour)
├── contracts/ # Phase 1 output (resolver / overlay / dossier / enforcement contracts)
└── tasks.md # Phase 2 output (/spec-kitty.tasks — NOT created here)
Source Code (repository root)
src/
├── charter/
│ ├── mission_type_profiles.py # resolver seam (resolve_mission_type_context, ResolvedMissionType/Governance), canonicalizer home
│ ├── context.py # action-path leak closure (_load_action_doctrine_bundle), thread mission_type
│ ├── scope_router.py # thread feature_dir/mission_type
│ └── mission_type_repository / base.py (doctrine/) # overlay adapter for per-type override
├── doctrine/
│ ├── base.py # builtin→org→project overlay stack (override adapter)
│ ├── missions/
│ │ ├── models.py # MissionType model (retire governance_refs)
│ │ ├── mission_types/*.yaml # MissionType artefacts (fix danglers)
│ │ ├── {documentation,research,plan}/governance-profile.yaml # authored governance (Q1: referenced sibling)
│ │ ├── {…}/actions/*/index.yaml # action-grain governance
│ │ ├── repository.py # get_expected_artifacts (dossier reader target)
│ │ └── */*.styleguide.yaml, */*.procedure.yaml # 6–8 net-new artifacts
│ └── graph.yaml # regenerated after authoring
├── runtime/next/
│ └── prompt_builder.py # Surface B consumer → resolver
└── specify_cli/
├── mission.py # get_mission_type (remove sw-dev governance default)
└── dossier/manifest.py # gate reader flip (detachable lane) + adapter
tests/
├── doctrine/… # enduring doctrine-module tests (resolution, non-leakage, determinism)
├── integration/… # enduring integration tests (real mission resolution)
└── (transitional parity scaffolds, deleted before merge)
Structure Decision: Single Python package. No new top-level package; work lands in the existing charter, doctrine, runtime.next, and specify_cli.dossier subsystems plus doctrine YAML. Enduring tests split doctrine-module (unit) vs integration; transitional parity scaffolds are removed at merge.
Complexity Tracking
No Charter Check violations to justify. The one deliberate complexity note: the resolver (resolve_mission_type_context) unifies two distinct hard-fail policies and multiple slots — it MUST be kept ≤ 15 complexity via extracted private helpers (_resolve_type_key, _resolve_governance_slot, _resolve_action_slot), not a flat function. Tracked as an implementation constraint on IC-03, not a charter violation.
Implementation Concern Map
> Concerns are NOT work packages. /spec-kitty.tasks translates these into WPs > (IC-06 will fan out to three content WPs; IC-07/IC-08 carry transitional parity > scaffolds). Do not read IC ordering as WP sequencing beyond the depends-on edges.
Cross-cutting notes (post-plan squad reconciliation)
- Dependency chain (corrected):
IC-01(independent tidy) ·IC-02 → IC-03 → {IC-04, IC-05, IC-06, IC-08} → IC-09·IC-07is an independent, detachable, non-blocking lane with an internal hard edge (reconcile → migrate); IC-09 does NOT depend on IC-07. - Two distinct models — not a collision.
MissionType(the artefact model,src/doctrine/missions/models.py) is edited by IC-01;MissionTypeProfile(the governance-profile model,src/charter/mission_type_profiles.py) is edited by IC-05. Different files, different layers → nomodels.pymerge collision (an earlier note calling this a collision was wrong). The existingdoctrine/missions/mission_type_repository.py::MissionTypeRepositoryloads the artefact model and must not be confused with the newMissionTypeProfileoverlay repo (IC-05). - Canonicalizer census (IC-02 owns):
src/specify_cli/mission.pyhas multiplesoftware-dev-defaulting sinks —get_mission_type:575,get_deliverables_path:605, and the deadget_mission_key:548(no live src callers → retire).get_mission_typehas ~13 live callers; each must be classified (hasmeta.json→ unaffected; typeless → neutral degrade, never software-dev;next_cmd.py:658documents reliance on the default). The census enumerates and routes all sinks/callers, or scopes specific ones OUT with rationale — a partial fix leaves the split half-closed. - Subsume-not-add (IC-03 owns): the three subsumed functions (
resolve_action_sequence,resolve_mission_type_governance,load_profile) are incharter/__init__.py __all__and have 8 live callers (5 FSM/runtime + 3 CLI). WP-SEAM migrates all 8 and dispositions the exports — it must not addresolve_mission_type_contextbeside the old three. id == mission_typeinvariant (IC-05 ↔ IC-06): riding the overlay means everygovernance-profile.yaml(software-dev + the 3 authored) gains anidequal to itsmission_type(extra="forbid"); the overlay repo subclass lives incharter/(layer rule:doctrine ↛ charter).- Terminal DRG regenerate (join-owned): IC-01 (dangler removal) and IC-06 (authoring) both touch
graph.yaml; the finalregenerate-graph --checkruns once, after both land (assigned to the IC-09 join), so neither clobbers the other's freshness. Run the terminology guard before push (CI-only gate). - Relate-to (no fold): #2532 (context.py god-module — IC-04's dead-pair deletion is a ~100-LOC down-payment, do not grow into the decompose) · #2628 (source of the IC-07 reconcile deltas) · #832 / #1755 (overlay + DRG regen precedent, cite) · #1740, #901 (adjacent/parent, OUT). Per the #883 partial-close: no auto-close keyword.
IC-01 — Retire the inert governance_refs field
- Purpose: Remove the dead, dangling per-type
governance_refsfield so the artefact's governance resolves only through the live path (no danglers). - Relevant requirements: FR-010.
- Affected surfaces:
src/doctrine/missions/models.py(MissionType),src/specify_cli/cli/commands/mission_type.py(display), all foursrc/doctrine/missions/mission_types/{software-dev,documentation,research,plan}.yaml(each carriesgovernance_refs: [];MissionTypeisextra="forbid", so leaving any unedited reds every load), plus the danglingDIR-010/011insoftware-dev.yaml; thedrg.py:169comment; tests asserting the field. - Sequencing/depends-on: none (tidy-first; independent). No
models.pycollision with IC-05 (that edits a different model incharter/— see cross-cutting notes). - Risks: missing one of the four YAMLs reds all MissionType loads; DRG parity guard re-point; update (not preserve) the ~3–4 tests that asserted the field.
IC-02 — Single mission-type canonicalizer (boundary-safe)
- Purpose: One canonicalizer for the mission-type key consumed by both
charterandspecify_cli, removing thesoftware-devgovernance default; closes the leak on the governance/dossier path. - Relevant requirements: FR-012, FR-001, FR-003, FR-003a, C-001.
- Affected surfaces: a boundary-safe canonicalizer module both layers may import (respecting
charter ↛ specify_cli; placement legal becausespecify_cli → charteris allowed),charter/mission_type_profiles.py(read at:380), and thespecify_cli/mission.pysinks —get_mission_type:575(remove sw-dev default),get_deliverables_path:605, and the deadget_mission_key:548(retire). Caller census: classify the ~13 liveget_mission_typecallers (runtime_bridge.py:1232,2244,mission_runtime/resolution.py:946,next_cmd.py:665[note:658documents default reliance],research.py:102,tasks_parsing_validation.py:982,mission_setup_plan.py:678,sync/dossier_pipeline.py:250,workflow_executor.py:564,mission.py:768, …) — has-meta.json→ unaffected; typeless → neutral degrade. - Sequencing/depends-on: none (Lane-B root; precedes IC-03/IC-04).
- Risks: layer-rule violation if placed wrong; partial census leaves the split half-closed; converting a silent-software-dev default into a silent-empty/crash at an un-adjudicated caller; the degrade branch (FR-003a) must be per-entry, not a blanket hard-error.
IC-03 — Unified resolver seam (ResolvedMissionType bundle)
- Purpose: One charter-mediated
resolve_mission_type_context(repo_root, *, mission_type=None, feature_dir=None) → ResolvedMissionTypethat both consumers converge on; governance is ordered + structured; type-grain (referencedgovernance-profile.yaml) ∪ action-grain with a URN-normalized disjointness guard. - Relevant requirements: FR-006, FR-013, FR-004, FR-003 (hard-error raised in the resolver), FR-009 (software-dev resolved as a peer, behaviour preserved), NFR-007, NFR-001 (transitional governance parity scaffold); Q1/Q3.
- Affected surfaces:
src/charter/mission_type_profiles.py(new bundle + resolver, subsumingresolve_action_sequence:266/resolve_mission_type_governance:325/load_profile:175) — migrate all 7 live call-sites (4 runtime:runtime_bridge_composition.py:186,319,decision.py:601,prompt_builder.py:346; 3 CLI:cli/commands/mission_type.py:1477,charter/activate.py:152,charter/mission_type.py:82;load_profilehas 0 callers — only the__all__export) and disposition thecharter/__init__.py __all__exports + the stale docstring atcharter/resolver.py:299(subsume-and-migrate, not add-beside). The bundle'sexpected_artifactsslot is populated by IC-07 (after upward reconcile), not here. - Sequencing/depends-on: IC-02.
- Risks: leaving any of the 8 callers on the old functions → a third resolution path entrenched; complexity ceiling (extract
_resolve_type_key/_resolve_governance_slot/_resolve_action_slot); preserve the two hard-fail policies as explicit branches; transitional byte-parity scaffold for software-dev (deleted after).
IC-04 — Action-path leak closure
- Purpose: Rewire the live action-doctrine path off
template_setinference ontometa.json mission_type; delete the dead_render_action_scoped/_append_action_doctrine_linespair; splittemplate_set. - Relevant requirements: FR-002, FR-001, FR-003a, C-004.
- Affected surfaces:
src/charter/context.py(_load_action_doctrine_bundle:865, reached viabuild_charter_context:252+build_charter_context_json:3254; the second stale default at:1465lives inside the dead pair to delete),src/charter/scope_router.py:66(threadfeature_dir/mission_type). Deleting the dead pair (_render_action_scoped:1500/_append_action_doctrine_lines:1451, ~100 LOC) orphanstests/charter/test_context.py:716— delete that test in the same WP (it covers dead code). Do not delete_filter_references_for_action(survives via the live caller atcontext.py:1065). - Sequencing/depends-on: IC-03.
- Risks: ~6
build_charter_contextcallers + ~36 test files updated (not shielded); per-entry degrade for the two mission-less callers (executor.py:270,workflow.py:675); RED-first behavioural test through a shared action name. Campsite: OUT → thecontext.pygod-module decompose is #2532 (3266 LOC, 45 pre-existing suppressions) — this IC is a ~100-LOC down-payment only; do not grow into the decompose.
IC-05 — Per-type project override channel
- Purpose: A project can override a mission type's governance, ridden through the existing
doctrine/base.pyoverlay stack (builtin → org → project + collision warnings). - Relevant requirements: FR-011, C-005 (Q2).
- Affected surfaces:
src/doctrine/base.pyoverlay (keys onid, skips id-less files at:249-256),MissionTypeProfileinsrc/charter/mission_type_profiles.py(addid; currently keyed onmission_type,extra="forbid"), and aBaseDoctrineRepository[MissionTypeProfile]subclass that must live incharter/(layer rule:doctrine ↛ charter; the base is importable charter→doctrine). Do not reuse/confuse the existingdoctrine/missions/mission_type_repository.py::MissionTypeRepository(loads the artefact model). - Sequencing/depends-on: IC-03. No collision with IC-01 (different model/file — see cross-cutting notes).
- Risks:
id == mission_typeinvariant — everygovernance-profile.yaml(software-dev + the 3 authored by IC-06) must addidequal tomission_typeor field-merge mis-keys silently (binds IC-05 ↔ IC-06); overlay keys onid(the real adapter cost); layer-rule trip if the subclass is placed underdoctrine/.
IC-06 — Author non-software governance (documentation / research / plan)
- Purpose: Populate the three non-software governance sets (type-grain profile + action-grain index) and author the 6–8 net-new DRG-resolvable artifacts.
- Relevant requirements: FR-005, SC-004; and FR-002/NFR-006 (content makes non-leakage non-vacuous).
- Affected surfaces:
src/doctrine/missions/{documentation,research,plan}/governance-profile.yaml+actions/*/index.yaml; net-new styleguides (Divio-type, plain-language, accessibility, publication, freshness-SLA), a research citation-discipline artifact, the missingspike-timebox-policyprocedure;src/doctrine/graph.yamlregenerate + freshness. - Sequencing/depends-on: IC-03 (schema stable) and the IC-05
idinvariant (each authoredgovernance-profile.yamlmust carryid == mission_type). Fans out to 3 WPs, unevenly sized (documentation ≈ 5 styleguides is materially heavier than research/plan) — size off the artifact inventory, not equal thirds. Parallel to IC-04. - Risks: undersizing (import the ADR artifact inventory, not the requirement count); DRG freshness gate; terminology guard. Campsite bonus: authoring
spike-timebox-policycloses a pre-existing dangler (researcher-robbie.agent.yaml:60references a file that does not exist).
IC-07 — Gates/dossier swap (detachable, non-blocking lane)
- Purpose: Reconcile the drifted
expected-artifacts.yamlupward into the doctrine tree, adaptConfigResult → ExpectedArtifactManifest, flip the dossier reader onto the doctrine tree, delete thespecify_clicopies — proving reduced dependence. - Relevant requirements: FR-007, NFR-004, SC-005, NFR-001 (dossier parity scaffold). Also owns the
ResolvedMissionType.expected_artifactsslot population (after reconcile) so the seam bundle never reads an un-reconciled tree. - Affected surfaces:
src/specify_cli/dossier/manifest.py:178(reader flip;load_manifest),src/doctrine/missions/repository.py:304(get_expected_artifacts→ConfigResult; adapterExpectedArtifactManifest.model_validate(config_result.parsed)— nofrom_dicttoday, + cache preserve), the 5 consumer sites (dossier/indexer.py:77,130,307,359,sync/namespace.py:98), and the twoexpected-artifacts.yamltrees. - Reconcile deltas (upward, WP-GATES-RECONCILE): port all of the
specify_cli-ahead deltas into the doctrine tree —runtime.charter-lint.decay→lint-report.json,blocking: false, and theoccurrence_map.yamlbulk-edit NOTE comment block (behaviourally load-bearing docs). Source: #2628. - Sequencing/depends-on: WP-GATES-RECONCILE before the flip (hard edge). Independent, detachable lane — NON-blocking for IC-09; final flip may defer to slice 2 on deep drift (reconciliation still lands, deferral recorded, never silent).
- Risks: type-boundary crossing (adapter + cache); already-drifted content; ~29
load_manifestassertions intests/dossier/test_manifest.pyshift the moment the reader flips — the reconcile-before-flip edge + transitional parity scaffold keep them green; transitional dossier-parity scaffold deleted after.
IC-08 — Steps swap (step-contract resolution through the artefact)
- Purpose: Route step-contract resolution through the MissionType artefact bundle; migrate the
specify_clistep-contract readers. - Relevant requirements: FR-008, SC-007, NFR-001 (steps parity scaffold); Q3.
- Affected surfaces: step-contract resolution in
runtime/doctrine;specify_clistep-contract readers (WPs must pin exact anchors); thestep_contractsbundle slot. - Sequencing/depends-on: IC-03. Same transitional-parity-then-delete discipline.
- Risks: preserve software-dev step behaviour; migrate readers off
specify_cli.
IC-09 — Enforcement + test posture
- Purpose: The enduring behavioural guards: non-leakage (URN-normalized denylist) + non-vacuity twin (shared action name), deterministic-ordering test; delete all transitional parity scaffolds.
- Relevant requirements: NFR-005, NFR-006, NFR-007, SC-001/002/003/006.
- Affected surfaces:
tests/doctrine/…(module) +tests/integration/…; removal of transitional scaffolds; the terminalgraph.yamlregenerate-graph --check(runs once, after IC-01 + IC-06 both land). - Sequencing/depends-on: IC-03, IC-04, IC-06 (+ IC-08 for steps enforcement). Explicitly EXCLUDES IC-07 — the enduring governance guards need the seam + leak-closure + content, not the detachable dossier flip; making the enforcement join depend on IC-07 would gate the mission/merge on a deliberately non-blocking lane (violating FR-007/NFR-004).
- Risks: vacuity (twin must fire through a shared action name); keep enduring tests behavioural, not code-shape ratchets; the terminal DRG regenerate must be the single owner so IC-01/IC-06 don't clobber freshness.