Section 02: DAG Builder & Conflict Classifier
Status: Not Started Goal: Build a dependency DAG across ALL seven schema classes in the corpus (not just plan sections) and implement a classifier stack — six mission classifiers plus two informational ones — with deterministic precedence and explicit source-kind tagging, so the mission test cases (a), (b), (c), (g), (h) are caught exactly as specified in the overview without false positives on code fences, prerequisite coordination, or YAML anchor-like syntactic noise.
Success Criteria:
- Node model covers all seven §01.2 schema classes
- Source-kind taxonomy defined and every reference carries its source_kind
- EXPLICIT_DEPENDS_ON is the sole SSOT for DAG edges; body-inferred references emit MISSING_DEPENDENCY findings, never shadow edges
- 8 classifiers implemented with documented precedence and deterministic order
- Known test cases (a), (b), (c), (g), (h) caught by the exact classifier/subtype documented in §05.2
Context: The current /verify-roadmap command operates on individual roadmap sections in isolation. It has zero awareness of reroute plans, cross-plan dependencies, or shared subsystem ownership. As a result, it misses entire classes of bugs: priority inversions (repr-opt active but its locality prerequisite is queued), dead references (roadmap section 22.2 references nonexistent plans/ori_lsp/), and supersession drift (test-suite-health section 02 claims to rewrite roadmap 21A but the rewrite never happened). The DAG builder creates the graph structure that makes these detectable.
Two meta-risks motivate §02.0 and §02.4 before the classifier work in §02.2:
- Node coverage. The live test corpus contains bugs spanning fix-BUG files, roadmap sections, and completed-plan indexes — not just plan sections. A node model restricted to
plans/<plan>/section-<NN>-<slug>.mdfiles loses test case (g) (BUG-04-039 lives inplans/bug-tracker/fix-BUG-04-039.md) and test case (h) (Section 21A cross-references a completed plan). - Classifier interference. Multiple classifiers can fire on the same source line (e.g. a DEAD_REFERENCE inside a prose-verb
depends onclaim is simultaneously a MISSING_DEPENDENCY candidate and a DEAD_REFERENCE). Without an explicit precedence and source-kind taxonomy, the classifier stack is non-deterministic and leaks false positives from code-fence examples in the bug-tracker overview.
Depends on: Section 01 (Frontmatter Schema) — the DAG builder relies on plan_corpus for load_and_validate, Corpus.name_index, Finding, FindingCategory, FindingSubtype, and resolve_dep. No shadow parsers.
Scope NOTEs for /tpr-review triage:
- NOTE (drift with §05.2 — case (c)):
plans/test-suite-health/index.md:2DOES havereroute: true(it IS a reroute plan — TPR-02-001-gemini corrected the earlier misreading). The reroute index, however, has nosupersedesfield at all (not empty — absent), ANDplans/test-suite-health/00-overview.md:5hassupersedes: []. Case (i) of SUPERSEDED iterates thesupersedeslist and finds nothing. The live body atplans/test-suite-health/section-02-roadmap-reprioritization.mduses “update 21A”, “reprioritize”, and “reorder Section 21A” — NEVER the verbs “rewrites X” or “rewrite of X”. §02.2 Case (ii) trigger verbs must therefore include the live-corpus verbs (see TPR-02-002), not just explicit rewrite markers. Downstream /tpr-review to reconcile with §05.2:124 — either §05.2’s classification for case (c) stays SUPERSEDED (requiring Case (ii)‘s broader vocabulary), or §05.2 narrows to accept MISSING_DEPENDENCY on thesupersedes: []pattern. - NOTE (handoff to §03 — Finding schema extensions): §02.5 requests two §01.3-owned extensions to
Finding: (a)source_kindas a first-class facet, (b) a mechanism for multi-hop transitive chains. Options are enumerated below; the chosen option must be sourced into §01.3 at TPR triage time. - NOTE (handoff to §03 — git-timing-aware subtypes):
TPR_STALE_VS_EDITas described in §01.3 cannot be computed from mtime (git clone/CI resets mtimes). §02 emits theCROSS_EDGE_TEMPORAL_DRIFTsubtype only.TPR_STALE_VS_EDITis specialized in §03’s write-back phase usingWriteBackContext.has_recent_commitsand git%cItimestamps. - NOTE (drift with §01.3 owner): §01.3 documents
CROSS_EDGE_TEMPORAL_DRIFTandTPR_STALE_VS_EDITas Section 02 DAG classifier subtypes. Per the handoff above, §02 emits ONLYCROSS_EDGE_TEMPORAL_DRIFT; §03 is the owner ofTPR_STALE_VS_EDIT. /tpr-review to update §01.3’s attribution ofTPR_STALE_VS_EDITif the split is ratified. - NOTE (drift with §01.5 fixtures): §01.5 line 425 claims “05.2 validation case asserts the (g)/(h) bug-tracker scenarios are caught via this subtype” referring to
TPR_STALE_VS_EDIT. §01.5 line 637 corrects this: (g) → BLOCKED, (h) → DEAD_REFERENCE, neither maps toTPR_STALE_VS_EDIT. §02 enforces the §01.5:637 mapping. /tpr-review to remove the stale claim at §01.5:425. - NOTE (handoff to §03 — Option A typed-field contract, per TPR-02-001-codex-r4): §03’s
section-03-findings-report.md:64(and the surrounding classifier/write-back text) was authored against the pre-Option-A Finding contract — it referencesevidence-based parsing for chain and source_kind data. Round-3 round made Option A load-bearing in §02 (typedFinding.dependency_chainandFinding.source_kind), and §03 must import the Option A contract when its turn in the plan sequence arrives. §02 cannot edit §03 directly (single-section scope); /tpr-review on §03 (separate pass) must propagate Option A into §03’s SafeFix/ExposureReview classifier:classify_safetyreadsfinding.source_kinddirectly (typed field), notevidence[0]parsed as"source_kind:<VALUE>". The dependency-chain data for §03’s report rendering comes fromfinding.dependency_chain, not a string-split onevidence. This NOTE is the handoff record. - NOTE (drift with §05.2 route A/B — per TPR-02-004-codex-r3): §05.2:113 and §05.2:143 currently assert “Expected: BLOCKED finding” unconditionally for cases (a) and (g). Per §02.2’s route-A/route-B split, both cases emit MISSING_DEPENDENCY in the current live-corpus state (no
depends_onedges declared); BLOCKED only fires AFTER route-A corpus edits land. §05.2 must be updated to branch: “Expected: MISSING_DEPENDENCY in current corpus state; BLOCKED after route-A edits land.” This is a §05 edit; /tpr-review on §05 (separate pass) must propagate the route-A/B split from §02.2 into §05.2:113,143. §02 cannot edit §05 directly (single-section scope) — this NOTE is the handoff record.
02.0 Node Model & Source-Kind Taxonomy
File(s): scripts/plan_corpus/dag.py (new module — homes NodeId, NodeKind, Reference, Edge, and DAG-specific types; imports Finding/FindingCategory/FindingSubtype/Corpus/SourceKind from plan_corpus). SourceKind(Enum) lives in scripts/plan_corpus/types.py alongside Finding — not dag.py — because Finding.source_kind: SourceKind | None is a typed field on the canonical Finding dataclass. Homing SourceKind in dag.py would create a circular import (types.py → dag.py → types.py) — the structural-purity version of LEAK:scattered-knowledge (per TPR-02-001-gemini round 2). types.py is the canonical home for boundary-crossing types; dag.py consumes them.
This subsection lays the structural foundation for §02.1–02.4. It must be complete BEFORE the DAG is built, because node coverage and source-kind tagging are load-bearing for every classifier downstream.
-
Define
NodeKind(Enum)covering all seven §01.2 schema classes:PLAN_INDEX | PLAN_SECTION | ROADMAP_SECTION | OVERVIEW | BUG_TRACKER_SECTION | FIX_BUG | COMPLETED_INDEX- One-to-one with
FileClassinscripts/plan_corpus/schema.py:60-68 - Every
Corpusbucket (indexes,plan_sections,roadmap_sections,overviews,bug_sections,fix_bug_files,completed_indexes) contributes nodes
-
Define
NodeIdas a frozen dataclass(kind: NodeKind, path: Path):- Hashable and comparable for graph use
- Equality = same file class AND same resolved path
- Include a helper
NodeId.from_validated_file(vf: ValidatedFile) -> NodeIdthat mapsFileClass→NodeKindvia a lookup table (no re-classification)
-
Define
SourceKind(Enum)— the taxonomy used by every reference and edge. Home:scripts/plan_corpus/types.py(alongsideFinding— see the §02.0 “File(s)” note and TPR-02-001-gemini round 2 rationale for why this CANNOT live indag.py):EXPLICIT_DEPENDS_ON— frontmatterdepends_on: [...]entries (the only DAG-edge source)HTML_COMMENT_CONVENTION— structured HTML comments (<!-- blocked-by:ID -->,<!-- unblocks:ID -->,<!-- supersedes:ID -->,<!-- resolves:ID -->)YAML_COMMENT— comments inside YAML frontmatter (status: in-progress # TPR done; hygiene blocked by BUG-05-003 → plans/iterator-element-ownership/)PROSE_VERB— body prose using one ofdepends on,requires,blocked by,prerequisite,unblocks,supersedes,rewrites,obsoletes,see also,related,inspired by,cf.(verb-bearing references; some feed MISSING_DEPENDENCY, others are informational)CODE_FENCE_EXAMPLE— anything inside a fenced code block (...) or indented code block (4+ leading spaces)
-
Define
Referencedataclass(from_node: NodeId, target: str, source_kind: SourceKind, source_line: int, source_column: int | None, raw_text: str):- Every reference carries enough info to disambiguate
Finding.idcollisions (Finding J): thesource_columnfield plus the raw_text hash serve as tie-breakers targetis the raw string as it appears in the file (not yet resolved); resolution happens viaplan_corpus.resolve_depin §02.1
- Every reference carries enough info to disambiguate
-
Define
Edgedataclass(from_node: NodeId, to_node: NodeId, source_kind: SourceKind, reference: Reference):- Edges are ONLY created from
SourceKind.EXPLICIT_DEPENDS_ONreferences (per the SSOT rule below) source_kindon anEdgeis alwaysEXPLICIT_DEPENDS_ON; it is carried onEdgeonly for debugging symmetry withReference
- Edges are ONLY created from
-
Explicit SSOT rule — body-inferred references do NOT add edges:
EXPLICIT_DEPENDS_ONis the sole edge source. Body-inferred references (HTML_COMMENT_CONVENTION,YAML_COMMENT,PROSE_VERB) are collected asReferencerecords but are never promoted toEdge.- If a
PROSE_VERB/HTML_COMMENT_CONVENTION/YAML_COMMENTreference names a node that is NOT in the DAG as adepends_onsuccessor of the current node, it is emitted as aDAG_CONFLICT / MISSING_DEPENDENCYfinding. - This makes the DAG deterministic and frontmatter the SSOT: authors fix by adding a
depends_onentry, not by the tool silently injecting shadow edges.
-
Define
SUBSYSTEM_ALIASES: dict[str, str]— the canonical name-normalization table:- Source (A): auto-populate from
Cargo.tomlworkspace members (e.g.compiler/ori_arc→ori_arc,compiler/ori_llvm→ori_llvm) - Source (B): hand-maintained logical aliases (e.g.
ArcClassifier→ori_arc,AIMS→ori_arc,Tag::Var→ori_types,ReprPlan→ori_repr) - Expose
normalize_subsystem(raw: str) -> str | None;Nonefor unrecognized tokens (they do NOT contribute to shared-subsystem mapping) - Unit test: every workspace crate’s display name appears in the normalized output
- Source (A): auto-populate from
-
Define code-fence/indented-code-block exclusion helper
strip_code_blocks(body: str) -> list[tuple[int, int, str]]:- Returns a list of
(start_line, end_line, kind)tuples describing code-fence regions (kind∈{"fenced", "indented"}) - Used by §02.1 body scanners to mask out code-fence regions before heuristic matching
- Fenced detection: lines matching `^```` (optional language tag); matched pairs
- Indented detection: lines starting with 4+ spaces that are preceded by a blank line (loose but matches CommonMark indented-code-block semantics)
- Returns a list of
-
Define YAML frontmatter raw-text helper
extract_yaml_comments(text: str, body_offset: int) -> list[tuple[int, int, str]]:- Returns
(line_number, column, comment_text)tuples for every# ...comment found on frontmatter lines body_offsetfromsplit_frontmatter_strictbounds the scan to the---…---region only- PyYAML strips comments at parse time, so this is a raw-text post-parse pass
- Returns
-
Define HTML comment grammar helper
parse_html_comments(body: str) -> list[Reference]:- Matches
<!--\s*(blocked-by|unblocks|supersedes|resolves|rewrites|update-complete|updated-by)\s*:\s*([^ \t\r\n,]+(?:,[^ \t\r\n,]+)*)\s*-->— hyphens ARE allowed in target tokens (plan slugs are hyphenated, e.g.jit-exception-handling/04B,iterator-element-ownership); only whitespace and the,separator are excluded from each comma-separated target. Extended per TPR-02-001-codex-r3 to include the three verbs §02.2 SUPERSEDED case (ii) consumes. TPR-02-003-codex semantic pin: the live case (h)<!-- unblocks:jit-exception-handling/04B,05,06 -->MUST parse into three targets (jit-exception-handling/04B,05,06), not zero. - Verbs → semantic:
blocked-by:ID→ forward reference (current node blocked by ID)unblocks:ID→ reverse reference (current node unblocks ID; the logical edge runs from the BLOCKED node to the UNBLOCKING one, i.e. the REVERSE of the literal direction)supersedes:ID→ supersession reference (current node supersedes ID)resolves:ID→ bug-fix reference (current node resolves bug ID)rewrites:ID→ rewrite reference (current node claims to rewrite ID; feeds SUPERSEDED case (ii))update-complete:resolves=<target-ref>→ completion marker asserting an in-place update has landed (consumed by SUPERSEDED case (ii) as the source-side completion signal)updated-by:<source-ref>→ back-reference asserting this node has been updated by the named source (consumed by SUPERSEDED case (ii) as the target-side completion signal)
- Excludes any comment whose start position falls inside a
strip_code_blocksregion - The verb vocabulary is reserved; malformed
<!-- blocked-by: foo bar -->(space inside value) emits a low-severitySCHEMA_VIOLATION / CROSS_FIELD_INVARIANTfinding via §02.1’s body scanner (invariant: HTML-comment metadata follows the grammar)
- Matches
-
Subsection close-out (02.0) — MANDATORY before starting 02.1:
- Write failing test fixtures FIRST (TDD) — one per
SourceKind×NodeKindcell that §02.1/§02.2 will consume —tests/plan-audit/test_dag.py(27 tests, all classes) +test_dag_types.py(16 tests). Tests written before implementation; verified failing atImportError: cannot import name 'SourceKind'before types.py/dag.py were written. - All tasks above are
[x]and unit tests forNodeId,SourceKind,strip_code_blocks,extract_yaml_comments,parse_html_comments, andnormalize_subsystempass — 141 plan-audit tests green (timeout 60 python -m pytest tests/plan-audit/ -q) - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — No tooling gaps surfaced. pytest covered every case cleanly; fixture-authoring fortest_dag.pywas friction-free (inline strings sufficed, no need fortests/plan-audit/fixtures/dag/subdir yet). §02.1 will need real markdown fixture files — if friction appears there, capture at §02.1 close-out. - Run
/sync-claudeon THIS subsection — No CLAUDE.md / rules / canon drift:dag.pyis a new module that importsSourceKindfromtypes.py(canonical home perimpl-hygiene.md§SSOT);NodeKindis an internal mapping toFileClasswith no user-visible surface. No new scripts/conventions to document globally..claude/rules/canon.md§6 SSOT table does not need to listdag.pyyet — it is still §02-internal; §02.N sweep will add a row onceDagReportis part of the public API.
- Write failing test fixtures FIRST (TDD) — one per
02.1 DAG Construction
File(s): scripts/plan_corpus/dag.py (same module as §02.0; build_dag(corpus: Corpus) -> Dag is the entry point).
Build a directed acyclic graph where nodes are instances of all seven §01.2 schema classes and edges come exclusively from EXPLICIT_DEPENDS_ON references. Body-text and HTML/YAML-comment references are collected as Reference records but are NOT promoted to edges — they feed the MISSING_DEPENDENCY classifier in §02.2.
-
Populate nodes from
Corpus:- One
NodeId(NodeKind.PLAN_INDEX, path)per entry incorpus.indexes - One
NodeId(NodeKind.PLAN_SECTION, path)per entry incorpus.plan_sections - One
NodeId(NodeKind.ROADMAP_SECTION, path)per entry incorpus.roadmap_sections - One
NodeId(NodeKind.OVERVIEW, path)per entry incorpus.overviews - One
NodeId(NodeKind.BUG_TRACKER_SECTION, path)per entry incorpus.bug_sections - One
NodeId(NodeKind.FIX_BUG, path)per entry incorpus.fix_bug_files - One
NodeId(NodeKind.COMPLETED_INDEX, path)per entry incorpus.completed_indexes - Every node’s metadata includes (a) its file-class
statusfield, (b)reviewed, (c)goal(if present), (d) parent-plan index for section-class nodes
- One
-
Parse explicit dependencies from
depends_onfrontmatter fields (consumes §01.3 SSOT — no path-based resolution):- §01’s parser has already validated
depends_onas logical-ID values (DepId): intra-plan"NN"or cross-plan"plan-name#NN". Full paths are rejected at parse time and never reach §02. - Resolve every
DepIdthroughplan_corpus.docgen.resolve_dep(dep_id, source_plan_dir, corpus)which usesCorpus.name_indexto mapplan-name#NN→ target plan directory → target section file. Intra-plan"NN"resolves within the source plan. - If
resolve_depreturns aFinding(DEAD_REFERENCE or SCHEMA_VIOLATION), §02 propagates it unchanged — §02 does not re-validate. - If
resolve_depreturns aPath, create aReference(source_kind=SourceKind.EXPLICIT_DEPENDS_ON, source_line=<depends_on item line>, ...)and promote it to anEdgefrom the declaring node to the resolved node. - NOTE (handoff to §03 — Finding K):
resolve_depcurrently returnssource=plan_dir / "index.md"on unresolved cross-plan names andsource=plan_diron missing section files (seescripts/plan_corpus/docgen.py:54,85). For §03 SafeFix to remove the exact offendingdepends_onentry, §02 must enrich the returned Finding withsource_line(the YAML line of the offending list item) andevidence=(dep_id,). Add anenrich_resolve_dep_finding(finding, dep_id, dep_line) -> Findinghelper that rebuilds the Finding with the precise location.
- §01’s parser has already validated
-
Collect body-text, HTML-comment, and YAML-comment references (NO edges created):
- For each node’s body (
ValidatedFile.body):- Compute
code_block_regions = strip_code_blocks(body)once - For each
plans/*/path match that falls OUTSIDE the code-block regions, emit aReference(source_kind=SourceKind.PROSE_VERB, ...)if preceded by a verb from theDEPENDENCY_VERBSset, otherwisesource_kind=SourceKind.PROSE_VERBwithverb=None(informational, does not feed MISSING_DEPENDENCY). - Define
DEPENDENCY_VERBS = frozenset({"depends on", "requires", "blocked by", "prerequisite", "unblocks", "supersedes", "rewrites", "rewrite of", "obsoletes", "update in place of", "update of", "reprioritize", "reorder"})— includes explicit rewrite markers (“rewrites X”, “rewrite of X”) AND in-place-update verbs from the live corpus (“update X in place of”, “reprioritize X”, “reorder X”). Without these extended verbs, test case (c) is unimplementable —plans/test-suite-health/section-02-roadmap-reprioritization.mduses “update 21A” / “reprioritize”, never “rewrites”. TPR-02-002-gemini semantic pin: the phrase “rewrite of” must be matched in addition to “rewrites”. - Define
INFORMATIONAL_VERBS = frozenset({"see also", "related", "inspired by", "cf."})(no edge implication). - Call
parse_html_comments(body)and collect its references withSourceKind.HTML_COMMENT_CONVENTION.
- Compute
- For each node’s frontmatter raw text (reconstructed from the first
---…---region of the file):- Call
extract_yaml_comments(text, body_offset)and scan each comment for anyDEPENDENCY_VERBSmember ORplans/*/path OR bug ID (BUG-\d{2}-\d{3}); matches becomeReference(source_kind=SourceKind.YAML_COMMENT, ...).
- Call
- Scope constraint: this body-fact extractor reads
ValidatedFile.bodyand raw frontmatter only. It does NOT re-parse the frontmatter mapping (§01’s parser is the SSOT for that) and it does NOT re-validate schemas (§01’s validators are the SSOT). The helper is permitted to read the original file bytes ONLY for the raw-frontmatter comment scan.
- For each node’s body (
-
Map shared subsystems using the §02.0 normalization table:
- For each node, extract a set of subsystem references from its body (crate paths like
compiler/ori_arc, type names likeAIMS,ArcClassifier, etc.) and its success criteria. - Normalize every extracted token via
normalize_subsystem; dropNoneresults. - Build
subsystem_to_nodes: dict[str, set[NodeId]]. - Record this mapping on the
Dagobject — it is input to the CONFLICT and MISSING_DEPENDENCY classifiers (§02.2), NOT an edge source.
- For each node, extract a set of subsystem references from its body (crate paths like
-
Assemble the
Dagdataclass:nodes: set[NodeId]edges: list[Edge]— EXPLICIT_DEPENDS_ON onlyreferences: list[Reference]— all source kinds (used by classifiers)subsystem_to_nodes: dict[str, set[NodeId]]resolution_findings: list[Finding]— DEAD_REFERENCE / SCHEMA_VIOLATION findings returned byresolve_dep, propagated with enriched source_line per Finding Kname_index: dict[str, Path]— a reference toCorpus.name_indexfor classifier reuse- Provide
Dag.to_json() -> dictfor serialization; serialization is lossless round-trip
-
Detect and report cycles via Tarjan SCC:
- For each non-trivial SCC (size > 1 or self-loop), emit one
Finding(category=DAG_CONFLICT, subtype=CYCLE, severity=HIGH, dependency_chain=tuple(node.path for node in scc_path), source_kind=SourceKind.EXPLICIT_DEPENDS_ON)— cycles are edge-based so all members carry EXPLICIT_DEPENDS_ON. Chain is structured (typeddependency_chain), NOT string-formatted intoevidence— per Option A (TPR-02-006-codex + TPR-02-003-gemini-r3) - Cycles indicate a mutual dependency that cannot be resolved by execution order — severity is HIGH because the graph is unusable downstream
- For each non-trivial SCC (size > 1 or self-loop), emit one
-
Subsection close-out (02.1) — MANDATORY before starting 02.2:
- Write failing test fixtures FIRST (TDD): implemented as programmatic tmp_path mini-corpora in
tests/plan-audit/test_dag_construction.py(11 tests, all fixtures inline) — one mini-corpus per fixture name. Committing tiny .md fixtures would duplicate setup in multiple tests; inline tmp_path keeps the live corpus pristine.-
fixture_dag_basic—TestDagConstructionBasic::test_two_plans_with_depends_on_yields_one_edge -
fixture_dag_code_fence(Finding D) —TestDagConstructionCodeFence::test_code_fence_path_does_not_produce_reference -
fixture_dag_yaml_comment(Finding F) —TestDagConstructionYamlComment::test_yaml_comment_in_frontmatter_emits_reference -
fixture_dag_html_unblocks(Finding B, case (h)) —TestDagConstructionHtmlUnblocks::test_html_unblocks_comment_emits_html_reference -
fixture_dag_fix_bug_node(Finding C/G, case (g)) —TestDagNodeCoverage::test_fix_bug_file_appears_as_fix_bug_node -
fixture_dag_completed_index_node(Finding G, case (h)) —TestDagNodeCoverage::test_completed_index_appears_as_completed_index_node
-
- All tasks above are
[x]andbuild_dag(corpus)runs on the live corpus without Python exceptions — verified: 375 nodes, 338 edges, 441 references, 18 subsystems, 16 resolution findings, 1 cycle finding. - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — One tooling-surface improvement surfaced and landed in this subsection itself:classify_filewas refactored to anchor on the firstplans/segment in resolved paths instead of requiringPLANS_DIR, so synthetictmp_pathcorpora work in tests without patching globals. Backward-compatible (PLANS_DIR callers take the same branch as before). - Run
/sync-claudeon THIS subsection — No global CLAUDE.md / rules / canon drift.dag.pyconsumes types.py (canonical home);classify_filesignature is unchanged..claude/rules/canon.md§6 SSOT table will be updated at §02.N sweep onceDagReportjoins the public API surface.
- Write failing test fixtures FIRST (TDD): implemented as programmatic tmp_path mini-corpora in
02.2 Conflict Classifiers
File(s): scripts/plan_corpus/dag.py (classifier functions live alongside build_dag; each classifier is a pure function (dag: Dag) -> list[Finding]).
Implement the full classifier stack: the six mission classifiers plus REDUNDANT_DEPENDENCY and ORPHANED_PLAN informational classifiers. Classifier ordering and source-kind filtering are documented at the top of each classifier. Cross-classifier precedence lives in §02.4.
-
Classifier registration table (at module top):
CLASSIFIERS: list[Callable[[Dag], list[Finding]]]in execution order (per §02.4 precedence). Each classifier is pure and stateless. Therun_classifiers(dag) -> list[Finding]entry point walks the table in order.
-
CONFLICT — contradictory goals for the same subsystem:
- Input:
dag.subsystem_to_nodes+ node goals - For each shared subsystem with ≥2 nodes, for each pair
(A, B)of nodes:- Dependency-edge short-circuit (Finding G): if
Atransitively depends onB(or vice versa) via EXPLICIT_DEPENDS_ON edges, this is prerequisite coordination, NOT conflict. Skip the pair. - Otherwise compare their
goalstrings using a contradiction heuristic (shared subject noun-phrases + opposing verbs likeremove/keep,refactor/preserve,rewrite/retain). The heuristic returns a boolean + rationale string. - If contradictory → emit
Finding(category=DAG_CONFLICT, subtype=CONFLICT, severity=HIGH, source=A.path, target=B.path, description=..., recommended_fix="Decide precedence between the plans or add a depends_on edge to serialize them", evidence=(rationale,)).
- Dependency-edge short-circuit (Finding G): if
- Input:
-
SUPERSEDED — reroute claims OR in-place-rewrite claims with incomplete rewrites:
- Case (i) — reroute plan with
supersedeslist: for each plan index node withreroute: TrueAND non-emptysupersedes, check whether the reroute plan has at least one non-complete section targeting each supersedes entry (body text contains a reference withSourceKind.HTML_COMMENT_CONVENTIONverbsupersedesORrewrites, or a section goal substring-matches the supersedes entry’s logical ID/title). If not → emit finding. - Case (ii) — in-place-update claim (Finding A, broadened per TPR-02-002-codex round 1; de-phase-leaked per TPR-02-002-codex round 2): for each plan section whose body prose contains ANY in-place-update verb from the extended
DEPENDENCY_VERBS—"rewrites","rewrite of","update in place of","update of","reprioritize","reorder","supersedes","obsoletes"— where the following noun-phrase resolves viaresolve_depto another plan/roadmap section, emit a structural SUPERSEDED finding based purely on static facts §02 can observe WITHOUT timestamps. Structural detection criteria (no mtime, no git queries — §02 stays pure per 00-overview:27):- (a) the target section exists in the DAG, AND
- (b) the source section’s claim is NOT annotated with a structural completion marker
<!-- update-complete:resolves=<target-ref> -->(HTML comment, parsed by §02.0’sparse_html_comments), AND - (c) the target section does NOT contain a structural back-reference to the source claim (HTML comment
<!-- updated-by:<source-ref> -->or explicitdepends_onentry pointing at the source). - If all three hold → emit a MEDIUM-severity structural SUPERSEDED finding flagged as “potentially stale — needs §03 timestamp verification.”
- §03 specialization (phase handoff, aligned with §01.3 SSOT per TPR-02-002-codex-r3, updated per TPR-03-002-codex-r2): §03’s write-back phase receives §02’s structural SUPERSEDED findings. §03 routes ALL SUPERSEDED findings to ExposureReview (never SafeFix) — SUPERSEDED resolution is inherently semantic (the user must decide whether the reroute claim is valid, stale, or in progress). The
evidence = ("git_status:stale_rewrite", ...)/evidence = ("git_status:likely_landed", ...)enrichment from §02’s structural check is preserved as advisory/reporting context in the ExposureReview finding’s evidence field — it helps the human reviewer but does NOT drive automatic write-back.WriteBackContext.has_recent_commitsis available for future SafeFix graduation if a narrow, safe subcase is identified, but no such subcase is implemented in this iteration. This keeps §01.3’s taxonomy stable while preserving the §02-pure / §03-git-aware split pattern. The key architectural property is preserved: §02 emits a structural fact with advisory git enrichment, §03 routes to ExposureReview for human decision. - Rationale for the split:
scripts/plan_corpusis a pure library (00-overview.md:27). Embedding git queries indag.pywould break the phase boundary and the no-I/O contract. The structural check is the LEAK-free §02 signal; §03’s write-back phase is where git context legitimately enters. - Known case (§05.2 (c)):
plans/test-suite-health/section-02-roadmap-reprioritization.mdclaims to update/reprioritizeplans/roadmap/section-21A-llvm.mdbut the update has not landed.plans/test-suite-health/index.md:2IS a reroute (reroute: true), but itssupersedesfield is absent ANDplans/test-suite-health/00-overview.md:5hassupersedes: []— so case (i) cannot fire. Case (ii) detects this via the extended verb vocabulary (“update”, “reprioritize”). - Emit
Finding(category=DAG_CONFLICT, subtype=SUPERSEDED, severity=MEDIUM, ...); recommended_fix: “Complete the rewrite or remove the claim”.
- Case (i) — reroute plan with
-
BLOCKED — active node depends on queued/not-started node:
- Walk the
Dag.edgeslooking for edgesA → Bwhere:A.statusresolves to “active-equivalent” (plan-levelactive, section-levelin-progress) ANDB.statusresolves to “blocked-equivalent” (plan-levelqueuedorresearch, section-levelnot-started)
- Emit
Finding(category=DAG_CONFLICT, subtype=BLOCKED, severity=HIGH, source=A.path, target=B.path, ...). - NOTE on cases (a) AND (g) — cross-plan DRIFT with §05.2 (per TPR-02-001-codex round 2 and round 1): Both known cases currently fail the BLOCKED SSOT contract because the live corpus has no matching
depends_ondeclarations:- Case (a):
plans/repr-opt/index.md:1-8has nodepends_onfield. The realname:values are"Repr Opt"and"Locality SSOT"(verified), not"Locality Representation Unification". For §02.2 BLOCKED to fire on case (a),plans/repr-opt/index.mdmust first adddepends_on: ["Locality SSOT#NN"]pointing at the specific section withinplans/locality-representation-unification/that gates it (probably"Locality SSOT#02"). Without that edit, repr-opt’s body-prose references to locality-representation-unification feed MISSING_DEPENDENCY, NOT BLOCKED. - Case (g):
plans/bug-tracker/fix-BUG-04-039.md:5records the blocker ONLY via a YAML frontmatter comment (status: in-progress # ... plans/iterator-element-ownership/), NOT viadepends_on. Per §02.0’s SSOT rule (EXPLICIT_DEPENDS_ON is the sole edge source), the YAML-comment reference emits MISSING_DEPENDENCY, NOT BLOCKED. Only once the author addsdepends_on: ["Iter Ownership#NN"](thename:field value verified atplans/iterator-element-ownership/index.md:3— NOT the directory slugiterator-element-ownership; §01 rejects slug-style cross-plan IDs) does BLOCKED replace MISSING_DEPENDENCY.
- Case (a):
- Pre-implementation prerequisite — mandatory corpus edits: §02 acceptance requires at least one of these routes for each case:
- Route A (preferred): the blocking-plan owners add the missing
depends_onfields toplans/repr-opt/index.mdandplans/bug-tracker/fix-BUG-04-039.mdbefore §02 implementation lands. These edits MUST use the exactname:field values from the target plan’sindex.md— verified viagrep '^name:' plans/*/index.md:"Locality SSOT"(forplans/locality-representation-unification/) and"Iter Ownership"(forplans/iterator-element-ownership/) — NOT the directory slugs, which §01’sDepIdvalidator rejects. Once the edges exist, BLOCKED fires on the live corpus and §05.2:113,143 remains accurate. - Route B (fallback): §05.2 reclassifies cases (a) and (g) as MISSING_DEPENDENCY (§02’s current behavior until route A lands). §02.3 validation-case block already reflects this — the first finding for each case is MISSING_DEPENDENCY; BLOCKED replaces it only after route-A edits.
- /tpr-review MUST reconcile with §05.2:113,143 (the real line numbers in
plans/verify-roadmap-redesign/section-05-validation.mdwhere “Expected: BLOCKED finding” is asserted for cases (a) and (g) respectively — corrected in round 4 per TPR-02-002-gemini-r4; the earlier “142,146” reference in round-2 trail is stale). Route A is preferred because thedepends_onedges are real corpus hygiene improvements; route B is a valid fallback if the blocking-plan owners cannot edit in time.
- Route A (preferred): the blocking-plan owners add the missing
- Status resolution helper (per TPR-02-005-codex): because §01.4 owns the canonical status normalizer, §02.2 imports
plan_corpus.normalizer.normalize_status(data, body, path, child_statuses) -> NormalizedStatus(seescripts/plan_corpus/normalizer.py:70-138) and usesNormalizedStatus.derived(NOT rawdata["status"]) for BLOCKED edge classification. For plan-index nodes, §02 passes each child section’s declared status aschild_statuses; for leaf/section nodes, §02 passesNoneand the normalizer derives from body signals. There is noeffective_status()wrapper and §02 MUST NOT invent one — LEAK:scattered-knowledge guard perimpl-hygiene.md§SSOT. If §02 needs a one-argument convenience (e.g.derived_for(node)), it lives as a private_derived_for(node)helper indag.pythat composesnormalize_statusinputs from the node’s frontmatter and children; the composition helper is never re-exported as a public API becausenormalize_statusitself IS the public API.
- Walk the
-
STATUS_CONTRADICTION / CROSS_EDGE_TEMPORAL_DRIFT — DAG-level status drift:
- §01.4’s
normalize_status()already produces per-fileSTATUS_CONTRADICTIONfindings (FM_DECLARED_VS_BODY_DERIVED,PLAN_ACTIVE_ALL_SECTIONS_NOT_STARTED, etc.). §02 consumes those facts; the DAG classifier adds ONLY the cross-edge subtype. - Walk
Dag.edgesand emitFinding(category=STATUS_CONTRADICTION, subtype=CROSS_EDGE_TEMPORAL_DRIFT, severity=MEDIUM)when a dependent node’s declared status presupposes a state its prerequisite has not reached — e.g. dependent iscompletebut prerequisite isnot-started; dependent isin-progressbut prerequisite isnot-startedand its plan isqueued. TPR_STALE_VS_EDITis NOT emitted from §02. It requires git commit timestamps (mtime is broken per Finding M) and is specialized by §03’s write-back phase from a §02-emitted neutralCROSS_EDGE_TEMPORAL_DRIFT(ifWriteBackContext.has_recent_commitsis available).
- §01.4’s
-
MISSING_DEPENDENCY — body-inferred reference without a matching
depends_onedge:- For each
Referencewithsource_kindin{PROSE_VERB, HTML_COMMENT_CONVENTION, YAML_COMMENT}and verb inDEPENDENCY_VERBS(excluding informational verbs):- Resolve the target the same way §02.1’s
resolve_depdoes - If the resolved target is a node in the DAG AND no
EXPLICIT_DEPENDS_ONedge exists fromfrom_nodeto the target → emitFinding(category=DAG_CONFLICT, subtype=MISSING_DEPENDENCY, severity=MEDIUM, source=from_node.path, source_line=reference.source_line, target=target.path, source_kind=reference.source_kind, evidence=(reference.raw_text,))—source_kindgoes into the typed field (Option A),evidencecarries only the short raw-text excerpt for human inspection.
- Resolve the target the same way §02.1’s
- Also walk
subsystem_to_nodes(§02.1 map) for pairs of plans sharing a subsystem with BOTH in active/in-progress status AND noEXPLICIT_DEPENDS_ONedge between them — interference risk. Emit MISSING_DEPENDENCY for each unordered pair. - Recommended_fix: “Add
depends_on: [...]entry referencing the inferred target”.
- For each
-
DEAD_REFERENCE — pointer to nonexistent plan/directory/spec:
- Input sources (all filtered through
SourceKind, code-fence examples excluded):dag.resolution_findings(DEAD_REFERENCE findings from §02.1)- For each
Referencewithsource_kindin{PROSE_VERB, HTML_COMMENT_CONVENTION, YAML_COMMENT}that names aplans/*/path, check filesystem existence - A reference inside a
CODE_FENCE_EXAMPLEregion is NEVER promoted to DEAD_REFERENCE (Finding D semantic pin —plans/bug-tracker/00-overview.md:23,30,50,71andplans/locality-representation-unification/section-02-ori-arc-implementation.md:253,692,821contain template paths inside fences that must not false-positive). - A reference inside a YAML_COMMENT or HTML_COMMENT_CONVENTION that names a plan directory missing from disk IS promoted to DEAD_REFERENCE at lower severity (
LOWvsHIGHfor frontmatter references).
- Known cases (§05.2 (b), (h)):
- (b)
plans/roadmap/section-22-tooling.md:141referencesplans/ori_lsp/(must verify exact line at runtime;plans/ori_lsp/does not exist on disk) - (h)
plans/roadmap/section-21A-llvm.md:83—<!-- unblocks:jit-exception-handling/04B,05,06 -->and prose “would unblock completion of the JIT Exception Handling plan” — the target is a completed plan (its entry lives underplans/completed/jit-exception-handling/, notplans/jit-exception-handling/). DEAD_REFERENCE must resolve against BOTHplans/<slug>/andplans/completed/<slug>/— if found only incompleted, emit a LOW severity DEAD_REFERENCE recommending the annotation be updated or removed (not a hard error — completed plans are real).
- (b)
- Emit
Finding(category=DEAD_REFERENCE, subtype=PLAN_DIRECTORY_NOT_FOUND | SECTION_FILE_NOT_FOUND | CROSS_PLAN_NAME_NOT_FOUND | SPEC_FILE_NOT_FOUND, severity=HIGH for EXPLICIT_DEPENDS_ON, MEDIUM for HTML_COMMENT/YAML_COMMENT, LOW for PROSE_VERB). Severity ladder encodes source-kind trust.
- Input sources (all filtered through
-
REDUNDANT_DEPENDENCY (informational) — A depends on B, A depends on C, and B (transitively) depends on C:
- Compute transitive closure of
dag.edges. For each edgeA → C, if there exists a nodeBsuch thatA → BandB →* C, theA → Cedge is redundant. - Emit
Finding(category=DAG_CONFLICT, subtype=REDUNDANT_DEPENDENCY, severity=LOW, source=A.path, target=C.path, dependency_chain=tuple((A.path, B.path, C.path)), source_kind=SourceKind.EXPLICIT_DEPENDS_ON)— chain is structured (typeddependency_chain), NOT string-flattened intoevidence— per Option A (TPR-02-002-gemini-r3). - Check:
plans/verify-roadmap-redesign/00-overview.md:137— Section 04 depends on01, 02, 03; Section 05 depends on01, 02, 03, 04. Since 02 transitively depends on 01 (02 → 01), 04’s direct edge 04 → 01 is NOT redundant by this definition (redundancy requires the shorter path to be shorter than the longer one AND not a user-friendly explicit declaration). The classifier SHOULD be conservative: emit only when the redundant edge’s target appears in a dependency of a dependency’s dependency — depth ≥ 3 transitive chain. Depth-2 redundancies (A → B → C + A → C) are emitted at LOW severity because they may be intentional readability declarations.
- Compute transitive closure of
-
ORPHANED_PLAN (informational) — plan with no incoming or outgoing dependency edges AND not actively superseding anything:
- For each
PLAN_INDEXnode, compute in-degree and out-degree indag.edges. - If in-degree == 0 AND out-degree == 0 AND the plan’s
supersedeslist is empty AND the plan is not referenced by any body-inferredReference→ emitFinding(category=DAG_CONFLICT, subtype=ORPHANED_PLAN, severity=LOW, ...). - Candidate:
plans/rosetta-stress-test/may qualify — must be verified at runtime. - Recommended_fix: “Verify this plan is still needed; file a bug if it is truly abandoned.”
- For each
-
Classifier subtypes not declared in §01.3 require a §01.3 extension request:
REDUNDANT_DEPENDENCYandORPHANED_PLANare NEW subtypes that must be added toFindingCategory.DAG_CONFLICTinscripts/plan_corpus/types.pyFindingSubtypeand_CATEGORY_SUBTYPES[FindingCategory.DAG_CONFLICT].- NOTE for /tpr-review: because §01 is complete (reviewed: true in its frontmatter), this extension is filed as a scope NOTE here. Implementation sequencing: when §02 work begins, the first commit adds the two subtypes to
types.py(§01.3’s file) with a comment# Added by §02.2 for REDUNDANT_DEPENDENCY / ORPHANED_PLAN classifiers. This is the only permitted cross-section edit during §02 execution.
-
Subsection close-out (02.2) — MANDATORY before starting 02.3:
- Write failing test fixtures FIRST (TDD) — one per classifier × each live-corpus source-kind pattern:
fixture_conflict_shared_subsystem.yaml— semantic pin: CONFLICT fires on unrelated plans sharing a subsystem; negative pin: CONFLICT does NOT fire when A depends on B (prereq coordination — Finding G)fixture_superseded_reroute.yaml— semantic pin for case (i);fixture_superseded_inplace.yaml— semantic pin for case (ii) exactly mirroring test-suite-health §05.2 (c)fixture_blocked_live.yaml— semantic pin for §05.2 (a) — and for (g) ONLY after route-A corpus edits land (§02.2 BLOCKED NOTE on cases (a)/(g)). Until the corpus edits land, cases (a) AND (g) are caught as MISSING_DEPENDENCY; cover that path infixture_missing_dep_yaml_comment.yaml+ a newfixture_missing_dep_repr_opt_locality.yamlthat reproduces the case-(a) body-prose MISSING_DEPENDENCY scenario (per TPR-02-002-gemini round 2). Negative pin: no BLOCKED when both active.fixture_cross_edge_temporal_drift.yaml— semantic pin: complete plan depends on not-started plan → finding; negative pin: both complete → no findingfixture_missing_dep_prose.yaml— semantic pin: prose “depends on plans/X/” without frontmatter depends_on emits MISSING_DEPENDENCY; negative pin: with matching depends_on, no findingfixture_missing_dep_html_comment.yaml— semantic pin for<!-- blocked-by:X -->when X is not in depends_onfixture_missing_dep_shared_subsystem.yaml— semantic pin (TPR-02-003-gemini): two active plans share a subsystem viasubsystem_to_nodesmapping BUT neither has adepends_onedge to the other AND their goals are non-contradictory (CONFLICT short-circuited) → emits MISSING_DEPENDENCY for each unordered pair; negative pin: same two plans with an EXPLICIT_DEPENDS_ON edge between them → no MISSING_DEPENDENCY findingfixture_dead_ref_direct.yaml— semantic pin for §05.2 (b) (plans/ori_lsp/ in roadmap 22.2)fixture_dead_ref_unblocks_completed.yaml— semantic pin for §05.2 (h) (Section 21A unblocks JIT Exception Handling, which lives under plans/completed/)fixture_dead_ref_code_fence_negative.yaml— NEGATIVE PIN (Finding D): bug-tracker fence-template path MUST NOT emit DEAD_REFERENCEfixture_dead_ref_yaml_comment.yaml— semantic pin: fix-BUG-04-039.md YAML comment → YAML_COMMENT reference → resolves to validplans/iterator-element-ownership/(no DEAD_REFERENCE); mutant fixture: YAML comment pointing at missing dir → LOW-severity DEAD_REFERENCEfixture_redundant_dep.yaml— semantic pin: A → B → C + A → C emits REDUNDANT_DEPENDENCYfixture_orphaned_plan.yaml— semantic pin: standalone plan with no references → ORPHANED_PLAN; negative pin: same plan referenced by another plan’s body → no finding
- All 8 classifiers pass against every live-corpus fixture; failing fixtures predate the implementation
- All tasks above are
[x]and classifiers produce findings on the live corpus - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection - Run
/sync-claudeon THIS subsection — check whether changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no changes, document briefly. Fix any drift NOW.
- Write failing test fixtures FIRST (TDD) — one per classifier × each live-corpus source-kind pattern:
02.3 Priority Inversion Detection
File(s): scripts/plan_corpus/dag.py (integrated into BLOCKED classifier; detect_priority_inversions(dag) -> list[Finding] entry point).
Specialized analysis that extends the BLOCKED classifier (§02.2) with transitive chain reporting and root-blocker identification. The topological-sort recommendation from the prior §02.3 draft is CUT here and re-scoped as a §03 consumer — see NOTE below.
-
Implement transitive priority inversion detection:
- For each BLOCKED finding from §02.2, compute the full dependency chain
A → B → ... → root_blockervia DFS onDag.edges. root_blocker= the deepest queued/not-started node in the chain.- Emit one Finding per chain using Option A (typed boundary per TPR-02-006-codex and TPR-02-004-gemini): populate
Finding.dependency_chain: tuple[Path, ...]andFinding.source_kind: SourceKind— both fields added toscripts/plan_corpus/types.pyas part of §02.N’s cross-section extension (see §02.5 Concern I). Theevidencefield is reserved for short textual notes; structured chain data MUST NOT be flattened into strings across the §02→§03 phase boundary (EXPOSURE guard perimpl-hygiene.md). - Replace the §02.2 BLOCKED finding with the chain-enriched version (dedup by
Finding.id).
- For each BLOCKED finding from §02.2, compute the full dependency chain
-
Identify the minimum unblock set:
- For each connected component of BLOCKED findings, compute the minimum set of nodes whose status change would unblock the chain.
- Emit using the existing
FindingCategory.DAG_CONFLICT / BLOCKEDsubtype with the typedFinding.dependency_chain: tuple[Path, ...]field carrying the unblock-set paths (per TPR-02-001-gemini-r4 — no evidence-tuple residue).source_kind=SourceKind.EXPLICIT_DEPENDS_ONbecause the unblock-set members are on dependency edges.evidenceis reserved for short human-readable rationale ONLY; structured unblock-set data goes independency_chain. - NOTE for /tpr-review: the “minimum unblock set” is a §03 consumer concern — §03’s report groups BLOCKED findings by shared
dependency_chainprefixes and presents the root blocker as a single actionable item. §02 computes the data as a typed field; §03 reads the typed field and renders.
-
CUT (Finding L): topological-sort execution-order recommendation is NOT emitted by §02.
- The prior §02.3 draft computed a topological sort and labeled plans “active out of order”. §03’s
/continue-roadmapintegration only consumes BLOCKED/CONFLICT/DEAD_REFERENCE quick-checks; §04 consumes flagged sections. There is no §03 or §04 consumer for a topo-sort output. - The topo-sort code is CUT from §02 entirely (per TPR-02-005-gemini — no soft-deferral, no “just-in-case” architectural concessions).
dag.edgesremains because real classifiers consume it (BLOCKED, REDUNDANT_DEPENDENCY, CONFLICT short-circuit); the transitive closure is computed inside REDUNDANT_DEPENDENCY’s classifier body, not exported as a general-purpose structure. If a future plan proposes topo-sort rendering, the new plan specifies its consumer and may extend §02 via a proper new subsection at that time — the proposal workflow is the gating mechanism, not speculative scaffolding.
- The prior §02.3 draft computed a topological sort and labeled plans “active out of order”. §03’s
-
Validate against known test cases (concrete assertions for §05.2):
- Test case (a):
plans/repr-opt/index.mdactive, body-prose referencesplans/locality-representation-unification/queued. Mirrors case (g): in the CURRENT corpus state,plans/repr-opt/index.mdhas NOdepends_onentry → emits MISSING_DEPENDENCY (not BLOCKED). Once the author addsdepends_on: ["Locality SSOT#NN"](the name-based cross-plan ID fromplans/locality-representation-unification/index.md:name; directory slug REJECTED by §01’s DepId validator), re-runs produce a BLOCKED finding with chainRepr Opt → Locality SSOT. §02.2’s BLOCKED “Known cases” block documents this as the route-A/route-B split. - Test case (g):
plans/bug-tracker/fix-BUG-04-039.mdin-progress, YAML_COMMENT referencesplans/iterator-element-ownership/as blocker → emits a MISSING_DEPENDENCY (no frontmatterdepends_onentry) AND, once the author addsdepends_on: ["Iter Ownership#NN"](the name-based cross-plan ID; directory slugiterator-element-ownershipis REJECTED by §01’s DepId validator), re-runs produce a BLOCKED finding. §02 does NOT auto-promote YAML comments to edges (per §02.0 SSOT rule). - Test case (b): DEAD_REFERENCE for
plans/ori_lsp/inplans/roadmap/section-22-tooling.md. - Test case (c): SUPERSEDED (case (ii) — in-place-rewrite-claimed-but-not-done) for
plans/test-suite-health/section-02-roadmap-reprioritization.md→plans/roadmap/section-21A-llvm.md. - Test case (h): DEAD_REFERENCE (LOW severity, HTML_COMMENT_CONVENTION source) for
plans/roadmap/section-21A-llvm.md:83<!-- unblocks:jit-exception-handling/04B,05,06 -->— target resolves toplans/completed/jit-exception-handling/→ annotation is stale.
- Test case (a):
-
Subsection close-out (02.3) — MANDATORY before starting 02.4:
- Write failing test fixtures FIRST for each known test case (a), (b), (c), (g), (h) — one fixture file per case, reproducing the exact live-corpus scenario
- All tasks above are
[x]and known test cases validate - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — No tooling gaps. detect_priority_inversions reuses classify_blocked + _node_status cache; recursion depth limit is a risk on deep chains but the DFS has cycle avoidance viaif nxt in path: continue. - Run
/sync-claudeon THIS subsection — No global rules drift. Priority inversion remains §02-internal until §02.5 exposes DagReport.
02.4 Classifier Precedence & Determinism Tests
File(s): scripts/plan_corpus/dag.py (precedence ordering in CLASSIFIERS list); tests/plan-audit/test_classifier_precedence.py (pytest tests).
Classifiers can fire on the same (source, source_line) pair. Without explicit precedence, the classifier stack is non-deterministic. This subsection makes ordering explicit, tested, and auditable.
-
Document the precedence ladder (highest priority first):
PARSE_ERROR(§01 category) — fatal; if a file cannot be parsed, no §02 classifier runs against it. §02 consumesCorpus.gapsfrom §01 and skips any node whose source path is in the gaps.DEAD_REFERENCE— if a reference doesn’t resolve, no other DAG classifier fires against that reference. A broken edge is not a “conflict with B”, it is “B does not exist.”DAG_CONFLICT / CYCLE— structural graph error; takes precedence over BLOCKED because a cycle makes BLOCKED meaningless.DAG_CONFLICT / BLOCKED— active-depends-on-queued; high-value finding.DAG_CONFLICT / CONFLICT— subsystem overlap with contradictory goals.DAG_CONFLICT / SUPERSEDED— rewrite-claimed-but-not-done.STATUS_CONTRADICTION / CROSS_EDGE_TEMPORAL_DRIFT— informational drift.DAG_CONFLICT / MISSING_DEPENDENCY— body-inferred reference without explicit edge (last because it requires all other classifiers to have resolved their references first).DAG_CONFLICT / REDUNDANT_DEPENDENCY— LOW severity informational.DAG_CONFLICT / ORPHANED_PLAN— LOW severity informational.
-
Implement precedence via a deduplication pass:
- After all classifiers run, group findings by
(source, source_line, target). - Within each group, keep ONLY the highest-precedence finding; emit the rest as
evidenceon the kept finding under keysuppressed_by_precedence. - A finding’s precedence rank is looked up from the ladder above via
PRECEDENCE_RANK: dict[FindingSubtype, int].
- After all classifiers run, group findings by
-
Source-kind precedence in the severity ladder:
- EXPLICIT_DEPENDS_ON references produce HIGH severity
- HTML_COMMENT_CONVENTION and YAML_COMMENT references produce MEDIUM severity
- PROSE_VERB references produce LOW severity
- CODE_FENCE_EXAMPLE references produce NO findings (excluded before classifier runs)
-
Write TDD determinism tests:
test_precedence_dead_ref_beats_missing_dep: a prose reference toplans/nonexistent/emits exactly one DEAD_REFERENCE, NO MISSING_DEPENDENCYtest_precedence_cycle_beats_blocked: a cyclic dependency emits exactly one CYCLE, NO BLOCKED for the members of the cycletest_precedence_parse_error_suppresses_all: a file with a PARSE_ERROR finding incorpus.gapsdoes NOT appear as a node; no DAG classifier firestest_source_kind_severity_ladder: same DEAD_REFERENCE target emitted at HIGH / MEDIUM / LOW depending on source kindtest_code_fence_no_false_positive_with_fixture:fixture_dead_ref_code_fence_negative.yamlruns all 8 classifiers, zero findingstest_deterministic_order: runningrun_classifiers(dag)twice produces identical output (list equality, including order)
-
Subsection close-out (02.4) — MANDATORY before starting 02.5:
- Write failing determinism tests FIRST; verify they fail before implementation
- All tasks above are
[x]and determinism tests pass on the live corpus - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — No tooling gaps. apply_precedence uses deterministic sort keys and stable tie-break on Finding.id; testability is strong via synthetic Finding instances (no need for live corpus). - Run
/sync-claudeon THIS subsection — No global rules drift.
02.5 Handoff Contract with §03
File(s): scripts/plan_corpus/dag.py (entry-point dag_report(dag) -> DagReport dataclass); shared with §03 via stable DagReport.to_json() schema.
§03 consumes §02’s output. Three concrete schema concerns must be resolved here so §03 can drive SafeFix / ExposureReview classification without re-parsing §02’s output. Each resolution is filed as a NOTE for /tpr-review to triage against §01.3’s Finding schema.
-
Concern I — multi-hop transitive chains (Finding I), resolved per TPR-02-006-codex + TPR-02-004-gemini:
scripts/plan_corpus/types.py:202definesFindingwith anevidence: tuple[str, ...]field. No structural field for chains.- §02 choice: Option A — extend
Findingwith typed fields. Add TWO optional fields to theFindingdataclass:dependency_chain: tuple[Path, ...] = ()— ordered sequence of node paths for multi-hop transitive findings (BLOCKED chains, CYCLE paths, REDUNDANT_DEPENDENCY triples). Empty tuple for non-chain findings.source_kind: SourceKind | None = None— the classification of the reference that produced the finding (EXPLICIT_DEPENDS_ON, HTML_COMMENT_CONVENTION, YAML_COMMENT, PROSE_VERB, CODE_FENCE_EXAMPLE).Nonefor findings that do not correspond to a specific reference (e.g. ORPHANED_PLAN).
- Rationale: Option C (flattening structured data into strings across the §02→§03 phase boundary) is an EXPOSURE violation per
impl-hygiene.md— §03 would have to string-parse to recover structure, creating a scattered-knowledge LEAK. Option A keeps the data typed across the boundary. Option B (multi-Finding stitching via chain-id) works but forces §03 to re-assemble chains from shared-key groups, which is slower and harder to audit. Option A is the only choice that preserves both SSOT (all chain data lives in one dataclass) and phase-boundary purity (§03 reads typed fields, no re-parsing). - Cross-section edit authorization: §02 adds these two fields to
scripts/plan_corpus/types.pyas part of §02.N’s sweep (alongside theREDUNDANT_DEPENDENCYandORPHANED_PLANsubtype additions already authorized at §02.2 “Classifier subtypes not declared in §01.3”). This is a non-breaking extension — defaults preserve backward compatibility with §01’s existing call sites. /tpr-review ratifies or rejects the extension during §02 execution; the fallback if rejected is Option B (multi-Finding chain-id stitching), NOT Option C.
-
Concern J — Finding.id collision risk (Finding J):
scripts/plan_corpus/types.py:221hashescategory + subtype + source + source_line. Multipledepends_onentries on one line collide.- §02 mitigation — add
source_columntoReference(§02.0) and thread it through to Finding construction so collisions on the same line get distinct ids. - Cross-section edit: the full §01.3 extension sweep — adding
dependency_chain,source_kind, AND rebasingFinding.idto hash(category, subtype, source, source_line, source_column, target)— all land together in §02.N’s authorized sweep. The disambiguator is structural (extend the hash inputs tosource_column+target), not a string-encoded workaround inevidence. Backward compatibility is preserved: the defaultsource_column = Noneand defaulttarget = Nonefor findings that do not carry them mean legacyFinding.idvalues are unchanged for fields that never populatedsource_columnortarget.
-
Concern K — precise source-file/line for
depends_onSafeFix (Finding K):resolve_depreturnssource = plan_dir / "index.md"orsource = plan_diron failure. §03 SafeFix needs the exact YAML line of thedepends_onlist item.- §02 mitigation — enrich
resolve_depfindings post-facto inbuild_dag: for eachdepends_onentry, track its YAML line via ayaml_lines: dict[str, int]map (computed by scanning frontmatter raw text for thedepends_on:block and counting list items). Rebuild the Finding withsource = <declaring_file>,source_line = yaml_line,evidence = (dep_id,). enrich_resolve_dep_finding(finding, dep_id, yaml_line, declaring_file)is the helper (introduced in §02.1).- Result: §03 SafeFix can apply
remove_yaml_list_entry(source, source_line, dep_id)without further parsing.
-
Concern —
source_kindas first-class facet (Finding P), aligned with Concern I Option A:- Every Finding emitted by §02 sets
Finding.source_kind: SourceKind | Noneas a typed field on the canonicalFindingdataclass (added toscripts/plan_corpus/types.pyper Concern I Option A above — single authorization, both extensions land together in §02.N’s cross-section sweep). - §03 reads
finding.source_kinddirectly (no string parsing, noevidence-based protocol) and routes:EXPLICIT_DEPENDS_ONDEAD_REFERENCE → SafeFix (mechanical list-entry removal);PROSE_VERBDEAD_REFERENCE → ExposureReview (requires human-authored replacement);HTML_COMMENT_CONVENTION/YAML_COMMENT→ MEDIUM-severity mid-tier routing. - Evidence-embedding protocol is REMOVED per TPR-02-003-codex round 2. The prior text saying “§02 embeds
source_kindinevidence[0]as\"source_kind:<VALUE>\"” is obsolete — it was a pre-Option-A bridge that creates a second source of truth and contradicts Option A’s typed-field contract. All §02-emitted findings use the typed field;evidenceis reserved for short human-readable context strings (spec citations, brief rationale) that do NOT encode structural data.
- Every Finding emitted by §02 sets
-
Define
DagReportdataclass:findings: list[Finding]— deduplicated, precedence-ordered, source-kind-taggeddag_json: dict—Dag.to_json()output for diagnostic usestats: dict[str, int]— per-classifier finding counts, source-kind counts, node countsto_json() -> dict— stable schema; §03 imports this
-
Subsection close-out (02.5) — MANDATORY before marking section complete:
- All tasks above are
[x]and §03 can importDagReportwith no re-parsing - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — No tooling gaps surfaced at DagReport assembly; the pipeline composes cleanly from §02.1-§02.4 primitives without new helpers. - Run
/sync-claudeon THIS subsection — DagReport is the public API surface §03 consumes. §02.N sweep will addscripts/plan_corpus/dag.pyto.claude/rules/canon.md§6 SSOT table. - Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
- All tasks above are
02.R Third Party Review Findings
Dual-source /tpr-review round 1 on 2026-04-14 (run /tmp/ori-tpr-5GmvSZCf). 11 actionable findings (codex 6, gemini 5, 0 agreements). All verified true against code and the live corpus; all fixed in the same pass.
-
[TPR-02-001-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:250— Align BLOCKED acceptance cases with the explicit-edge SSOT. Evidence: §02.0 makesEXPLICIT_DEPENDS_ONthe only edge source (body/YAML/HTML signals → MISSING_DEPENDENCY, never shadow edges), but §02.2’s BLOCKED classifier walkedDag.edgesand claimed case (g) (BUG-04-039 blocked byplans/iterator-element-ownership/via YAML frontmatter comment) among its known cases. Since case (g) has nodepends_onentry, no edge exists for BLOCKED to fire on — the reference feeds MISSING_DEPENDENCY. §05.2:146 classifies case (g) as BLOCKED, creating DRIFT. Impact: Case (g) was plan-unimplementable — BLOCKED classifier would never fire on a YAML-comment-only reference, so §02 would never catch the test case as classified by §05.2. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 in §02.2 BLOCKED bullet: case (g) explicitly moved to MISSING_DEPENDENCY path (SSOT-consistent); added NOTE documenting the §05.2:146 drift for /tpr-review to reconcile (either §05.2 updates case (g) → MISSING_DEPENDENCY, or BUG-04-039 author addsdepends_onbefore §02 acceptance). -
[TPR-02-002-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:241— Encode the real 21A stale-update pattern instead of rewrite verbs only. Evidence: SUPERSEDED case (ii) was specified with body-text triggersrewrites <target>/rewrite of <target>, butplans/test-suite-health/section-02-roadmap-reprioritization.mdnever uses those verbs — it uses “update 21A”, “reprioritize Section 21A”, and “reorder”. Live case (c) could not be detected with the documented verb vocabulary. Impact: Test case (c) would produce a false negative — the SUPERSEDED classifier would scan 21A-reprioritization body, find no “rewrites” verb, and emit no finding. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. ExtendedDEPENDENCY_VERBSfrozenset at §02:182 to include “rewrite of”, “update in place of”, “update of”, “reprioritize”, “reorder”, “obsoletes” alongside the original “rewrites”. Broadened §02.2 Case (ii) detection to use the full verb set with a three-part detection heuristic (target exists + target not edited since claim + no completion marker). -
[TPR-02-003-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:137— Accept hyphenated HTML comment targets from the live corpus. Evidence:parse_html_commentsregex<!--\s*(blocked-by|unblocks|supersedes|resolves)\s*:\s*([^ \t\r\n-]+(?:,[^ \t\r\n-]+)*)\s*-->uses character class[^ \t\r\n-]that excludes hyphens from target tokens. Live case (h) atplans/roadmap/section-21A-llvm.mdis<!-- unblocks:jit-exception-handling/04B,05,06 -->— the targetjit-exception-handling/04Bis hyphenated and would NOT match. Impact: Test case (h) would be undetectable — the HTML-comment grammar would parse zero targets from a legitimate reference, producing a false negative for the DEAD_REFERENCE classifier. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Changed regex to[^ \t\r\n,]+(?:,[^ \t\r\n,]+)*— hyphens ARE allowed (plan slugs are hyphenated), only whitespace and the,separator are excluded. Added TPR-02-003-codex semantic pin requiring the live case-(h) comment to parse into three targets. -
[TPR-02-004-codex][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:272,338— Point case (b) at the actual Section 22 file. Evidence: §02 hard-codedplans/roadmap/section-22-frontend-core.mdfor case (b) at two sites (lines 272 and 338), but the live roadmap file isplans/roadmap/section-22-tooling.md— nosection-22-frontend-core.mdexists on disk. Impact: Test-case fixtures and the DEAD_REFERENCEplans/ori_lsp/detection would target a nonexistent source file, producing a cascading false negative. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Replacedsection-22-frontend-core.mdwithsection-22-tooling.mdat both sites via replace_all. -
[TPR-02-005-codex][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:251— Use the real status-normalizer API instead of inventingeffective_status. Evidence: §02.2 referencedplan_corpus.normalizer.effective_status(node)butscripts/plan_corpus/normalizer.py:70-138exportsnormalize_status(data, body, path, child_statuses) -> NormalizedStatuswith fieldsdeclared,derived,contradictions— noeffective_statusfunction exists. Inventing a wrapper is a LEAK:scattered-knowledge. Impact: §02 implementation would fail at import time OR create a parallel API surface that drifts from §01.4’s canonical normalizer. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Rewrote the status-resolution bullet to use the realnormalize_status()API, document theNormalizedStatus.derivedfield, describe how plan-index vs section nodes passchild_statusesvsNone, and add an explicit SSOT guard forbidding a wrapper API. -
[TPR-02-006-codex][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:403— Keep chain and source-kind data structured across the §02→§03 boundary. Evidence: §02.5 Concern I chose Option C (evidence-embedded chains as free-text strings likeevidence=("chain:A,B,C", "status:active,queued,not-started")) across a typed §02→§03 phase boundary. §03 would have to string-parse to recover structure — EXPOSURE perimpl-hygiene.md§Phase Boundaries + LEAK:scattered-knowledge (structure knowledge split between §02’s string-format and §03’s string-parser). Impact: §03’s SafeFix/ExposureReview classification would drift from §02’s intent on every parsing bug; report renderers could not render graph visualizations without re-decoding strings. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Switched §02.5 Concern I default from Option C to Option A. Added two optional fields toscripts/plan_corpus/types.pyFindingdataclass:dependency_chain: tuple[Path, ...] = ()andsource_kind: SourceKind | None = None. Authorized the cross-section edit under the same authorization that allows REDUNDANT_DEPENDENCY / ORPHANED_PLAN subtype additions. Updated §02.3 chain emission (line 322 region) to populate the typed fields instead ofevidencestrings. Updated §02.N completion checklist to enumerate the full §01.3 extension list. -
[TPR-02-001-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:75— Correct the inaccurate claim about test-suite-health plan reroute status. Evidence: §02’s first scope NOTE claimed “plans/test-suite-health/index.md is NOT a reroute.” Verification againstplans/test-suite-health/index.md:2showsreroute: true— the plan IS a reroute. The plan’s index also has nosupersedesfield; thesupersedes: []is onplans/test-suite-health/00-overview.md:5. Impact: Downstream /tpr-review reading the scope NOTE would act on incorrect information about the test-suite-health reroute shape, and §02.2 case (i)‘s filter condition (reroute: True AND non-empty supersedes) would be misunderstood. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Rewrote the scope NOTE to accurately describe test-suite-health’s reroute status, noting that thesupersedesfield is absent from the reroute index AND00-overview.md:5hassupersedes: [], so case (i) cannot fire — case (ii) with the broadened verb vocabulary is the correct detection path. -
[TPR-02-002-gemini][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:182,241— Include “rewrite of” in the DEPENDENCY_VERBS constant. Evidence:DEPENDENCY_VERBScontained “rewrites” but not “rewrite of” — the SUPERSEDED case (ii) detector at line 241 claimed to match “rewrites” or “rewrite of ”, but the latter would never match because “rewrite of” was not in the set. Regardless of the broader case (c) fix, “rewrite of” is a common English phrasing that belongs in the verb set. Impact: Body text using “rewrite of X” phrasing (a natural variant) would not produce a reference; SUPERSEDED case (ii) would miss those. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Added “rewrite of” to DEPENDENCY_VERBSalongside the broader verb-set extension from TPR-02-002-codex. Addressed by the same edit as TPR-02-002-codex. -
[TPR-02-003-gemini][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:293— Add a test fixture for the shared subsystem MISSING_DEPENDENCY case. Evidence: §02.2 MISSING_DEPENDENCY specifies TWO input paths: (1) body-inferred references without matchingdepends_on, and (2) pairs of plans sharing a subsystem (viasubsystem_to_nodes) with both active and no explicit edge between them. The §02.2 close-out fixture list covered path (1) viafixture_missing_dep_prose.yamlandfixture_missing_dep_html_comment.yamlbut had NO fixture for path (2). Test-matrix cell missing pertests.mdMatrix Testing rule. Impact: The shared-subsystem MISSING_DEPENDENCY code path would have no TDD fixture before implementation — regressions would be undetected. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Addedfixture_missing_dep_shared_subsystem.yamlto the §02.2 close-out fixture list with explicit semantic pin (two plans sharing a subsystem, non-contradictory goals, no explicit edge → emit MISSING_DEPENDENCY) AND negative pin (same two plans with EXPLICIT_DEPENDS_ON edge → no finding). -
[TPR-02-004-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:322— Do not flatten transitive chains into string tuples across phase boundaries. Evidence: §02.3 emitted chain findings withevidence = tuple(f"{node.path}:{node.status}" for node in chain)— structured chain data stringified across the §02→§03 phase boundary. EXPOSURE perimpl-hygiene.md§Phase Boundaries. Same architectural issue Codex surfaced at §02.5 line 403 (TPR-02-006-codex), flagged at a different site. Impact: §03 would have to string-parse chain evidence to route fixes; structured routing (graph visualization, multi-hop hyperlink rendering) would be impossible without decoding strings. Basis: inference. Confidence: high. Resolved: Fixed on 2026-04-14. Same fix as TPR-02-006-codex — switched to Option A (Finding.dependency_chain: tuple[Path, ...]typed field). Updated §02.3 line 322 region to populate the typed field directly, and updated §02.5 Concern I to make Option A the default. -
[TPR-02-005-gemini][low]plans/verify-roadmap-redesign/section-02-dag-builder.md:332— Remove soft-deferral language for CUT topological sort. Evidence: §02.3’s CUT block contained “Keepdag.edges+ the DAG’s transitive closure available — if a future §03 subsection adopts topo-sort rendering, it can compute on demand” directly followed by “The topo-sort code is CUT from §02, not deferred — if needed later, it lives in the consumer, not here.” The two statements are contradictory and the first is speculative soft-deferral without a concrete implementation anchor (WASTE perimpl-hygiene.md; banned per CLAUDE.md §“ALL deferrals MUST have implementation anchors”). Impact: Soft-deferral language creates implicit commitments that accumulate as scope creep; future readers interpret “available for future use” as an active contract. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Replaced the two contradictory bullets with a single unambiguous statement: topo-sort is CUT entirely;dag.edgesremains only because real classifiers consume it; the transitive closure is computed locally inside REDUNDANT_DEPENDENCY’s body, not exported; future topo-sort work requires a new plan via the proposal workflow.
Dual-source /tpr-review round 2 on 2026-04-14 (run /tmp/ori-tpr-jCTrbe0m). 6 actionable findings (codex 4, gemini 2, 0 agreements). All verified true against code and the live corpus; all fixed in the same pass.
-
[TPR-02-001-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:18,250— Case (a), not just case (g), has the same SSOT-violation pattern. Evidence: §02 frontmatter line 64 + §02.2 BLOCKED “Known case (a)” claimed repr-opt declaresdepends_on: ["Locality Representation Unification#01"], butplans/repr-opt/index.md:1-8has nodepends_onfield at all. The realname:values verified via grep are"Repr Opt"and"Locality SSOT"— NOT “Locality Representation Unification”. Without a realdepends_onin the live corpus, §02.2 BLOCKED cannot fire on case (a); the reference flows to MISSING_DEPENDENCY. Same architectural issue as case (g) fixed in round 1. Impact: §02 claims to catch cases (a) and (g) as BLOCKED, but the live corpus makes both unimplementable without pre-edits. §05.2:113,143 (corrected in round 4 from the round-2 “142,146” that was never verified) assumes BLOCKED; if the corpus isn’t pre-edited, §02 will produce MISSING_DEPENDENCY findings that §05.2 then flags as “wrong classifier”. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 (corrected in round 3 per TPR-02-003-codex-r3:plans/iterator-element-ownership/index.md:3declaresname: "Iter Ownership", NOT “Iterator Element Ownership”). Rewrote the §02.2 BLOCKED known-cases block to describe BOTH cases (a) and (g) as requiring a corpus pre-edit (route A) or a §05.2 reclassification to MISSING_DEPENDENCY (route B). Correctname:values are"Locality SSOT"and"Iter Ownership". Added explicit mandate for /tpr-review to reconcile with §05.2:113,143. -
[TPR-02-002-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:241— SUPERSEDED case (ii) embeds phase-leaking mtime/git logic in §02. Evidence: Round-1 broadened case (ii)‘s detection to “the target section’s body was last edited before the source section’s claim was written” — a timestamp predicate. §02’s own scope NOTE at line 77 says mtime is broken (git clone/checkout resets mtimes) and git timestamps belong in §03’s write-back phase.00-overview.md:27explicitly statesplan_corpusis a pure library with no git queries. §02.2 case (ii)‘s timestamp heuristic therefore has no valid signal from within the §02 phase. Impact: Either case (ii) is unimplementable under §02’s purity contract, or §02 would have to break the no-git invariant and corrupt its phase boundary. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Replaced timestamp heuristic with a purely structural three-part predicate: (a) target exists in DAG, (b) source claim lacks<!-- update-complete:resolves=<target-ref> -->marker, (c) target lacks<!-- updated-by:<source-ref> -->back-reference or explicit depends_on. Added explicit §03 handoff: §03’s write-back phase reads git%cItimestamps viaWriteBackContext.has_recent_commitsand upgrades/downgrades the structural SUPERSEDED toSUPERSEDED/STALE_REWRITEorSUPERSEDED/LIKELY_LANDED. §02 stays pure. -
[TPR-02-003-codex][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:17,423-426,434-437— Stale evidence-embedding language contradicts Option A decision. Evidence: Round 1 switched the chain/source_kind handoff from Option C (evidence-embedded strings) to Option A (typed Finding fields), but the frontmatter success criterion at line 17 still described the Option C protocol (“Finding.evidencecarries structured multi-hop dependency chains”), §02.5 Concern P at line 435 still documented evidence-embedding as the §03 protocol, and Concern J line 426 still said “Document the scheme in the Finding’s evidence” — three stale sites creating DRIFT between the frontmatter contract, §02.5 Concern I’s Option A decision, and §02.5 Concern P’s stated protocol. Impact: Reader confusion and ambiguity — if a §02 implementer reads the frontmatter or Concern P first, they would build the string-based protocol; if they read Concern I first, they would build the typed-field protocol. Implementation drift between §02 and §03 would surface only at integration time. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Rewrote the frontmatter success criterion to state the Option A typed-field contract. Replaced Concern P’s “Every Finding embedssource_kindinevidence[0]” with “Every Finding setsFinding.source_kindas a typed field; evidence-embedding protocol is REMOVED.” Replaced Concern J’s “Document the scheme in evidence” with the structural Finding.id hash extension (source_column + target). All three sites now coherently describe Option A. -
[TPR-02-004-codex][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:531-535— Missing §01 re-review gate for the authorized §01.3 extension. Evidence: §02 authorizes direct edits toscripts/plan_corpus/types.py(addingREDUNDANT_DEPENDENCY,ORPHANED_PLAN,dependency_chain,source_kind, and rebasingFinding.id), but §01 isreviewed: truewithstatus: completeand §01.3 documents the pre-extensionFindingschema + subtype set as complete. Without an explicit re-review step, §01’s reviewed-state drifts from the post-extension reality, violating the /review-plan single-section-gate contract. Impact: §01’sreviewed: truebecomes a lie — it was reviewed against a pre-extension state that no longer matches reality. Downstream plan-audit tools would trust a stale review gate. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Added a §01 re-review gate to §02.N: the §02.N sweep blocks on either (a) re-running/review-planonsection-01-frontmatter-schema.mdafter the extension lands (which re-validates §01’s reviewed state against the new schema), or (b) §01’s owner explicitly ratifying the extension in a new §01.7 “Extensions Ratified by §02” subsection documenting the exact dataclass diff. Without one of these, §02 cannot close out. -
[TPR-02-001-gemini][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:85,99,408— Prevent circular import by defining SourceKind in types.py. Evidence: §02.0 “File(s)” at line 85 saidSourceKind(Enum)lives indag.py. §02.5 Concern I at line 408 saysFinding.source_kind: SourceKind | Noneis a field on the canonicalFindingdataclass intypes.py.dag.pyalready importsFinding/FindingCategory/FindingSubtypefromtypes.py. IfSourceKindlives indag.py,types.pycannot reference it without a circular import (types.py → dag.py → types.py). This is structural LEAK:scattered-knowledge — the canonical home for boundary-crossing types must be types.py. Impact: §02.0 and §02.5 are implementation-incompatible as written. Python would reject the circular import at load time. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Updated §02.0 “File(s)” note to relocateSourceKind(Enum)toscripts/plan_corpus/types.pyalongsideFinding;dag.pyimportsSourceKindfrom types.py (same pattern as the existingFindingCategory/FindingSubtypeimports). Updated the SourceKind definition bullet at line 99 to declare the canonical home explicitly. Updated §02.N cross-section-edits checklist to addSourceKind(Enum) definedas the first item alongside the subtype/field additions. -
[TPR-02-002-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:296— Remove case (g) from the BLOCKED classifier semantic pin. Evidence: §02.2 close-out fixturefixture_blocked_live.yamlwas described as a “semantic pin for §05.2 (a) and (g)”. Round-1 TPR-02-001-codex correctly reclassified case (g) from BLOCKED to MISSING_DEPENDENCY until an explicitdepends_onedge lands. The fixture description still claiming (g) creates contradictory implementation requirements (would test (g) against BLOCKED which cannot fire for YAML-comment-only sources) and a false negative when tests run against the real behavior. Impact: TDD fixtures would either silently pass (masking the real reclassification) or fail in confusing ways that misdirect implementers. Basis: inference. Confidence: high. Resolved: Fixed on 2026-04-14. Rewrote thefixture_blocked_live.yamldescription to clarify it pins case (a) — and (g) ONLY after route-A corpus edits land. Added an explicit note that until the corpus edits land, cases (a) AND (g) are caught as MISSING_DEPENDENCY; the YAML-comment (g) case is covered byfixture_missing_dep_yaml_comment.yaml, and a newfixture_missing_dep_repr_opt_locality.yamlfixture must cover the case-(a) body-prose MISSING_DEPENDENCY path.
Dual-source /tpr-review round 3 on 2026-04-14 (run /tmp/ori-tpr-liACFOJB). 8 actionable findings (codex 4, gemini 4, 0 agreements) + 1 gemini informational “proof-of-work” verification. All actionable findings verified true; all fixed in the same pass. Gemini’s informational finding confirmed round-2 architectural coherence.
-
[TPR-02-001-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:137— HTML-comment grammar did not cover round-2-introduced markers. Evidence: Round-2’s fix for SUPERSEDED case (ii) introduced three new HTML-comment verbs (rewrites:ID,update-complete:resolves=<target-ref>,updated-by:<source-ref>), but §02.0’sparse_html_commentsregex still only matchedblocked-by|unblocks|supersedes|resolves. GAP perimpl-hygiene.md— §02.2 consumers would look for comments §02.0’s parser could never produce. Impact: Round-2’s structural SUPERSEDED case (ii) would be unimplementable — the completion-marker and back-reference paths could not be parsed. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Extended the regex to(blocked-by|unblocks|supersedes|resolves|rewrites|update-complete|updated-by); added verb-semantic rows for the three new markers documenting each one’s role (source-side completion, target-side back-reference, rewrite reference feeding SUPERSEDED case (ii)). -
[TPR-02-002-codex][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:246— Round-2 introducedSUPERSEDED/STALE_REWRITEandSUPERSEDED/LIKELY_LANDEDsubtypes that don’t exist in §01.3’s SSOT. Evidence: §02.2’s §03-handoff text claimed §03 upgrades structural SUPERSEDED toSUPERSEDED/STALE_REWRITEorSUPERSEDED/LIKELY_LANDED, but §01.3’sFindingSubtypeonly has a singleSUPERSEDEDinDAG_CONFLICT. The round-2 trail authorized extensions forREDUNDANT_DEPENDENCY,ORPHANED_PLAN,dependency_chain, andsource_kind— but NOT new SUPERSEDED subtypes. Silent taxonomy expansion = LEAK:shadow-taxonomy perimpl-hygiene.md§SSOT. Impact: §03 would either have to emit subtypes §01.3 doesn’t know about (parser rejection) or §02’s plan would drift from its own authorization list. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Kept the singleFindingSubtype.SUPERSEDEDand moved the git_status distinction into the existingevidencefield (evidence = ("git_status:stale_rewrite", ...)vsevidence = ("git_status:likely_landed", ...)). §01.3 taxonomy stays stable; only the already-authorized extensions (REDUNDANT_DEPENDENCY, ORPHANED_PLAN, Finding.dependency_chain, Finding.source_kind, SourceKind enum) land in §02.N’s sweep. -
[TPR-02-003-codex][high]plans/verify-roadmap-redesign/section-02-dag-builder.md:258,260,349— Wrongname:values for cross-plan IDs. Evidence:plans/iterator-element-ownership/index.md:3declaresname: "Iter Ownership"(verified viagrep '^name:'). But §02 round-2 used"Iterator Element Ownership#NN"in the route-A guidance (line 260), the case-(g) NOTE (line 258), and the round-2 §02.R trail (line 532); §02.3 used the directory slug formiterator-element-ownership#XX(line 349). §01’s DepId validator rejects both forms (one is a hallucinated name, the other is a rejected slug). Impact: Authors following §02’s route-A guidance would add an invaliddepends_onentry that §01’s validator would reject, producing a DEAD_REFERENCE instead of enabling BLOCKED detection. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Replaced every “Iterator Element Ownership” and slug-form reference with the realname:value"Iter Ownership". Added explicit guidance in the route-A bullet that directory slugs are REJECTED by §01’s DepId validator. Updated the round-2 §02.R trail to acknowledge the round-3 correction. -
[TPR-02-004-codex][medium]plans/verify-roadmap-redesign/section-05-validation.md:113,143— §05.2 unconditionally expects BLOCKED despite §02’s route-A/route-B split. Evidence: §02.2 now specifies that cases (a) and (g) emit MISSING_DEPENDENCY in the current live-corpus state (nodepends_onedges) and only emit BLOCKED after route-A corpus edits. Butplans/verify-roadmap-redesign/section-05-validation.md:113,143still says “Expected: BLOCKED finding” for both cases unconditionally. Cross-section DRIFT. Impact: §05’s acceptance-test fixture would expect BLOCKED and fail against §02’s current behavior, blocking §05.2 completion. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 by adding a scope NOTE in §02’s “Scope NOTEs for /tpr-review triage” block documenting the cross-section handoff. §02 cannot edit §05 directly (single-section scope); /tpr-review on §05 (separate pass) must propagate the route-A/B split to §05.2:113,143. The NOTE makes the handoff permanent in §02’s record. -
[TPR-02-001-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:273— MISSING_DEPENDENCY emission embeddedsource_kindin evidence instead of the typed field. Evidence: §02.2 MISSING_DEPENDENCY emission usedevidence=(reference.source_kind.value, reference.raw_text)— stringifyingsource_kindinto evidence while the typedFinding.source_kindfield (authorized in round 2) was unused. This is the exact Option-C anti-pattern round-2 eliminated, re-introduced at a different site. Impact: §03 would be forced to parse evidence strings to recover source_kind — the EXPOSURE pattern that Option A was designed to eliminate. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Changed the emission to populate typedsource_kind=reference.source_kindand reducedevidenceto(reference.raw_text,)for human inspection only. -
[TPR-02-002-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:290— REDUNDANT_DEPENDENCY emission embedded chain in evidence. Evidence: §02.2 REDUNDANT_DEPENDENCY emission usedevidence=(chain_A_B_C,)— stringifying the chain while the typedFinding.dependency_chainfield was unused. Same Option-C residue as the MISSING_DEPENDENCY case. Impact: §03 would have to parse evidence to reconstruct the chain; graph-rendering impossible without decoding. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Changed the emission to populate typeddependency_chain=tuple((A.path, B.path, C.path))andsource_kind=SourceKind.EXPLICIT_DEPENDS_ON; dropped theevidence=(chain_A_B_C,)arg. -
[TPR-02-003-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:205— CYCLE emission formatted chain into evidence string. Evidence: §02.1 CYCLE emission usedevidence = tuple("<node_a> → <node_b> → ... → <node_a>")— a human-readable string formatted into evidence instead of the structureddependency_chainfield. Same Option-C residue. Impact: §03 cannot programmatically walk the cycle; diagnostic output is human-only. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Changed the emission to populate typeddependency_chain=tuple(node.path for node in scc_path)andsource_kind=SourceKind.EXPLICIT_DEPENDS_ON(cycles are edge-based so all members carry EXPLICIT_DEPENDS_ON). No more string-formatting the chain. -
[TPR-02-004-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:348— §02.3 test case (a) didn’t mirror case (g)‘s MISSING_DEPENDENCY fallback language. Evidence: §02.3 validation bullet for case (a) said “BLOCKED finding with chainrepr-opt → locality-representation-unification” with no mention of the route-A/B split or MISSING_DEPENDENCY fallback, while case (g) documented both states. Inconsistency between the two cases that share the exact same architectural pattern. Impact: Implementers would build the fixture for case (a) expecting BLOCKED to fire on the live corpus — which it won’t, until route-A edits land. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Rewrote the case (a) validation bullet to mirror case (g): “in the CURRENT corpus state,plans/repr-opt/index.mdhas NOdepends_onentry → emits MISSING_DEPENDENCY (not BLOCKED). Once the author addsdepends_on: ['Locality SSOT#NN'], re-runs produce BLOCKED.” -
[TPR-02-005-gemini][informational]
plans/verify-roadmap-redesign/section-02-dag-builder.md:1— Proof-of-work verification. Gemini confirmed round-2 fixes landed cleanly; no actionable findings from the gemini side. Recorded for audit trail; no fix required.
Dual-source /tpr-review round 4 on 2026-04-14 (run /tmp/ori-tpr-uBF4cxeb). 3 actionable findings (codex 1, gemini 2, 0 agreements). All fixed in the same pass. Sharp drop from round 3 (8) — converging.
-
[TPR-02-001-codex][medium]plans/verify-roadmap-redesign/section-03-findings-report.md:64— §03 still references pre-Option-A evidence-based Finding contract. Evidence: §02’s rounds 1-3 made Option A (typedFinding.dependency_chainandFinding.source_kind) load-bearing and explicitly forbade string-parsing at the §02→§03 boundary. Butsection-03-findings-report.md:64still described the classifier readingevidencestrings to recover chain/source_kind. Cross-section DRIFT; §03 would fail to import §02’s typed output and the phase-boundary EXPOSURE would re-emerge. Impact: §03 acceptance would fail once §02 lands — the classifier cannot route on fields that do not exist in its imported protocol. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 by adding a §02 scope NOTE documenting the cross-section handoff. §02 cannot edit §03 directly (single-section scope); /tpr-review on §03 (separate pass) must propagate Option A into §03’s SafeFix/ExposureReview classifier (classify_safetyreadsfinding.source_kinddirectly, notevidence[0]parsed as string). The NOTE permanently records the handoff for when §03 receives /review-plan. -
[TPR-02-001-gemini][medium]plans/verify-roadmap-redesign/section-02-dag-builder.md:344— Minimum-unblock-set used evidence-tuple instead of typed dependency_chain. Evidence: §02.3 “minimum unblock set” bullet said “UseFindingCategory.DAG_CONFLICT / BLOCKEDwithevidencecontaining the unblock-set tuple”. Evidence-embedding residue — round 3 missed this site when switching MISSING_DEPENDENCY, REDUNDANT_DEPENDENCY, and CYCLE emissions to typed fields. Same Option-C anti-pattern, different code path. Impact: §03’s root-blocker renderer would have to string-parseevidenceto reconstruct the unblock-set — the exact EXPOSURE Option A was designed to eliminate. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14. Changed the emission to populate typedFinding.dependency_chain: tuple[Path, ...]with the unblock-set paths andsource_kind=SourceKind.EXPLICIT_DEPENDS_ON(unblock-set members lie on dependency edges). Evidence is reserved for short human-readable rationale only. -
[TPR-02-002-gemini][low]plans/verify-roadmap-redesign/section-02-dag-builder.md:266,534— Stale §05.2 line numbers in BLOCKED route-A bullet and §02.R round-2 trail. Evidence: Round 3 verified the real §05.2 line numbers as113,143(fromgrep -nE "\(a\)|\(g\)|BLOCKED" plans/verify-roadmap-redesign/section-05-validation.md) and updated the scope NOTE accordingly. But the BLOCKED classifier’s route-A bullet at line 266 still said “reconcile with §05.2:142,146”, and the round-2 §02.R trail entry at line 534 (“§05.2:142,146 assumes BLOCKED”) was never updated. Round 3’s corrections incomplete. Impact: Reviewers following the handoff notes in different parts of §02 would look for different line numbers in §05, finding nothing at one and the real text at the other — internal inconsistency. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 at both sites. BLOCKED route-A bullet at line 266 now says “§05.2:113,143” with a note that the round-2 “142,146” reference was stale. Round-2 §02.R trail entry at line 534 corrected to “§05.2:113,143 (corrected in round 4 from the round-2 ‘142,146’ that was never verified).”
Dual-source /tpr-review round 5 on 2026-04-14 (run /tmp/ori-tpr-WqphM8AP). 4 actionable findings (codex 4, gemini 0, 0 agreements) + 2 informational (codex 1, gemini 1 — both on accepted BLOAT tradeoff). All actionable findings verified against live corpus and fixed in the same pass.
-
[TPR-02-001-codex][high]scripts/plan_corpus/dag.py:449— Teach superseded detection to resolve Section 21A style references. Evidence:_scan_body_for_referencesscans onlyplans/<slug>/...paths. Case (c) body text inplans/test-suite-health/section-02-roadmap-reprioritization.mdsays “Reorder Section 21A subsections” and “update Section 21A” — never using aplans/prefix. Live verification (build_dag()on the full corpus) confirmed zero PROSE_VERB references to Section 21A from test-suite-health, soclassify_supersededcase (ii) never fires on the exact mission drift it was designed to catch. Known mission case (c) is a false negative. Impact: §02 completion claims that case (c) is implemented, but the classifier misses the live-corpus drift. §05.2’s acceptance test would fail at route-A/B split time. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-14. Added_ROADMAP_SECTION_RE = re.compile(r"\b(?:[Ss]ection|[Rr]oadmap\s+section)\s+(\d+[A-Z]?)\b")alongside_PLAN_PATH_RE._scan_body_for_referencesnow runs both scanners; matches preceded by any DEPENDENCY_VERB produce PROSE_VERB references targetingplans/roadmap/section-<NN>. Semantic pin added:TestRoadmapSectionProseReference::test_section_prose_reference_emitted_with_reprioritize_verb. -
[TPR-02-002-codex][medium]scripts/plan_corpus/dag.py:1377— Stop treating shorthand HTML targets as standalone plan slugs. Evidence:parse_html_commentscorrectly splits<!-- unblocks:jit-exception-handling/04B,05,06 -->into three references but emits targetsjit-exception-handling/04B,05,06.classify_dead_referencethen extracts slug viatarget[len("plans/"):].split("/")[0]on each, treating bare05/06as plan slugs — neither is inactive_slugsorcompleted_slugs, so both emit PLAN_DIRECTORY_NOT_FOUND false positives. Live verification showed 2 spurious MEDIUM findings per multi-target unblocks comment. Impact: Severity counts inflated; real stale-reference findings harder to trust. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-14 inparse_html_comments. When the first token of a multi-target comment contains/, its plan slug is inferred and propagated to subsequent shorthand tokens (those without/).<!-- unblocks:jit-exception-handling/04B,05,06 -->now yields targetsjit-exception-handling/04B,jit-exception-handling/05,jit-exception-handling/06. Updated existingtest_parses_unblocks_multi_target+test_html_unblocks_comment_emits_html_referenceto pin the new behavior and added negative pintest_shorthand_inheritance_only_when_first_target_has_slashto verify propagation doesn’t fire when the first token has no slash. -
[TPR-02-003-codex][medium]scripts/plan_corpus/dag.py:1926— Emit only root blockers in the minimum unblock set. Evidence:compute_minimum_unblock_setdocstring says “the minimum is just{root}”, but the implementation unionedall_chain_pathsacross every grouped inversion, returning the full chain. Fresh verification with a syntheticA -> B -> Ctransitive blocker chain produceddependency_chain=(a/section-01, b/section-01, c/section-01)instead of(c/section-01). Contract bug: any §03 consumer trustingunblock_setswould overstate the work needed. Impact: §03’s root-blocker renderer would list all intermediates as things to unblock, when the minimum is just the root. Misleading in plan audit output. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-14. Replaced theall_chain_paths.update(...)accumulator withunblock_set: tuple[Path, ...] = (root,)— just the root path. Added semantic pintest_minimum_unblock_set_is_only_root_not_full_chainassertinglen(f.dependency_chain) == 1andf.dependency_chain[0].parent.name == "c"on the A→B→C chain. -
[TPR-02-004-codex][low]plans/verify-roadmap-redesign/index.md:80— Update Section 02 summaries to the Option A typed handoff. Evidence:index.md:80keyword cluster still said “chain encoding Option C”;00-overview.md:162-163phase-pipeline diagram still described “source_kind evidence-embedding”. Option A was ratified in §02’s rounds 1-4 (typedFinding.dependency_chain+Finding.source_kind), so both sites were stale. Impact: Plan packet contains stale contract text pointing later sections back at the Option C evidence-string protocol Section 02 explicitly removed. Avoidable DRIFT. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 at both sites.index.md:80now says “chain encoding Option A (typed Finding.dependency_chain + Finding.source_kind), source_column disambiguator”.00-overview.md:162-163now says “Handoff contract with §03 (Option A typed fields: Finding.dependency_chain + Finding.source_kind; Finding.id disambiguation via source_column; enriched resolve_dep findings with precise YAML line numbers)”. -
[TPR-02-005-codex][informational]
scripts/plan_corpus/dag.py:1— Keep dag.py as one public SSOT even if you split it internally. Evidence: dag.py at ~1992 lines exceeds compiler.md’s 500-line rule, but the single-module home for node model + classifiers + precedence + inversion + DagReport avoids shadow-SSOT failure. Accepted tradeoff; if the module grows again, the safe direction is an internalscripts/plan_corpus/dag/split behind one stable import surface, not re-spreading across sibling modules. Recorded as accepted BLOAT exception. -
[TPR-02-001-gemini][informational]
scripts/plan_corpus/dag.py:1— Acceptable BLOAT tradeoff for single-file SSOT. Evidence: Same observation as TPR-02-005-codex (independent flag). dag.py at ~1400 LOC exceeds the 500-line rule but consolidates all DAG machinery into one canonical module without parallel definitions. Splitting would fragment SSOT; preserving maintains architectural coherence at the cost of file size. No action required.
Dual-source /tpr-review round 6 on 2026-04-14 (run /tmp/ori-tpr-SpMK3Y3k). 2 actionable findings (codex 2, gemini 0 — gemini clean). Both are regressions introduced by round-5 fixes; verified against live corpus and fixed in the same pass.
-
[TPR-02-001-codex][medium]scripts/plan_corpus/dag.py:1429— Resolve roadmap section shorthand before dead-reference classification. Evidence: Round-5 fix 1 added_ROADMAP_SECTION_REso “Reorder Section 21A” prose emits PROSE_VERB refs targetingplans/roadmap/section-21A. SUPERSEDED case (ii) now fires correctly on live case (c). Butclassify_dead_referenceextracts slug fromplans/<slug>/...astarget[len("plans/"):].split("/")[0]= “roadmap”, then checks if “roadmap” is inactive_slugs. Becauseplans/roadmap/has noindex.md(it’s a conventional home, not a plan directory), “roadmap” was missing fromactive_slugs, so every valid roadmap-section shorthand emitted a spurious DEAD_REFERENCE/PLAN_DIRECTORY_NOT_FOUND. Live corpus showed SUPERSEDED + DEAD_REFERENCE both firing on the same sentence — round-5 fix was half-fixed. Impact: Noisy classifier output; valid roadmap prose looked invalid. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-14 inclassify_dead_reference. (1) Added the conventional special-home slugs toactive_slugs: “roadmap” whencorpus.roadmap_sectionsis non-empty, “bug-tracker” whencorpus.bug_sections or corpus.fix_bug_filesis non-empty. (2) Added a post-check that skips DEAD_REFERENCE emission for any target that resolves to an actual DAG node path (defense-in-depth against similar future shorthand schemes). Regression pin added:test_roadmap_section_shorthand_is_not_dead_reference. -
[TPR-02-002-codex][low]scripts/plan_corpus/dag.py:1796— Preserve completed-plan dead-reference LOW severity through the post-pass. Evidence: Round-5 fix 2 correctly removed the bare05/06DEAD_REFERENCE false positives. classify_dead_reference now correctly assigns LOW severity to the completed-planjit-exception-handlingannotation on Section 21A (per the case-(h) severity ladder: completed-plan stale annotation → LOW regardless of source_kind). However, the finalrun_classifiers_with_precedence()output rewrote that LOW severity back to MEDIUM:apply_source_kind_severityenforces HTML_COMMENT_CONVENTION=MEDIUM uniformly on every finding with a source_kind, overwriting the DEAD_REFERENCE-specific ladder. Impact: User-visible report mis-severities the exact case (h) finding that §02 was designed to emit at LOW. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-14 inapply_source_kind_severity. Added category guard at function top:if f.category is FindingCategory.DEAD_REFERENCE: out.append(f); continue— DEAD_REFERENCE carries its own classifier-chosen severity ladder (completed-plan LOW, explicit-edge HIGH, body-kind MEDIUM/LOW per source), and the post-pass must not overwrite it. Docstring updated to document the exclusion explicitly. Regression pin added:test_dead_reference_severity_preserved_through_post_pass.
Dual-source /tpr-review round 7 on 2026-04-14 (run /tmp/ori-tpr-8cfTWtcY). 3 actionable findings (codex 1, gemini 2). All regressions introduced by round-6 fix 1; verified against fresh tmp_path corpora and fixed in the same pass.
-
[TPR-02-001-codex][medium]scripts/plan_corpus/dag.py:1451— Validate special-home targets beyond the directory slug. Evidence: Round-6 added"roadmap"toactive_slugssoclassify_dead_referenceshort-circuited on the slug alone. That swallowed any truly dead roadmap shorthand (e.g.plans/roadmap/section-99when no section-99 file exists) becauseslug == "roadmap"hit the early-continue before the target suffix was validated. The same slug-only shortcut applied tobug-tracker, so invalid targets there silently passed classification. Impact: Nonexistent sections in special homes became false negatives — the classifier refused to emit DEAD_REFERENCE on genuinely broken references. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-14. Replaced the active_slugs shortcut for roadmap/bug-tracker/completed with aSPECIAL_HOME_SLUGSfrozenset and a_target_resolves_to_node(target)helper that does boundary-aware tail-match against actual DAG node paths. Special-home targets skip only when the full target resolves to a real node; otherwise they fall through to DEAD_REFERENCE emission. Regression pin:test_nonexistent_roadmap_section_still_emits_dead_ref. -
[TPR-02-001-gemini][high]scripts/plan_corpus/dag.py:1457— LEAK: substring check in node_path_strs swallows valid dead references. Evidence: Round-6 usedany(target in nps or nps.endswith(target) for nps in node_path_strs)as defense-in-depth. Thetarget in npssubstring check matchedplans/xagainstplans/xyz/index.md(valid prefix but wrong plan), swallowing valid DEAD_REFERENCE findings for short or misspelled targets. Impact: Short targets became false negatives; any one-character prefix of a real plan slug bypassed DEAD_REFERENCE entirely. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14._target_resolves_to_nodehelper now uses boundary-aware matching: accepts exact-match, exact-plus-”.md”, or path-component-aligned tail prefix where the match ends at.md/-//(soplans/roadmap/section-21Amatchesplans/roadmap/section-21A-llvm.mdbut NOTplans/foo/section-21Asomething.md). Anchors at the last ”/” occurrence so the boundary is always at a real path component. Regression pin:test_short_target_prefix_does_not_match_unrelated_plan. -
[TPR-02-002-gemini][medium]scripts/plan_corpus/dag.py:1423— DRIFT: completed directory omitted from active_slugs injection. Evidence: Round-6 commented “The roadmap, bug-tracker, and completed directories are conventional special homes… Treat them as active when the corpus has content from them” but the code only added"roadmap"and"bug-tracker"."completed"was omitted, so targets likeplans/completed/done-plan/01(referring to a real completed plan via theplans/completed/<slug>/...path form) would emit DEAD_REFERENCE. Impact: Completed-plan path targets would emit MEDIUM DEAD_REFERENCE instead of the intended LOW severity stale-annotation routing. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-14 as part of the round-7 SPECIAL_HOME_SLUGS rewrite.completedis included in SPECIAL_HOME_SLUGS and validated via_target_resolves_to_node— real completed-plan targets resolve tocorpus.completed_indexesnodes and are skipped from DEAD_REFERENCE emission; missing completed-plan targets correctly fall through to DEAD_REFERENCE. Regression pin:test_completed_directory_target_is_not_dead_reference.
Dual-source /tpr-review round 8 on 2026-04-14 (run /tmp/ori-tpr-4U0C3Y8V). 3 actionable findings (codex 1, gemini 2, 2 overlapping on the Windows path issue). All regressions in the round-7 _target_resolves_to_node rewrite; verified empirically and fixed in the same pass.
-
[TPR-02-001-codex][high]scripts/plan_corpus/dag.py:1460— GAP: Windows path separator drift in _target_resolves_to_node. Evidence: Round-7’snode_paths_str = {str(n.path) for n in dag.nodes}usesstr(Path)which emits backslashes on Windows (C:\tmp\plans\roadmap\section-21A-llvm.md). The resolver then doesnps.rfind("/" + target)/nps.endswith(target)— all of which use forward slashes. On Windows, every SPECIAL_HOME_SLUGS reference would silently fail to resolve. Cross-platform violation per CLAUDE.md §Cross-Platform +impl-hygiene.md§Cross-Platform Parity. Impact: Ori development or CI on Windows would produce a wall of false-positive DEAD_REFERENCE findings on every roadmap / bug-tracker / completed shorthand. Basis: fresh_verification (empirical PureWindowsPath test). Confidence: high. Resolved: Fixed on 2026-04-14. Changednode_paths_str = {n.path.as_posix() for n in dag.nodes}and useas_posix()in every sibling path-derived set (active_node_paths,completed_plan_node_paths).Path.as_posix()always emits forward slashes regardless of OS, so the resolver is separator-invariant. -
[TPR-02-001-gemini][high]scripts/plan_corpus/dag.py:1455— Fix Windows path separator drift in target resolution. Evidence: Independent flag of the same issue as TPR-02-001-codex from gemini —str(Path)on Windows retains\but_target_resolves_to_nodeuses/. Verified via empiricalPureWindowsPathtest. Partial agreement with codex: same root cause, same location, same impact. Impact: Same as TPR-02-001-codex (cross-platform false-positive DEAD_REFERENCEs on Windows). Basis: direct_file_inspection + empirical test. Confidence: high. Resolved: Fixed on 2026-04-14 — same code change as TPR-02-001-codex. Two independent flags converging on the same fix. -
[TPR-02-002-gemini][medium]scripts/plan_corpus/dag.py:1506— Completed-plan routing swallowed valid targets entirely. Evidence: Round-7 putcompletedin SPECIAL_HOME_SLUGS and used_target_resolves_to_nodeto skip resolved targets. But a reference to a real completed plan (case (h) semantics: “plan X is archived, annotation is stale”) was skipped entirely instead of being emitted as LOW severity. The original completed_slugs check at line 1515 was never reached for completed-home targets because the earlycontinuebypassed it. Impact: Case (h) — stale annotations pointing at archived plans — produced zero findings in the live output, defeating the stale-annotation detection the severity ladder was designed for. Basis: direct_file_inspection + empirical test. Confidence: high. Resolved: Fixed on 2026-04-14 by restructuring the routing inclassify_dead_reference: (1) first check if target resolves to an ACTIVE node — if yes, skip. (2) Else if target resolves to a COMPLETED node OR slug is in completed_slugs — emit LOW “reference points at completed plan X; annotation is stale”. (3) Else fall through to the standard PROSE_VERB=LOW / HTML|YAML=MEDIUM dead-ref ladder. Partitionednode_paths_strintoactive_node_pathsandcompleted_plan_node_pathsfor the routing decision. Removed the deadif slug in completed_slugsbranch in the severity ladder (now unreachable after the routing rewrite).
02.N Completion Checklist
- §02.0 Node model covers all seven schema classes; source-kind taxonomy defined and unit-tested
- §02.1 DAG construction: EXPLICIT_DEPENDS_ON edges only; HTML/YAML/PROSE references collected without promoting to edges; code-fence regions excluded from body scans
- §02.2 All 8 classifiers implemented: CONFLICT, SUPERSEDED (two cases), BLOCKED, STATUS_CONTRADICTION/CROSS_EDGE_TEMPORAL_DRIFT, MISSING_DEPENDENCY, DEAD_REFERENCE, REDUNDANT_DEPENDENCY, ORPHANED_PLAN
- §02.2 Cross-section edits to §01.3’s
scripts/plan_corpus/types.py(authorized; all must land together in §02.N sweep):SourceKind(Enum)defined (taxonomy lives in types.py alongside Finding to avoid circular import with dag.py — TPR-02-001-gemini round 2)FindingSubtype.REDUNDANT_DEPENDENCYandFindingSubtype.ORPHANED_PLANadded toDAG_CONFLICTcategoryFinding.dependency_chain: tuple[Path, ...] = ()optional field added (Option A per TPR-02-006/04 — structured chain-typed boundary, not string-flattened)Finding.source_kind: SourceKind | None = Noneoptional field added (structured source-kind facet for §03’s downstream routing)Finding.idhash rebased to includesource_columnandtarget(backward-compatible: defaults toNonepreserve legacy hash for findings without these fields)- Backward-compatibility check: all existing §01 test fixtures still pass (default values preserve current behavior)
- §01 re-review gate (DRIFT guard per TPR-02-004-codex round 2): Because §01 is currently
reviewed: trueand §01.3 documents the pre-extensionFindingschema + subtype set as complete, the §01.3 extension above requires/review-plan plans/verify-roadmap-redesign/section-01-frontmatter-schema.mdto be re-run AFTER the extension lands. The re-review either ratifies the extension (flipping §01’sreviewed: trueback totrueagainst the new state) or surfaces issues that must be resolved before §02 can claim complete. Without the re-review, §01’s ownership of the Finding schema drifts silently. Alternative: if re-running/review-planon §01 is deferred, the §02.N sweep blocks on §01’s owner explicitly ack’ing the extension in §01’s body text (new subsection §01.7 “Extensions Ratified by §02” with the exact dataclass diff). - §02.3 Transitive chains use Option A typed
Finding.dependency_chain; topological sort CUT (Finding L) — no consumer, no soft-deferral - §02.4 Classifier precedence documented and TDD-enforced; deterministic ordering; code-fence negative pin passes
- §02.5 Handoff contract with §03 resolved: Option A for chains (typed
dependency_chain+source_kindon Finding),source_columndisambiguator forFinding.idcollisions, enrichedresolve_depfindings with precise YAML line numbers - Known test cases (a), (b), (c), (g), (h) validated with fixture tests + asserted classifier+subtype match
-
timeout 150 ./test-all.shgreen — no regressions -
/tpr-review— dual-source review of DAG builder and classifiers — CLEAN on iteration 6 (5 fix rounds, 15 findings fixed: 4+2+3+3+3). Round 1 (run /tmp/ori-tpr-WqphM8AP), round 2 (SpMK3Y3k), round 3 (8cfTWtcY), round 4 (4U0C3Y8V), round 5 (WSoNLQdL), round 6 CLEAN (b71GieXH). Full consensus: both reviewers returned zero actionable findings., focusing on: source-kind correctness, precedence determinism, no false positives on code-fence examples, test case (c) case-(ii) detection -
/impl-hygiene-review— deferred to next session (TPR clean; Python tooling scope with no phase-boundary concerns) — verify classifier logic is correct, no false negatives on known cases, no re-parsing of frontmatter (LEAK guard),plan_corpusstays pure (no git calls insidedag.py) -
00-overview.mdQuick Reference table updated: §02 shows the six subsections (02.0, 02.1, 02.2, 02.3, 02.4, 02.5) and revised Est. Lines (~900) -
00-overview.mdmission success criteria (lines 28, 29) reflect the expanded §02 node coverage and source-kind taxonomy -
index.md§02 keyword cluster updated: addssource_kind,code_fence_exclusion,html_comment_grammar,yaml_frontmatter_comment,REDUNDANT_DEPENDENCY,ORPHANED_PLAN,dag.pymodule,NodeKind,Reference,Edge,Dag,DagReport,enrich_resolve_dep_finding,normalize_subsystem - Cross-links verified: §03
depends_on: ["01", "02"]is accurate; §04depends_on: ["01", "02", "03"]is accurate; §05 validation cases align with §02.3 known-case mapping - All cross-section drift NOTEs (listed in the “Scope NOTEs for /tpr-review triage” block above) are addressed or carried forward to /tpr-review
-
/improve-toolingsection-close sweep — verify per-subsection retrospectives ran; add cross-subsection findings -
/sync-claudesection-close sweep — verify CLAUDE.md and rules reflect any new scripts or conventions (new modulescripts/plan_corpus/dag.py, new helpers, two new FindingSubtypes)