8Dependency-direction corrections (Plan 142 prose-preservation Phase 4, GATE4 Part A).
12The proper render exposed INVERTED ``depends_on`` edges in the P1.7-ingested seed
13(a dependent "blocked by" a *later / not-started* predecessor โ chronologically
14impossible). This tool FIXES the clearly-inverted edges (each evidence-backed,
15see ``tools/142_dependency_direction_audit.md``); the genuinely-ambiguous ones are
16left untouched and FLAGGED in the audit for an operator decision.
18The final, operator-confirmed edge set (GATE4 direction fixes + the GATE4 ADDENDUM
19cleanup bundle). ``FIXES`` re-points ``depends_on``; ``STATUS_FIXES`` corrects a
21- ``M2.depends_on -> [M1]`` (was [M3,M7.4] inverted; + legacy "M2 Depends on: M1").
22- ``M3.depends_on -> [M2]`` RETIRED at Plan 151 T0 (operator M2->M3 gate waiver).
23- ``M7.depends_on -> [M2, M5]`` (legacy "M7 Depends on: M2 + M5"; M7.4 nested -> phase-level).
24- ``G.4a.depends_on -> []`` + ``G.5a.depends_on -> [G.4a]`` (inverted vs G.5a's prose).
25- ``B.depends_on -> []`` ยท ``C.depends_on -> []`` ยท ``M11-GATE.depends_on -> []``
26 (3 spurious chronologically-impossible edges removed).
27- ``M11-GATE.status blocked -> done`` (legacy shows M11-GATE โ
done at Round 24).
29Edge ``kind`` stays ``gates`` (``gates``/``blocks`` render identically as a ๐
30blocker). No item is added or removed (fidelity preserved); only ``depends_on`` +
31the one status are re-pointed. Deterministic + idempotent.
35- ``apply(source_dir)`` โ load each affected record, re-point ``depends_on``,
36 re-emit canonically; ``FIXES`` is the declarative edit set.
48- Deterministic + idempotent; writes only the 5 affected ``source`` records;
49 never touches the live legacy roadmap; every record re-validates + canonicalises.
53- oc_diagnostics (structured debug logging via _dbg)
54- oc_chat.registry.regenerate (imported for the contracts bootstrap it runs)
55- option_c_registry_contracts (DependencyEdge - the corrected depends_on edges)
56- oc_chat.registry.source.canonical (load_record and format_record_text - reads each affected record and re-emits it canonically)
57- oc_chat.registry.source.scanner (scan_source_tree - finds the phase and roadmap item records by stem)
59from __future__
import annotations
62from dataclasses
import dataclass, field
63from pathlib
import Path
66sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
68from oc_diagnostics
import _dbg
71import option_c_registry_contracts
as rc
76_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
80FIXES: dict[str, list[str]] = {
98STATUS_FIXES: dict[str, str] = {
107 records_changed: int = 0
108 edits: list = field(default_factory=list)
111def _edges(ids: list[str]) -> list[rc.DependencyEdge]:
112 return [rc.DependencyEdge(id=item_id, kind=
"gates")
for item_id
in ids]
115def _before(model: object) -> list[str]:
117 for edge
in getattr(model,
"depends_on", [])
or []:
118 out.append(edge.id
if isinstance(edge, rc.DependencyEdge)
else str(edge))
122def apply(source_dir: Path) -> FixReport:
123 """Apply the declarative ``depends_on`` + ``status`` fixes to the source records."""
126 by_stem = {sc.stem: sc
for sc
in scan_source_tree(Path(source_dir))
if sc.kind
in (
"phase",
"roadmap_item")}
127 record_ids = list(dict.fromkeys([*FIXES, *STATUS_FIXES]))
128 for record_id
in record_ids:
129 scanned = by_stem.get(record_id)
131 _dbg(
"OC_CHAT.MAIN", func, f
"record {record_id} not found โ skipped", 2)
133 model, body = load_record(scanned)
134 update: dict[str, object] = {}
135 change: list[str] = []
136 if record_id
in FIXES:
137 before, after = _before(model), list(FIXES[record_id])
139 update[
"depends_on"] = _edges(after)
140 change.append(f
"depends_on {before} -> {after}")
141 if record_id
in STATUS_FIXES:
142 before_status = getattr(model,
"status",
None)
143 if before_status != STATUS_FIXES[record_id]:
144 update[
"status"] = STATUS_FIXES[record_id]
145 change.append(f
"status {before_status} -> {STATUS_FIXES[record_id]}")
148 updated = model.model_copy(update=update)
149 scanned.path.write_text(
150 format_record_text(updated, body=body), encoding=
"utf-8", newline=
""
152 report.records_changed += 1
153 report.edits.append((record_id,
"; ".join(change)))
154 _dbg(
"OC_CHAT.MAIN", func, f
"{record_id}: {'; '.join(change)}", 3)
155 _dbg(
"OC_CHAT.MAIN", func, f
"changed {report.records_changed} records", 2)
161 repo = Path(__file__).resolve().parents[1]
162 report = apply(repo /
"oc_chat" /
"roadmap" /
"source")
163 _dbg(
"OC_CHAT.MAIN", func, f
"records_changed={report.records_changed}", 2)
164 for record_id, change
in report.edits:
165 print(f
"{record_id}: {change}")
166 print(f
"records_changed={report.records_changed}")
170if __name__ ==
"__main__":
171 raise SystemExit(_main())