DRG completeness (#2843 / #2847) — pre-spec research squad findings
Date: 2026-07-21 Origin: post-landing residue of PR #2833 (in_tension_with DRG edges). A bounded 4-lens, profile-loaded pre-spec research squad (doctrine-daphne, architect-alphonso, paula-patterns, planner-priti) investigated GitHub issue #2843 before spec. This note is the durable record so the parked mission can resume without re-running the squad.
Operator decision — SPLIT
The original #2843 bundle was split into two missions:
- #2843 (re-scoped) — items 2+3, the small "close what #2833 opened" work. Mission branch
doctrine/drg-completeness-2843. - #2847 — item 1 (anti-pattern corpus promotion), carved out as its own mission because it is ~22× the size of the other two combined, needs a human curation pass, and has zero build-dependency on them.
Both are native sub-issues of epic #2466.
Corrected ground truth (measured, not estimated)
- 136 inline
anti_patterns:entries / 131 unique, across 34 authoring files (18 styleguides carrying rich 4-field blocksname/description/bad_example/good_example; 16 procedures carrying thin 2-field blocksname/description). Tactics carry no inline block. - Dedup ≈ 0. The "collapse duplicate copies" premise is false — the inline corpus is a
disjoint, mostly-unique population. The only exact recurrence is one byte-identical duplicate
file (
styleguides/writing/kitty-glossary-writing.styleguide.yamlmirrored atbuilt-in/writing/) — a campsite delete. - The inline population is disjoint from #2833's 6 nodes (those are paradigm-derived and appear zero times inline).
- 12
Relationmembers lack descriptions (the issue's "9" was stale;models.pydocstring says twelve).
Item A (#2843) — relation-description + glossary parity · size S
- Backfill
RELATION_DESCRIPTIONS(src/doctrine/drg/models.py) for:requires, suggests, refines, replaces, enhances, overrides, specializes_from, delegates_to, scope, instantiates, applies, vocabulary. - Dual-doc: the content-equality parity test
tests/doctrine/test_relation_doc_parity.pyguardsdocs/architecture/doctrine-relationships.md(widen_SCOPED_RELATIONS3→15); also extenddocs/context/doctrine.md(decision pending: bring it under the parity check too). - Not transcription:
appliesvsscopeare semantically contested/mis-wired (applieshas 1 edge,scope157) — an adjudication;vocabulary/refines/delegates_toare "never emitted" today and must be described as intended-but-dormant.
Item B (#2843) — activation-gate consolidation = LATENT CORRECTNESS BUG · size M
Two lenses (alphonso + paula) independently found this is not a cosmetic refactor:
charter/drg.py::_node_is_activated Step 3 compares canonical node ids (DIRECTIVE_001)
against config stems (001-architectural-integrity-standard), so a populated
activated_directives list silently drops every directive node. It only appears healthy
because per-ID activation lists are ~always None (default-allow). Three sites independently work
around it: compiler._resolve_config_activated_ids, _check_graph_kind_parity, and #2833's
_node_is_tension_scan_active.
Required (for the spec):
- Verify first (highest-leverage): confirm empirically that no production
.kittify/config.yamlpopulates per-kindactivated_*lists — decides "latent-bugfix" framing and blast radius. - Red-first characterization test proving
filter_graph_by_activationdrops a stem≠canonical directive node under a populated list (against the real corpus, not hermeticid==stemfixtures). - One canonical-URN-aware gate via a single stem→canonical resolver (home
charter.kind_vocabulary, whereresolve_artifact_urnlives). Siting (resolve-in-filter vs resolve-at-PackContextvs resolve-at-gate-boundary) is a/plandesign decision. - Collapse workarounds honestly: delete the tension-scan reimplementation
(
_node_is_tension_scan_active/_build_tension_active_urns/_resolve_activated_urns_for_kind) so it becomes a plain gate consumer; re-point_check_graph_kind_parity(a per-ID behavior upgrade owned with tests). The compilerreferences.yamlprojection legitimately stays (it builds a catalog id list, not a graph filter) — do not promise deleting it. - 5-consumer regression net (populated-
activated_directivesbefore/after tests):specify_cli/mission_step_contracts/executor.py,charter/reference_resolver.py,charter/compiler.pyclosure,charter/consistency_check.py::_check_drg_cross_kind_refs,charter/context.py.
Item 1 (#2847) — anti-pattern corpus promotion · size L (own mission)
- Retain, don't migrate:
DRGNodeholds onlyurn/kind/label/provenance/tags— no room for description/examples. Keep the rich inline blocks (examples co-located); the node is a thin queryable handle; the "why" lives on therejectsedgereason. - Extractor-mint, not hand-overlay: teach
src/doctrine/drg/migration/extractor.pyto mint ananti_patternnode +rejectsedge (source = host artefact URN) from each inline block. It has noanti_patternspath today; hand-authoring 131 nodes in the overlay registry does not scale. - Add a
severity/tag field (anti-pattern|smell) to the inline schemas so the ~131 classification calls are authored, not guessed. - Human curation pass (the real weight): ~6 near-duplicate concept clusters under different
names (
Over-Mocking,Shared Mutable State×3,Gate-hardwiring,Meta-tracker…) that exact-name extraction would silently double-mint → a reviewedname → canonical-urnmap. - Integrity: ~118–131
rejectsedges; INV-004 validators already enforce the shape (add a dup-URN guard); golden-count churn intests/doctrine/drg/migration/test_extractor_projection.py.
Sequencing
Item A is independent (enum+docs). Item B lands before #2847 (the ~118 anti_pattern nodes ride the
corrected gate; no hard dependency since anti_pattern: URNs are slug==id, so they don't hit the
stem≠canonical bug). Full per-lens detail lives in the #2843 / #2847 issue bodies and session
memory (project_drg_completeness_2843_mission).