Mission Specification: Doctrine Governance Fidelity

Mission Branch: mission/doctrine-governance-fidelity Created: 2026-06-27 Status: Draft Mission ID: 01KW42KY6AXQ3B6DTCTFT0XMTV Topology: lanes (no coordination branch) Input: Operator scope — issues #2156, #2166, #2153, #2082 (Charter & Doctrine governance correctness).

Summary

Three doctrine surfaces compute or read a governance signal but silently fail to honour it at the consumer surface — the defect class is "the data is present, the consumer doesn't use it." This mission closes all three, each as an independent lane:

answer but emits a hardcoded directive line instead of interpolating it.

never pass org_dirs when building the profile repository, so org / extension doctrine-pack agents are invisible to dispatch routing, to the governance context of a --profile-hinted org agent, and to the projected .claude/agents/ surface — even when the operator has activated them.

(drg/override_policy.py) has no runtime consumer — it is enforced only by an architectural test that deployed consumer repos never run — so an unsanctioned built-in override surfaces nowhere in a real project.

  • Lane A — #2153: charter generate reads the documentation_policy interview
  • Lane B — #2156 + #2166: spec-kitty dispatch (and agent-profile projection)
  • Lane C — #2082: the built-in-override governance predicate

The lanes are file-disjoint and independently testable (true MVP slices). The provisional 3-issue scope was found by the pre-planning adversarial squad to be undersized ~2–3× (#2156 is three legs, not one site; #2082 requires promoting test-local logic into production before wiring), and #2166 is not separable from #2156 — it is the projection leg of the same org_dirs omission.

User Scenarios & Testing (mandatory)

User Story 1 — Documentation policy survives charter generation (Priority: P2)

An operator runs the comprehensive charter interview and writes a detailed documentation_policy answer. They expect that policy text to appear in the generated charter alongside the other interview answers (as risk_boundaries already does).

Why this priority: P2 correctness/fidelity bug (#2153). Low blast radius (single site, single sink) but it silently discards operator input — a governance trust defect. Smallest, lowest-risk slice; lands first.

Independent Test: Seed .kittify/charter/interview/answers.yaml with a sentinel documentation_policy value, run spec-kitty charter generate --from-interview, and grep the generated charter.md Project Directives section for the sentinel. Red against current code (sentinel absent), green after the fix.

Acceptance Scenarios:

1. Given an interview answers file whose documentation_policy is "SENTINEL_DOCS: adopt Divio structure", When charter generate --from-interview runs, Then the generated charter.md Project Directives section contains the substring SENTINEL_DOCS: adopt Divio structure. 2. Given an interview answers file with no documentation_policy answer, When charter generate --from-interview runs, Then no documentation directive line is emitted (the empty-answer branch does not regress). 3. Given the same interview, When the charter is generated, Then the documentation_policy directive is rendered with the same interpolation shape as the adjacent risk_boundaries directive (label + verbatim answer).


User Story 2 — Org-pack doctrine agents participate in dispatch end-to-end (Priority: P1)

An operator installs an org / extension doctrine pack that ships agent profiles and activates one. They expect that profile to be routable by spec-kitty dispatch, to carry its governance context when invoked via --profile, and to be projected into the host agent surface (.claude/agents/) — exactly as built-in profiles are.

Why this priority: P1 (#2156 + #2166). Operators who install doctrine extension packs cannot use their agents in the single public governed dispatch surface, nor see them in their IDE — a governance-configuration gap that defeats the purpose of extension packs. Highest user-visible value.

Activation contract: An org-pack agent profile is dispatch/projection-visible when the pack is declared in .kittify/config.yaml AND the profile passes the three-state charter gate — activated_agent_profiles absent (None → all admitted, backward-compat default), or the id is in the explicit list. An explicit list that omits the id (or an empty set) makes it a structured miss, identically across charter context --include, dispatch routing, governance context, and projection.

Independent Test: In a scratch project, declare a real-format net-new org-pack profile and measure visibility on the dispatch routing catalog, the --profile governance context, and the projection manifest across both gate regimes (key absent / explicit list including vs excluding the profile). The positive case is RED today (dispatch sees N−1); the negative case must stay hidden.

Acceptance Scenarios:

1. Given an org-pack profile orgzilla-org-analyst with activated_agent_profiles absent (default) or an explicit list that includes it, When ProfileRegistry(repo_root).list_all() is read (the dispatch routing catalog), Then orgzilla-org-analyst is present (currently absent). 2. Given the same profile, When spec-kitty dispatch "<request>" --profile orgzilla-org-analyst runs, Then the invocation resolves the profile and its governance context is non-empty (not a routed-but-context-empty half-fix). 3. Given the same pack (activation-admitted), When agent profiles are projected, Then orgzilla-org-analyst is written to the host agent surface and recorded in the projection manifest with a non-builtin source_layer (#2166). 4. Given an explicit activated_agent_profiles list that excludes orgzilla-org-analyst, When dispatch routing and projection run, Then the profile is absent from both (charter de-activation is honoured — the gate is not bypassed by a raw org_dirs splice). 5. Given a project with no org packs declared, When dispatch / projection run, Then behaviour is byte-identical to today (no regression on the built-in-only path) and the existing .kittify/profiles project layer is still honoured unchanged. 6. Given a future contributor wires a routing/projection surface, When they splice raw org_dirs/resolve_org_roots that bypass the activation filter, Then an architectural gate fails (the bypass cannot silently recur). 7. Given an org pack laid out for runtime resolution (<pack>/agent_profiles/), When spec-kitty charter activate agent-profile orgzilla-org-analyst runs, Then it succeeds (no "Unknown agent-profile ID") — the activation CLI and runtime resolve the pack from the same layout (FR-013).


User Story 3 — Unsanctioned built-in overrides surface in a real repo (Priority: P2)

An operator's org pack overrides a built-in DRG node. Whether the repo sanctions that override is governed by .kittify/doctrine/replaceable-builtins.yaml. The operator expects spec-kitty doctor doctrine to report an unsanctioned override as a finding — not have the adjudication live only in a test the deployed repo never runs.

Why this priority: P2 tech-debt closure (#2082). Wires a dormant governance gate to a runtime consumer and retires its dead-symbol allowlist headroom. Depends on no other lane; sized as its own slice because the adjudication logic must first be promoted from test code into production.

Independent Test: In a scratch repo with an org pack that overrides a built-in node without a sanctioning allowlist entry, run spec-kitty doctor doctrine --json and assert the unsanctioned override appears as a finding and the report is not healthy. With a sanctioning entry present, assert it is not flagged.

Acceptance Scenarios:

1. Given an org pack overriding a built-in DRG node with no allowlist entry, When spec-kitty doctor doctrine --json runs, Then the override is reported as an unsanctioned finding and healthy is false. 2. Given the same override with a matching replaceable-builtins.yaml entry, When doctor doctrine runs, Then it is not flagged (sanctioned overrides are silent). 3. Given a project-tier (not org-tier) override, When doctor doctrine runs, Then it is intentionally not governed (trusted operator tier) and the scope boundary is documented. 4. Given the override-policy symbols now have a runtime consumer, When the dead-symbol / dead-module gates run, Then the four override_policy symbols and the module are removed from their allowlists and the category_7_grandfathered_orphans baseline is lowered 7 → 6.

Edge Cases

layer, read by ProfileRegistry) and .kittify/doctrine/agent_profiles (doctrine layer, read by DoctrineService) are different bounded contexts. Lane B must add org_dirs while preserving each site's existing project layer — it must NOT blind-swap ProfileRegistry onto DoctrineService, which would change which project profiles dispatch sees.

construction sites (e.g. agent/tasks.py language resolution, charter/context module cache) may be deliberately built-in-only. Lane B's consolidation + ratchet is scoped to the org-pack-honouring surfaces (dispatch routing, governance context, projection); built-in-only sites are confirmed-then-excluded, not swept.

activation gate lives in charter/resolver.py (DoctrineService.agent_profiles filters by PackContext.activated_agent_profiles), two layers above resolve_org_roots. Wiring raw org_dirs into the routing/projection repositories would surface declared-but-de-activated org profiles whenever a project maintains an explicit activation list — bypassing the charter as the entry point and creating a NEW split-brain opposite to the one this mission closes (gated charter context --include hides what ungated dispatch routes to). The org overlay MUST be the activation-filtered subset (C-008).

→ all org profiles admitted (so #2156 "install → visible" holds for the common case with no activation needed). The first charter activate materialises an explicit list (~16-entry default pack + target), turning the gate on project-wide thereafter — from then on, de-activated profiles must be hidden everywhere.

<pack>/agent_profiles/; the activation CLI (charter/_layer_roots.py) only registers an org root when <pack>/doctrine/ exists, so charter activate agent-profile <id> fails "Unknown agent-profile ID" against a runtime-flat pack. Both resolvers must agree on pack layout.

reusing a built-in id shadows it at merge time. Routing through the activation filter closes the activation half; override-sanction (replaceable-builtins) stays a doctor doctrine diagnostic (Lane C) and is intentionally NOT enforced at the dispatch surface in this mission — recorded so a later mission can close it.

when the answer is absent.

built-in-only behaviour with zero new output.

takes effect post-merge; Lane C must pair the unmask with a full tests/ architectural/ dry-run before the PR (it cannot catch offenders within its own merge).

  • Two distinct project profile layers. .kittify/profiles (legacy invocation
  • Built-in-only sites are intentional. Some AgentProfileRepository
  • Charter is the org-resolution gate (not a test-reliability footnote). The
  • Gate is opt-in / off-by-default. activated_agent_profiles absent → None
  • Activation-CLI vs runtime layout split-brain (FR-013). Runtime resolves
  • Override shadow at the routing surface (noted, partial). An org profile
  • Empty documentation_policy. The fix must not emit a stray directive line
  • No org packs declared. All Lane B/C surfaces must short-circuit to today's
  • Gate-unmask cannot self-validate. Lowering the category_7 baseline only

Requirements (mandatory)

Functional Requirements

IDTitleUser StoryPriorityStatus
FR-001Interpolate documentation_policy into the charter directiveAs an operator, I want my documentation-policy answer rendered in the generated charter so that my governance intent is not silently dropped.HighOpen
FR-002Preserve the empty-answer branchAs an operator who omits a documentation policy, I want no spurious directive line emitted so that the charter stays clean.MediumOpen
FR-003Charter-activation-aware org-profile resolverAs a maintainer, I want one helper that returns the charter-activated org-pack profiles (composing resolve_org_roots + PackContext.activated_agent_profiles via build_activation_aware_doctrine_service), provenance-tagged, so that every org-honouring consumer resolves org profiles through the charter gate identically.HighOpen
FR-004Dispatch routing sees charter-activated org profilesAs an operator, I want ProfileRegistry/router to include org-pack profiles admitted by the charter activation gate so that dispatch routes to activated org agents and never to de-activated ones.HighOpen
FR-005--profile-hinted org agent carries governance contextAs an operator, I want a dispatched (activated) org profile to load its governance context so that the invocation is governed, not empty.HighOpen
FR-006Projection includes charter-activated org profiles (#2166)As an operator, I want charter-activated org-pack agents projected to .claude/agents/ and the projection manifest so that my host IDE sees exactly the activated set.HighOpen
FR-007Preserve existing project profile layersAs an operator, I want the .kittify/profiles and .kittify/doctrine/agent_profiles project layers unchanged so that the fix adds the org overlay without moving existing profiles.HighOpen
FR-008Anti-regression gate for activation-bypassing constructionAs a maintainer, I want an architectural gate forbidding a routing/projection surface from splicing raw org_dirs/resolve_org_roots that bypass the activation filter, so that the charter gate cannot silently be bypassed and must be the entry point.MediumOpen
FR-013Unify activation-CLI pack layout with runtime resolutionAs an operator, I want charter activate agent-profile <id> to resolve org packs from the same layout runtime resolution uses, so that activating a runtime-resolvable org profile does not fail with "Unknown agent-profile ID".HighOpen
FR-009Promote override-adjudication into productionAs a maintainer, I want find_overridden_builtin_urns / find_unsanctioned_overrides / UnsanctionedOverride moved from the test into drg/override_policy.py so that a runtime consumer can call them.HighOpen
FR-010doctor doctrine reports unsanctioned built-in overridesAs an operator, I want doctor doctrine to flag unsanctioned built-in overrides in a deployed repo so that governance is enforced at runtime, not only in CI tests.HighOpen
FR-011Retire override-policy dead-symbol allowlists + lower baselineAs a maintainer, I want the four override-policy symbols + module removed from the dead-symbol/-module allowlists and category_7 baseline lowered 7 → 6 so that the ratchet is not gamed.MediumOpen
FR-012Document the project-tier ungoverned boundaryAs an operator, I want it documented that project-tier overrides remain intentionally ungoverned so that the trust model is explicit.LowOpen

Non-Functional Requirements

IDTitleRequirementCategoryPriorityStatus
NFR-001No built-in-only regressionA project with no org packs and no documentation_policy produces byte-identical output to pre-mission on all three surfaces (charter, dispatch/projection, doctor).ReliabilityHighOpen
NFR-002Two-regime live-evidence proof for Lane BThe fix is proven by a live run on BOTH regimes: (positive) a charter-activated org profile IS visible through dispatch + projection, AND (negative) a declared-but-de-activated org profile (explicit activated_agent_profiles excluding it) is ABSENT from dispatch + projection. A single activated-only assertion is insufficient (it cannot witness a gate bypass).ReliabilityHighOpen
NFR-003New-code quality gatesAll new/boy-scout-touched code passes ruff + mypy with zero issues and complexity ≤ 15; every new branch/helper has a focused test in the same lane (Sonar new-code coverage).MaintainabilityHighOpen
NFR-004Fail-closed governance readsPromoted override-policy predicates remain pure and fail-closed (a malformed allowlist or DRG does not flip an unsanctioned override to "sanctioned").SecurityHighOpen

Constraints

IDTitleConstraintCategoryPriorityStatus
C-001Lanes topology, no coordMission runs as topology: lanes with no coordination branch; lanes are file-disjoint and independently mergeable.TechnicalHighOpen
C-002Distinct project layers preservedC-002 governs the project overlay only: Lane B must NOT collapse .kittify/profiles and .kittify/doctrine/agent_profiles into one layer or reroute ProfileRegistry's project layer through DoctrineService. The org overlay is composed separately (C-008).TechnicalHighOpen
C-003Ratchet scoped to org-honouring surfacesFR-008's gate covers dispatch/context/projection only; confirmed built-in-only sites are excluded with recorded rationale, not swept.TechnicalMediumOpen
C-008Charter is the org-resolution entry pointOrg-pack agent profiles reach dispatch routing, governance context, and projection only through the charter activation filter (PackContext.activated_agent_profiles, three-state). No invocation/projection-layer consumer may splice raw org_dirs/resolve_org_roots that bypass the filter. Reuse build_activation_aware_doctrine_service; never re-implement the gate.TechnicalHighOpen
C-004Gate-unmask paired with full-gate dry-runFR-011's baseline lower is validated by a full tests/architectural/ (incl. CI-only shards) dry-run before the PR.TechnicalHighOpen
C-005Red-first through pre-existing entry pointsEach lane's failing test drives the pre-existing public surface (charter generate, ProfileRegistry/dispatch, doctor doctrine), not the fix's new internal API.ProcessHighOpen
C-006Canonical sources onlyUse resolve_org_roots and the existing _doctrine_collect/doctor DRG load; do not hand-roll org-root resolution or new DRG plumbing.TechnicalHighOpen
C-007Realistic test dataOrg-pack fixtures use real-format pack ids, profile ids, and .agent.yaml shape; sentinel values are realistic policy prose, not foo/bar.ProcessMediumOpen

Key Entities

resolved to roots by resolve_org_roots; agent profiles live under <pack>/agent_profiles/*.agent.yaml.

org layer is the one omitted at the dispatch/projection construction sites.

allowlist governing which built-in DRG nodes an org pack may override.

  • Org doctrine pack: declared in .kittify/config.yaml (doctrine.org.packs[].local_path),
  • AgentProfileRepository layers: built_in / org_dirs / project_dir — the
  • ReplaceableBuiltinsPolicy: the .kittify/doctrine/replaceable-builtins.yaml

Issue Matrix

IssueTitlePriorityParent epicLaneDisposition
#2156Enable non built-in Doctrine agents in dispatch modeP1#1799Bin-mission
#2166Agent-profile projection ignores the org-pack layerP1#1868Bin-mission (folded — projection leg of #2156)
#2153charter generate discards documentation_policy answerP2#1799Ain-mission
#2082Wire built-in-override governance into doctor doctrineP2#2080Cin-mission
#2049Shrink ratchet allowlistsP3Creference-by-checklist (FR-011 delivers one burn-down item; no sweep)
#2059Decompose doctor.py god-moduleP3Creference-by-checklist (land override code by extraction; no full de-godding)
#1416Charter synthesis drops interview answers (key drift)Aprior-art cross-link (CLOSED via PR #1419; touched only synthesizer/, never compiler.py — #2153 is distinct)

Success Criteria (mandatory)

Measurable Outcomes

generated charter.md Project Directives; the empty-answer case emits no line.

or explicitly activated), the dispatch routing catalog, the --profile governance context, and the projection manifest all include it (specify-vs-dispatch divergence eliminated); with the profile explicitly de-activated, all three exclude it (the gate is honoured, not bypassed).

org pack laid out for runtime resolution (no "Unknown agent-profile ID") — the activation CLI and runtime resolver agree on pack layout.

identical output on all three surfaces vs pre-mission (no regression).

in a deployed repo and stays silent for a sanctioned one.

dead-symbol/-module allowlists, category_7_grandfathered_orphans baseline is 6, and the full tests/architectural/ suite (incl. CI-only shards) passes.

construction site omits org awareness.

  • SC-001: A seeded documentation_policy sentinel appears verbatim in the
  • SC-002: With an org-pack profile admitted by the charter gate (default
  • SC-007: spec-kitty charter activate agent-profile <id> succeeds against an
  • SC-003: A project with no org packs and no documentation policy produces
  • SC-004: spec-kitty doctor doctrine flags an unsanctioned built-in override
  • SC-005: The four override_policy symbols + module are removed from the
  • SC-006: An architectural gate fails if a new org-honouring profile-repo