Mission Specification: Missions/ Doctrine Tree Relocation & Gate Preconditions
Mission Branch: research/doctrine-wheel-mission-types-public-api Created: 2026-08-04 Status: Draft Input: User description: "Doctrine consumer-surface contract & missions/ extraction — ... Full relocation deliverable: #3091 ... Preconditions: #3039, #3036 ... Bundled fixes: #3183, #3182 ..." — narrowed 2026-08-04 after a post-spec adversarial squad and an explicit operator scope-split decision (see Split decision below): the public-API contract thread (#3179) is carved out to a separate follow-on mission. This mission scopes only the missions/ relocation, its two CI-gate preconditions, and the two small bundled fixes.
Change mode: bulk_edit — this mission relocates the missions/ doctrine tree (src/doctrine/missions → packs/built-in/missions, a structural path move) and repoints every identified reader onto the new location. occurrence_map.yaml, including a moves: block for the relocation, is produced during /spec-kitty.plan and must be approved before implementation.
Post-plan squad addendum (2026-08-04)
A 4-lens post-plan review (architect-alphonso, debugger-debbie, planner-priti, doctrine-daphne) of plan.md/research.md found further gaps, now fixed in this spec:
1. Stale file path. src/doctrine/agent_profiles/built-in/doctrine-daphne.agent.yaml (cited throughout the earlier draft) no longer exists — the relocate-builtin-doctrine-packs-01KYT87F mission moved it to packs/built-in/agent_profiles/doctrine-daphne.agent.yaml three days before this spec was written. Every reference below uses the current path. 2. Two more resolvers this mission must converge, not just kernel + doctrine. doctrine.missions.repository.MissionTemplateRepository.default_missions_root() is the already-promoted authority two other sites (charter.mission_type_profile_repository.builtin_missions_root(), specify_cli.runtime.home.get_package_asset_root()'s dev_roots fallback) were retargeted onto by the sole-door mission's own WP06 — whose test docstring (tests/charter/test_missions_root_authority.py:19-22) explicitly states "Full convergence onto doctrine.pack_paths.built_in_dir remains deferred to GitHub issue #3091" — i.e. this mission. And src/doctrine/drg/migration/extractor.py::_missions_root() is a DRG-specific resolver whose own docstring asserts "Missions were not relocated... they still live inside the doctrine package" — an assumption this mission's relocation directly falsifies, and which feeds the DRG-generated mission_type.graph.yaml/mission_step_contract.graph.yaml fragments. 3. src/doctrine/missions/ mixes a Python package with data content — the relocation must not conflate them. The directory holds 11 .py logic modules (repository.py, mission_type_repository.py, step_contracts.py, etc.) alongside data subdirectories (mission_types/, built_in_step_contracts/, mission-steps/, per-type templates//actions/). packs/built-in/ cannot host Python modules (a hyphenated name is not a legal package, and every sibling kind directory ships YAML/MD only). Only the data content relocates; the .py modules stay in src/doctrine/missions/ as an ordinary Python package, repointed to read data from the new external root. 4. The relocate-and-repoint split was unsafe. The original FR-005 (relocate) / FR-006 (repoint) split left a genuinely broken intermediate state — readers still referencing a deleted path — violating NFR-001 and contradicting the bulk-edit occurrence_map.yaml's own single-diff treatment of the move. Merged into one requirement, landed as one atomically-reviewed change. 5. Smaller fixes: doctrine_package_dir() survives unmodified as its own public, test-pinned symbol (FR-004 replaces only its caller inside _resolve_built_in's step 3); the kernel primitive's own exception type must be translated back to PackRootNotFound at the pack_paths.py boundary (a real consumer catches that specific type); the gate-file split (FR-001) must explicitly relocate the shared scan helpers (Site, _rel, _read_lines, _text_files, the root constants) rather than leaving their landing spot undecided.
Split decision (2026-08-04)
The original draft of this spec bundled five user stories: a public-API contract for doctrine/charter (#3179), the missions/ relocation (#3091), two CI-gate preconditions (#3039/#3036), and two small bundled fixes (#3183/#3182). A 4-lens post-spec adversarial squad (architect-alphonso, debugger-debbie, planner-priti, reviewer-renata) found, among other corrections (see checklists/requirements.md for the full list):
- The
missions/relocation is more architecturally involved than originally scoped —src/kernel/paths.pyhardcodes doctrine mission-type vocabulary and bothsrc/doctrine/missions/src/specify_cli/missionspath shapes, not just the onefiles("doctrine")call the draft named, and no existing architectural gate actually covers a kernel→doctrine import edge. - This same territory (
missions/relocation) was already deferred once before by therelocate-builtin-doctrine-packs-01KYT87Fmission's own post-plan adversarial squad, for exactly this four-layer-reader complexity. - The public-API contract thread (#3179) does not strictly gate the relocation — the project's own research trail states this explicitly (
docs/plans/doctrine/next-slice-wheel-mission-types-public-api-research.md, "(b) is not strictly gating (a)").
Given that convergent evidence, the operator elected to split into two missions, relocation first: this mission de-risks the already-deferred, harder piece; the public-API contract (#3179, the original User Story 1) is specced as its own follow-on mission once this one is underway or landed.
Background
The project's own docs/plans/3-2-x-open-core-delivery-plan.md declares a load-bearing sequencing rule for the 3.2.x doctrine rework: boundary → sole-door → contract → schema, landing out of order is named as "the one way the strategy fails." The charter-sole-door-bypass-closure-01KZ3WAA mission (PR #3175, closing #3176) landed the sole-door step. This mission completes the missions/ relocation (#3091) that both the eventual wheel cutover and the still-pending contract step need settled first, clearing two named CI-gate preconditions and two small bundled defects surfaced along the way. The contract step itself (a versioned public API for doctrine/charter, #3179) is a separate, adjacent-but-not-gating follow-on mission.
Full research trail: docs/plans/doctrine/next-slice-wheel-mission-types-public-api-research.md.
User Scenarios & Testing (mandatory)
User Story 1 - missions/ doctrine tree relocated to packs/built-in (Priority: P1)
As a Spec Kitty maintainer, I want the missions/ doctrine tree (mission types, step contracts, templates) to live in packs/built-in/missions/ alongside every other built-in doctrine kind (agent_profiles/, directives/, tactics/, ...), instead of inside src/doctrine/, so mission-type content is shippable the same way the rest of built-in doctrine already is.
Why this priority: Named by the ADR and the delivery plan as changing what the doctrine wheel must carry — a precondition for the deferred wheel cutover — and the piece this mission exists to de-risk, including whatever src/kernel changes make the move sound.
Independent Test: src/doctrine/missions/ no longer exists on disk; a mission-type resolution request (e.g., spec-kitty init listing available mission types) resolves its content correctly from packs/built-in/missions/ in both an editable checkout and a built wheel.
Acceptance Scenarios:
1. Given the cross-layer reader inventory (doctrine, kernel, charter, specify_cli, upgrade migrations, and the two newly-identified resolvers — MissionTemplateRepository.default_missions_root() and the DRG extractor's _missions_root()) required before any move, When the inventory is complete, Then every reader of missions/ content has an explicit move/stay/repoint decision recorded in a committed inventory artifact (see FR-003), and the inventory explicitly separates the .py logic modules (which stay in src/doctrine/missions/) from the data subdirectories (which move). 2. Given src/kernel/paths.py's current direct files("doctrine") lookup and its _looks_like_missions_root/_resolve_env_root helpers, which already hardcode doctrine's mission-type vocabulary ("software-dev", "documentation", ...) and both the src/doctrine/missions and src/specify_cli/missions path shapes (the full C-004 violation, not just the one resource-loader call), When the kernel-owned sibling-path-resolution primitive is extracted, Then src/kernel/ holds no doctrine- or specify_cli-domain-identifying string or vocabulary at any point — including the interim state before missions/ physically moves — and the primitive's own tests (not the existing charter/doctrine-wheel gates, which do not cover this edge) prove it. 3. Given the extracted, domain-agnostic primitive, When missions/ data content moves to packs/built-in/missions/, Then every identified reader — including MissionTemplateRepository.default_missions_root() (converged onto the same primitive) and the DRG extractor (repointed, with mission_type.graph.yaml/mission_step_contract.graph.yaml regenerated and diffed against committed state) — is repointed onto the new location in the same atomically-reviewed change, and no reader silently retains a stale path.
User Story 2 - Legitimate built-in-doctrine content cleanup is not blocked by a self-contradicting gate (Priority: P1)
As a maintainer relocating or cleaning up built-in doctrine content (as this mission's missions/ move does), I want tests/architectural/test_no_dead_doctrine_paths.py to permit removing a repo-coupled reference instead of requiring it to exist, and to stop conflating its differently-scoped gates, so the relocation work in User Story 1 does not trip a gate that currently enforces the exact coupling the "Shippable doctrine" rule forbids.
Why this priority: Both #3039 and #3036 name themselves as required before any legitimate doctrine-content extraction — a direct, named precondition for User Story 1, not incidental cleanup.
Independent Test: Remove a repo-local, repo-tree-only reference (e.g., a src/doctrine/graph.yaml mention in an avoidance-boundary field, or a relative markdown link that escapes the built-in-doctrine package) from built-in doctrine content; the test suite passes (fewer/no forbidding-mention or unresolvable-cross-link sites), where today it fails — and a planted violation still reds the gate (see AS2).
Acceptance Scenarios:
1. Given test_no_dead_doctrine_paths.py's four gates — Gate A (scan_graph_monolith_paths) and Gate B (scan_shipped_pack_paths), both src/-wide scans not scoped to doctrine content, Gate C (scan_doctrine_cross_links, the only gate actually scoped to src/doctrine/), and Gate D (the docs/-scanning pre-move-path check) — When the module is split, Then the split groups by actual current scope (A+B together as the CLI/shipped-wide pair; C separately as the doctrine-content gate; D explicitly assigned a named landing module, not left in whichever module is convenient), with the existing NFR-002 discriminator-proof discipline preserved unchanged across the split, and no gate's scan root is narrowed as a side effect of the file move. 2. Given the exact-list pinning in test_forbidding_mention_would_false_red_without_its_discriminator — an NFR-002 proof whose job is to demonstrate discriminator A2 is real, not an ordinary assertion — When the fix lands, Then it decouples the proof from the live shipped artifact (packs/built-in/agent_profiles/doctrine-daphne.agent.yaml, its current post-relocation path — the pin's own live assertion already reads ("packs/built-in/agent_profiles/doctrine-daphne.agent.yaml", "src/doctrine/graph.yaml")) onto a planted synthetic fixture carrying a forbidding mention, so the discriminator's effect-set pin still runs (and still catches an unexpected new exclusion — widening remains a visible diff against the fixture) while the live shipped artifact itself is freed to drop the repo-local reference. Loosening the live assertion to tolerate an empty effect set is an explicitly rejected shape (issue #3036's own tracker record) and must not be the implementation. 3. Given Gate C's on-disk cross-link resolution requirement, When a relative link legitimately points outside the built-in-doctrine package boundary, Then it is explicitly exempted rather than perpetually required to resolve on this repo's own disk, using the same fixture-decoupling principle as AS2 rather than a bare loosened assertion.
User Story 3 - Mission-type activation errors say what they mean (Priority: P2)
As an operator or agent debugging a custom activated_mission_types entry with no loadable profile, I want the error message to distinguish "activated in config" from "has a loadable profile" so I am not told in the same sentence that a mission type is both unknown and registered.
Why this priority: A real, reproducible defect (#3183) in the exact activation/availability vocabulary this mission's relocation work touches; small and self-contained.
Independent Test: Configure activated_mission_types: [my-custom] with no resolvable MissionTypeProfile on disk; the resulting error message no longer states the id is both unknown and registered. The existing tests/charter/test_mission_type_profiles.py::test_unknown_type_raises_unknown_mission_type_error test does not exercise this scenario (it only covers an id absent from existing_mission_types()) — a new red-first reproduction of the actual activated-but-unresolvable case must be added before the fix, per this repo's ATDD-first directive (C-011).
Acceptance Scenarios:
1. Given an activated mission-type id with no loadable profile, When resolve_mission_type_context() raises, Then the message names the two distinct facts separately (e.g., "activated but has no loadable profile") rather than contradicting itself.
User Story 4 - Dogfood command-template overrides model the canonical construction path (Priority: P3)
As a contributor relying on this repo's own TIER-1 command-template overrides (implement.md, review.md), I want them to demonstrate the canonical build_activation_aware_doctrine_service construction path and reference only commands that still exist, so agents dogfooding this repo are not taught the pattern the sole-door mission just banned.
Why this priority: Small, campsite-scoped cleanup; independent of the other stories' delivery, folded in here rather than left to wait for the follow-on public-API mission.
Independent Test: Read .kittify/overrides/missions/software-dev/command-templates/implement.md and review.md; neither references raw AgentProfileRepository(...)/DoctrineService(...) construction or the retired spec-kitty constitution context command.
Acceptance Scenarios:
1. Given the two stale override files, When they are refreshed against the current canonical templates, Then each either matches the canonical construction pattern or is deleted if it no longer diverges usefully from the canonical template.
Edge Cases
- How does the
missions/relocation handle a project's own project-tier or org-tier doctrine pack that references the oldsrc/doctrine/missionslayout in its own content? Out of scope to migrate consumer projects in this mission; the resolver-primitive change must not break the existing project/org tier resolution contract, which already takes a caller-supplied root. - What happens if the Gate A/B vs C/D split (User Story 2) reveals a currently-passing assertion that silently depended on the scopes being combined? Any such coupling must be surfaced and resolved explicitly, not carried forward as an implicit dependency between the split modules.
- What happens to a currently-open PR or in-flight mission that reads
src/doctrine/missionsdirectly? Out of scope to coordinate with in-flight work; this mission migratesmain's own call sites only. (Verified during post-plan review: no open PR or in-flight branch touches any of this mission's target files.) - What happens to the DRG-generated fragments (
packs/built-in/mission_type.graph.yaml,packs/built-in/mission_step_contract.graph.yaml) once_missions_root()is repointed? They must be regenerated (spec-kitty doctrine regenerate-graphor equivalent) and diffed against committed state as part of the same change —tests/doctrine/drg/test_regen_roundtrip.pyalready asserts byte-identical regeneration and will fail loudly if this is missed, but the regeneration step itself must be a stated part of the work, not discovered only via that test's failure.
Sequencing note (binding)
FR-003 through FR-005 (the missions/ relocation) are blocked by FR-001 and FR-002 landing first — both name themselves in their own tracker issues (#3039, #3036) as required "before any legitimate doctrine-content extraction." FR-004 (the kernel primitive) has no upstream dependency and runs in parallel with FR-001/FR-002; FR-005 requires both FR-003 (inventory) and FR-004 (primitive) complete — a fork/join, not a strict chain. /spec-kitty.tasks must encode this as real multi-parent work-package dependency edges, not infer it from User Story prose alone.
Requirements (mandatory)
Functional Requirements
| ID | Title | User Story | Priority | Status |
|---|---|---|---|---|
| FR-001 | Split test_no_dead_doctrine_paths.py by actual scope, naming all four gates, relocating shared helpers | As a maintainer, I want Gate A + Gate B (both src/-wide, not doctrine-scoped) split from Gate C (the only gate actually scoped to src/doctrine/), with Gate D (docs/-scanning) given its own explicitly-named landing module, preserving the existing NFR-002 discriminator-proof discipline unchanged. The shared scanning helpers (Site dataclass, _rel, _read_lines, _text_files, and the _REPO_ROOT/_SRC_ROOT/_DOCTRINE_ROOT/_PACKS_ROOT/_TEXT_SUFFIXES constants — currently used by Gates A, B, and C) must land in a new shared underscore-prefixed helper module both post-split modules import, matching this directory's existing convention (_gate_coverage.py, _sole_door_scan.py), not be duplicated. Blocking dependency: none upstream; blocks FR-003–FR-005. | High | Open |
| FR-002 | Decouple the NFR-002 proof onto a synthetic fixture, not a loosened assertion | As a maintainer, I want test_forbidding_mention_would_false_red_without_its_discriminator (and Gate C's analogous case) redriven from a planted synthetic fixture rather than the live shipped artifact, per issue #3036's own recorded design decision — so the effect-set pin stays provable while the live shipped artifact becomes free of the repo-local reference. Explicitly NOT an empty-tolerant loosening of the live assertion — that shape is a recorded rejected remedy. Scope includes the "daphne cleanup" itself (removing the src/doctrine/graph.yaml mention from packs/built-in/agent_profiles/doctrine-daphne.agent.yaml's avoidance-boundary, at that file's line 136), since #3036's own comment says the fix "wants to land with the daphne cleanup it enables." Blocking dependency: FR-001 (module split lands first); blocks FR-003–FR-005. | High | Open |
| FR-003 | Cross-layer missions/ reader inventory, method: symbol-tracing not path-grepping | As a maintainer, I want every reader of src/doctrine/missions/ content — across doctrine, kernel, charter, specify_cli, and upgrade migrations — enumerated with an explicit move/stay/repoint decision, recorded as a committed inventory artifact at docs/plans/doctrine/missions-reader-inventory-01KZ6G6H.md (outside kitty-specs/, because finalize-tasks currently rejects any WP owned_files entry under kitty-specs/ — a known CLI gap tracked at issue #2643), found by tracing every symbol imported from doctrine.missions. to its implementation (a bare path-literal grep can miss a load-bearing call reached only via an unrelated comment, as it did during this mission's own post-plan review). The inventory must explicitly include three sites already identified: (1) doctrine.missions.repository.MissionTemplateRepository.default_missions_root() — the already-promoted authority two other sites were retargeted onto by the sole-door mission's WP06, whose own test (tests/charter/test_missions_root_authority.py:19-22) explicitly defers "full convergence... to GitHub issue #3091"* (this mission); (2) src/doctrine/drg/migration/extractor.py::_missions_root(), whose docstring's "missions were not relocated" assumption this mission falsifies; (3) the .py-vs-data-content split within src/doctrine/missions/ itself — the 11 logic modules stay, only the data subdirectories move. Blocking dependency: FR-001, FR-002. | High | Open |
| FR-004 | Extract a kernel-owned, domain-agnostic sibling-path-resolution primitive; converge pack_paths.py and MissionTemplateRepository.default_missions_root() onto it | As a maintainer, I want src/kernel/paths.py's direct files("doctrine") lookup and its _looks_like_missions_root/_resolve_env_root helpers (which hardcode doctrine's mission-type vocabulary and both the src/doctrine/missions/src/specify_cli/missions path shapes) replaced by a primitive that holds no doctrine- or specify_cli-identifying string or vocabulary at any point, verified by a new kernel-scoped architectural test (the existing test_charter_no_specify_cli_import.py/test_doctrine_wheel_closure.py do not cover this edge, and the pre-existing test_kernel_does_not_import_doctrine pytestarch rule is already vacuous against this exact string-based lookup). Decided during planning (DM-01KZ6JH5): converge now — doctrine/pack_paths.py::_resolve_built_in and doctrine.missions.repository.MissionTemplateRepository.default_missions_root() (per FR-003's finding) both delegate to the new kernel primitive in this same mission. doctrine_package_dir() survives unmodified as its own public, test-pinned symbol (tests/doctrine/test_built_in_location_authority.py asserts its identity; only its caller inside _resolve_built_in's installed-wheel step changes) — this FR does not retire it. The primitive necessarily raises its own exception type (kernel cannot import doctrine.pack_paths.PackRootNotFound); pack_paths.py's call site must catch-and-re-raise as PackRootNotFound so existing consumers (e.g. specify_cli/doctrine/pack_validator.py's except (PackRootNotFound, BuiltInContentDirNotAvailable)) are not silently broken. Blocking dependency: none; independent of FR-001/FR-002, feeds FR-005. | High | Open |
| FR-005 | Relocate missions/ data content and repoint every reader — one atomic change | As a maintainer, I want the missions/ doctrine tree's data subdirectories (not its .py logic modules, which stay in src/doctrine/missions/) physically moved from src/doctrine/missions to packs/built-in/missions, and every reader FR-003 identified (doctrine's .py modules, kernel, charter, specify_cli, upgrade migrations, MissionTemplateRepository.default_missions_root(), and the DRG extractor) repointed onto the new location, landed as one atomically-reviewed change — not two separable increments — since an intermediate move-without-repoint state is a genuinely broken build (NFR-001), and the bulk-edit occurrence_map.yaml's own moves: block already treats this as one diff. Includes regenerating packs/built-in/mission_type.graph.yaml/mission_step_contract.graph.yaml and diffing against committed state. Blocking dependency: FR-003 and FR-004 (fork/join — both required, not a strict chain). | High | Open |
| FR-006 | Fix the UnknownMissionTypeError message contradiction | As an operator/agent, I want the error message to state separately that a mission-type id is activated but has no loadable profile, instead of calling it both unknown and registered. A red-first reproduction test for the actual activated-but-unresolvable scenario must exist first (the existing test_unknown_type_raises_unknown_mission_type_error test does not cover it). | Medium | Open |
| FR-007 | Refresh the stale TIER-1 override templates | As a contributor, I want .kittify/overrides/missions/software-dev/command-templates/implement.md and review.md rebased on the current canonical templates (correct construction pattern, no reference to the retired constitution context command) or deleted if they no longer diverge usefully. | Low | Open |
Non-Functional Requirements
| ID | Title | Requirement | Category | Priority | Status |
|---|---|---|---|---|---|
| NFR-001 | No behavior regression from the relocation and repoint | FR-005's move+repoint is a pure content-location change; the full existing test suite (tests/charter, tests/doctrine, tests/architectural, tests/doctrine/drg) passes unchanged except pre-existing baseline-red pins (per the repo's own baseline-red gotcha policy). Specifically includes tests/doctrine/drg/test_regen_roundtrip.py's byte-identical regeneration assertions. | Reliability | High | Open |
| NFR-002 | Layer direction preserved through the relocation | FR-004's kernel primitive and FR-005's repoints introduce no new upward import edge. The existing tests/architectural/test_charter_no_specify_cli_import.py (scoped to src/charter/) and test_doctrine_wheel_closure.py (scoped to pyproject.toml manifest declarations) do not cover a kernel→doctrine edge and must not be cited as FR-004's proof; FR-004 requires its own new kernel-scoped AST-walk gate (the existing test_kernel_does_not_import_doctrine pytestarch rule in test_layer_rules.py is already vacuous against the string-literal files("doctrine") shape). All four gates from FR-001/FR-002 remain green throughout. | Reliability | High | Open |
| NFR-003 | Gate split preserves existing coverage | FR-001's module split must not reduce Gate A's or Gate B's src/-wide coverage, or Gate C's doctrine-content coverage, or Gate D's docs/ coverage; the same set of assertions (test_project_tier_graph_path_would_false_red_without_its_discriminator, test_gate_a_rejects_a_planted_violation, etc.) pass in their new locations, and the shared scan helpers are not duplicated across modules (see FR-001). | Reliability | Medium | Open |
| NFR-004 | Shared architectural-gate ratchet stays fresh across parallel lanes | If FR-001 and FR-004 land in parallel work-package lanes, both touch tests/architectural/_gate_coverage_baseline.json (the shared orphan/coverage ratchet). Whichever lands second must re-run its regeneration command (--update-baseline) against the merged state before that lane's own review completes. | Reliability | Low | Open |
Constraints
| ID | Title | Constraint | Category | Priority | Status |
|---|---|---|---|---|---|
| C-001 | No wheel publish in this mission | The actual kernel/doctrine/charter wheel publish (independent installable packages, independent release cadence, extended clean-install-verification CI) is explicitly out of scope, deferred to a separate future mission tracked at #3101. src/kernel, src/doctrine, src/charter remain in the root wheel's packages list as today. | Technical | High | Open |
| C-002 | Named exclusions stay deferred | #3179 (the doctrine/charter public API contract — carved out to its own follow-on mission per the Split decision above), #2986 (runtime→doctrine function-local-import boundary blind spot), #3022 (further extraction of built-in packs into a separate spec-kitty-packs-open repo), and #2468/#2652 (full mission-type-as-ArtifactKind promotion and specify_cli/missions retirement) are explicitly out of scope for this mission. | Business | High | Open |
| C-003 | Bulk-edit workflow governs the relocation | The missions/ relocation is governed by change_mode: bulk_edit; occurrence_map.yaml (all 8 categories plus a moves: block for the relocation) must be authored and approved during /spec-kitty.plan before any implementation work package claims a file this mission's scope touches. | Technical | High | Open |
Key Entities
missions/doctrine tree: split into two distinct things this mission must not conflate — the data content (mission_types/,built_in_step_contracts/,mission-steps/, per-typetemplates//actions/), which relocates topacks/built-in/missions/alongside the other built-in doctrine kind directories; and the.pylogic modules (repository.py,mission_type_repository.py,step_contracts.py, and others), which stay insrc/doctrine/missions/as an ordinary Python package, repointed to read data from the new external root (packs/built-in/cannot host Python modules).- Sibling-path-resolution primitive: the kernel-owned utility that resolves an installed-package sibling directory (e.g.,
packs/built-in/relative to an installed package) using only kernel's own self-referential anchor (e.g. its own__file__), holding no doctrine- or specify_cli-identifying string.doctrine/pack_paths.py::_resolve_built_inanddoctrine.missions.repository.MissionTemplateRepository.default_missions_root()converge onto this primitive in this mission (decided during planning, DM-01KZ6JH5, and per FR-003's finding) rather than surviving as parallel implementations.doctrine_package_dir()is a distinct, separately-public symbol that survives unchanged. - DRG-generated
missions/-derived fragments:packs/built-in/mission_type.graph.yamlandpacks/built-in/mission_step_contract.graph.yaml, produced bysrc/doctrine/drg/migration/extractor.py's_missions_root()resolver — must be regenerated and diffed against committed state once that resolver is repointed to the new content location.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
src/doctrine/missions/'s data subdirectories (mission_types/,built_in_step_contracts/,mission-steps/, per-typetemplates//actions/) no longer exist there; mission-type resolution succeeds identically frompacks/built-in/missions/in both an editable checkout and a built wheel. The.pylogic modules remain importable asdoctrine.missions.*fromsrc/doctrine/missions/, unmoved. - SC-002:
src/kernel/paths.py(including its_looks_like_missions_root/_resolve_env_roothelpers) contains no directfiles("doctrine")lookup and no doctrine- or specify_cli-identifying string or vocabulary; the same resolution is available through a kernel-owned primitive, proven by a new kernel-scoped architectural test (not the pre-existingtest_kernel_does_not_import_doctrine, which is vacuous against this shape).doctrine/pack_paths.py::_resolve_built_inandMissionTemplateRepository.default_missions_root()both delegate to that same primitive rather than duplicating its algorithm;doctrine_package_dir()remains a separate, unmodified public symbol;pack_paths.py's call site translates the primitive's own exception back toPackRootNotFound. - SC-003:
UnknownMissionTypeError's message never states that a mission-type id is both unknown and registered in the same sentence, proven by a new red-first reproduction of the actual activated-but-unresolvable case (not the existing, non-covering test). - SC-004: Neither
.kittify/overrides/missions/software-dev/command-templates/implement.mdnorreview.mdreferences a rawAgentProfileRepository/DoctrineServiceconstruction or the retiredconstitution contextcommand. - SC-005:
tests/architectural/test_no_dead_doctrine_paths.py's four gates are split by actual current scope (A+B as thesrc/-wide pair, C as the doctrine-content gate, D explicitly landed in a named module), with shared scan helpers relocated to one common module — each independently relocatable without silently dropping another's coverage. - SC-006: Removing a repo-coupled reference from built-in doctrine content (verified via the daphne-profile cleanup at
packs/built-in/agent_profiles/doctrine-daphne.agent.yamland the missions/ relocation itself) passes the architectural gate rather than failing it, and a planted violation against the new synthetic fixture still reds the same gate (the anti-widening property is preserved, not traded away). - SC-007: The cross-layer
missions/reader inventory (FR-003) exists as a committed, reviewable artifact naming doctrine, kernel, charter,specify_cli, and upgrade-migration readers explicitly — includingMissionTemplateRepository.default_missions_root()and the DRG extractor's_missions_root()by name — not asserted without evidence. - SC-008:
packs/built-in/mission_type.graph.yamlandpacks/built-in/mission_step_contract.graph.yamlare regenerated and byte-identical to their committed state after the relocation, pertests/doctrine/drg/test_regen_roundtrip.py.