Research — Mission-Type DRG Node + Cross-Grain Integrity Gate
R1 — DRG generator internals (S0 grounding)
Decision: Add mission_type as a node kind and emit one node per built-in type via the existing generator. Findings:
Rationale: matches the ADR's "packs offer via the graph" for the 8 existing kinds; brings mission_type onto the same rails, which also unblocks cascade (_source_urn resolves). Alternatives considered: a bespoke mission_type registry outside the DRG (rejected — perpetuates the "runs outside the graph" gap the ADR closes).
graph.yamlnode shape is{urn, kind, label}(plus edges elsewhere). Present kinds: action, agent_profile, directive, paradigm, procedure, styleguide, tactic, template, toolguide — nomission_type/step_contract/gate/asset(confirms the ADR S0 gap).- DRG code:
src/doctrine/drg/{models.py, validator.py, migration/extractor.py, loader.py, query.py}; generated headergenerated_by: drg-migration-v1. - Node kinds are enumerated/validated in
drg/models.py+drg/validator.py; generation is indrg/migration/extractor.py. Adding a kind = register in models/validator + teach the extractor to emitmission_type:<type>nodes frommissions/mission_types/*.yaml, then regenerate +--check.
R2 — FR-013 enforcement home (adjudication outcome)
Decision: The load-bearing enforcer is a doctrine-integrity / DRG consistency gate over the unioned (type ⊕ action) grain on real content, with a non-vacuity twin. The resolver-embedded CrossGrainDoubleDeclarationError is a lazy fast-fail only, subordinate to the gate. Rationale: ADR 2026-07-14-2 Enduring-verification already decided this (doctrine-module + integration test with a non-vacuity twin through a shared action name). The resolver raise is verifiably swallowed on every runtime path (CrossGrainDoubleDeclarationError(ValueError) sibling of UnknownMissionTypeError; runtime except Exception: pass; nothing reads .governance), and making the runtime consume it eagerly would regress NFR-001. Alternatives considered: (a) resolver-embedded eager guard as sole enforcement — rejected (inert + NFR-001 regression + vacuous on shipped disjoint doctrine); (b) abandon the union — rejected (ADR mandates the union + _EMPTY_GRAIN retirement).
R3 — Vacuity is correct-by-design
Decision: Frame the gate as forward-looking regression protection; author a deliberate-collision temp-tree fixture for the non-vacuity twin. Rationale: shipped doctrine is authored disjoint on purpose (plan/actions/plan/index.yaml: "kept disjoint (FR-013) — do not repeat type-grain selections"). Under the ADR's pack-extensibility, future org/project grains can collide. "Catches no existing defect" is by design; the defect was only the spec's self-contradictory wording, now fixed.
R4 — Eager→lazy + overlay-aware root
Decision: Thunk the governance slot (mirror expected_artifacts/step_contracts); source the action-grain from the overlay-aware missions_root (MissionTypeProfileRepository/package-asset resolution), not the resolver's repo_root. Rationale: ADR NFR-001 driver ("keep resolution lazy like expected_artifacts/step_contracts"); type-grain is overlay-resolved (project>org>builtin), so a single-root action-grain would be asymmetric authority (ADR-incoherent). load_action_index returns a per-action ActionIndex dataclass → needs an aggregation loop + ActionIndex→Mapping[str,list[str]] adapter before from_grains. Alternatives considered: single shipped root (rejected — asymmetric); eager (rejected — NFR-001).
R5 — Second-source reconciliation
Decision: Reconcile the two enduring test-side unions (_resolve_union, _resolve_union_from_mission) in-mission to a single source. Rationale: once production folds the action-grain in, these double-union; they are a live second implementation of the exact reduction this mission canonicalizes (DIRECTIVE_044 single-authority). ADR testing posture expects substantial suite change.