Phase 1 Data Model — DRG Reachability Metric & Orphan Wiring

This mission adds no runtime data types. The "data model" here is (1) the DRG entities the change touches, (2) the new companion-metric definition, and (3) the golden-constant / ledger move-set.

DRG entities (existing — reference only)

(doctrine.drg.models.DRGEdge; construct edges via this model, not dataclasses.replace).

relevant here: action channel = scope(d1) → requires(∞) + suggests(≤depth) from the scope set; profile channel = {requires, specializes_from}(∞) from profile seeds.

packs/built-in/*.graph.yaml). Regenerated deterministically by doctrine regenerate-graph / generate_graph.

  • DRGNode { urn: "<kind>:<id>", kind, ... } — a doctrine artifact.
  • DRGEdge { source, target, relation, when?, reason?, provenance? } — pydantic model
  • Relation — enum: scope, requires, suggests, vocabulary, specializes_from, … The traversal
  • DRGGraph{ nodes, edges }; the shipped composed graph is load_built_in_graph() (reads

New: reachability companion metric (_ACTION_UNREACHABLE_SHIPPED) — action-only, partitioned

(Revised after post-plan squad — the primary pin is action-only, per #3009's literal "reachable from actions" ask; the both-channel set is retained as a named partition subset.)

Primary definition (action-only, whole-graph, "not reachable from any action root"):

_ACTION_UNREACHABLE_SHIPPED = { n.urn for n in graph.nodes
    if n.urn NOT in action_channel_reachable(graph, action_seeds, _ACTION_D2_DEPTH)
    and kind(n.urn) NOT in _BY_DESIGN_UNREACHABLE_KINDS } − action_seeds − agent_profile_seeds

glossary_pack} (edgeless-by-construction / resolved by URN presence, not traversal). Action and agent_profile seeds are traversal roots, excluded. anti_pattern` exclusion rationale recorded in the contract (Alphonso Axis-1).

channel at _ACTION_D2_DEPTH (bootstrap) — a named constant, not a literal.

differ. Deterministic (NFR-003). Guard helper lives in the test module (Renata F7).

  • _BY_DESIGN_UNREACHABLE_KINDS = `{mission_step_contract, asset, anti_pattern, template, mission_type,
  • Computed via the canonical action_channel_reachable / profile_channel_reachable helpers. Action
  • Assertion: set-equality against the pinned frozenset; failure reuses the existing _describe URN

Value: 88 before wiring → 75 after (measured, like-for-like exclusions). The 13 that leave: the 3 directives (DISCIPLINED_REFACTORING, RECONCILE_CHANGE_SCOPE_TENSIONS, USE_MUTATION_TESTING) + their cascaded families (7 refactoring-* Fowler tactics, mutation-testing-workflow, python-/typescript-mutation-tools).

Asserted partition (totality — Debbie #1; #3009 "record which nodes the count covers"): the 75 splits, total & disjoint, into two named frozensets:

dispositioned in IC-03). Both-channel measure moves 38 → 34; the four that leave vs the un-wired baseline are RECONCILE_CHANGE_SCOPE_TENSIONS, spike-timebox-policy, glossary-maintenance-workflow, meeting-minutes-pipeline.

{requires, specializes_from, suggests} web (by design; group-dispositioned).

  • _DEAD_DOCTRINE_SHIPPED = 34 — reachable from neither channel (the genuine residual; each member
  • _PROFILE_DELIVERED_SHIPPED = 41 — action-unreachable but delivered via the profile channel's

Why the directives don't move _DEAD_DOCTRINE_SHIPPED: DISCIPLINED_REFACTORING / USE_MUTATION are already profile-channel reachable, so they sit in _PROFILE_DELIVERED_SHIPPED, not the dead set; wiring them still shrinks the action-only primary pin (they leave it) and the activated-only action pins (their cascaded activated tactics leave).

Golden-constant / ledger move-set (IC-04)

Each move needs a docs/plans/doctrine/delivery-reachability-wiring-table.md composition-ledger row naming the responsible edge (NFR-004) + a numbered-ledger entry in test_extractor_projection.py. Exact numeric targets for the activated-only and incidence pins are recomputed at implement time against the regenerated graph; direction is fixed here.

(Corrected per Renata F1/F5: _PROFILE_UNREACHABLE is activated-only; only glossary-maintenance-workflow is activated of the three procedures. The activated action pins move via cascaded activated tactics, not the non-activated directives.)

ConstantFileMoveDriver / correct accounting
_ACTION_UNREACHABLE_SHIPPED (new, primary)test_reachability.pynew pin, 88→75action-only whole-graph; 13 leave (3 directives + families). Needs a NEW mechanical ledger-coverage test (F2/Debbie #5).
_DEAD_DOCTRINE_SHIPPED / _PROFILE_DELIVERED_SHIPPED (new subsets)test_reachability.pynew pins, 34 / 41asserted partition of the primary (total & disjoint)
_ACTION_UNREACHABLE_D1 / _D2 (activated-only)test_reachability.pyshrinkvia cascaded activated tactics (refactoring-*, mutation-testing-workflow) + RECONCILE (the only activated directive of the three); NOT DISC/USE (not activated)
_PROFILE_UNREACHABLE (activated-only)test_reachability.pyshrink by 2 (Debbie, verified)glossary-maintenance-workflow (activated procedure) and RECONCILE_CHANGE_SCOPE_TENSIONS (activated directive, now profile-reachable via edges 2/3). spike/meeting-minutes not activated → not members
_PROFILE_RESCUES (= D2 − PROFILE_UNREACHABLE)test_reachability.pygains glossary-maintenance-workflow only (RECONCILE leaves both D2 and PU, so does NOT enter rescues); ledger row per member in the existing NFR-002 sectionalready mechanically gated (test_every_profile_rescue_member_has_a_ledger_row, section-scoped)
_ACTIVATED_BUT_ORPHANEDtest_extractor_projection.pyshrink (RECONCILE leaves)edges 2/3 — MUST only shrink (C-003)
_AWAITING_REFERENCEStest_extractor_projection.pyshrink (DISCIPLINED_REFACTORING, USE_MUTATION leave incidence-orphan status)edges 1/4 give them incident edges
_INTENTIONAL_ORPHANS / _SHIPPED_ORPHANStest_extractor_projection.pyshrinkfewer incidence orphans
_ORPHANS_RESOLVED_BY_OVERLAYtest_extractor_projection.pyreconcile if a curated extractor edge supersedes an overlay oneedges 1–4
numbered-ledger entry 18 + shipped-edge-count prosetest_extractor_projection.pynew ledger entry (not a live int; byte-identity gate enforces)+6 edges (F6)
DOCUMENTED_ORPHAN_RESIDUAL (21, <= ceiling)test_doctrine_regenerate_graph.pyratchet DOWN in SAME WP as incidence shrinkC-005
_NORMALIZATION_DELTA (31)test_reachability.pyno movewiring does not touch store↔node slug reconciliation

Invariants to preserve:

(_EDGELESS_BY_CONSTRUCTION, _AWAITING_REFERENCES, _NOT_A_TRAVERSAL_TARGET, _ACTIVATED_BUT_ORPHANED).

  • Incidence partition stays total & disjoint: sum(len(part)) == len(_INTENTIONAL_ORPHANS) over
  • Reachability/incidence residuals only shrink or hold (C-003) — no node added to any defect/residual set.
  • Graph regeneration is byte-identical on re-run (determinism).

Residual truth-up (IC-03)

Every member of the pinned residual gets a disposition (Debbie #1 totality):

profile channel — by design").

(with note: DIRECTIVE_035, DIRECTIVE_039, migrate-project-guidance, deployable-skill-authoring, atomic-design [inert-edge], the SPDD/documentation/java-mission clusters) | by-construction | B2-deferred- with-referent-named.

profile seed, excluded from the reachability sets by construction (Debbie #2). Do not list it as a reachability residual.

action-reachable of the former "6 promoted" (decision-marker-capture, no-parallel-duplicate, python-review-checks, red-main-release-discipline); reasons-canvas-writing + occurrence-classification are profile-only; atomic-design is a reachability residual (Debbie #4).

  • 41 _PROFILE_DELIVERED_SHIPPED — dispositioned as a group ("action-unreachable, delivered via the
  • 34 _DEAD_DOCTRINE_SHIPPED — each gets a one-line disposition: honest activation/runtime-only residual
  • agent_profile:human-in-charge is recorded ONLY as an incidence (#1923) residual — it is a
  • Retire toolguide:rtk-search-tooling (removed from disk + graph). Promote only the genuinely