oc_chat
Loading...
Searching...
No Matches
verify_142_seed_fidelity.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tools/verify_142_seed_fidelity.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/seed/test_seed_142.py"
6"""
7The one-time seed-fidelity verifier (Plan 142 P1.7, §9 — CI-runnable).
8
9Purpose
10-------
11Verify the committed ``oc_chat/roadmap/source/**`` seed against the live v3.38
12oracle (Registry Source & Storage Spec V0.22 §9): the source is canonical
13(``format --check``); ``regenerate`` is deterministic + idempotent (the
14byte-stable source->store property — §9.2); the migration is **lossless** (every
15v3.38 item_id appears in the regenerated store, modulo the classified dedup —
16zero unclassified drops, §9.4); and it quantifies the §5.6 sub-phase promotions
17(§9 3c). The render-based 4-tuple (§9.3) + the §9 (1) renderer-diff + (3b)
18ordering are operator-staged precursors (flagged, not self-attested).
19
20Run from the worktree root, preferably as ``python -m tools.verify_142_seed_fidelity``
21[roadmap_dir] [source_dir]; the direct ``python tools/verify_142_seed_fidelity.py``
22also works via the ``sys.path`` bootstrap below.
23
24Responsibilities
25----------------
26- ``verify(roadmap_dir, source_dir)`` -> a structured result (CI exit 0/1).
27
28Diagnostics
29-----------
30Domain: OC_CHAT.MAIN
31Levels:
32 L2 - verification lifecycle
33 L3 - per-check results
34 L4 - per-record comparison
35
36Contracts
37---------
38- Read-only over the committed seed; the oracle + regenerated stores are
39 worktree-local temporaries; never touches the shared registry.
40
41Dependencies
42------------
43- oc_diagnostics (structured debug logging via _dbg)
44- tools.seed_142_ingest (ingest_oracle and emit_source - builds the v3.38 oracle store and re-emits it for the promotion and dropped-dependency counts)
45- oc_chat.registry.regenerate (imported for the contracts bootstrap it runs)
46- oc_chat.registry.regenerate.pipeline (regenerate - rebuilds the store twice for the idempotence check)
47- oc_chat.registry.source.canonical (format_source_tree - the canonical-source check in report mode)
48- oc_chat.registry.source.scanner (scan_source_tree - lists the committed source records)
49"""
50from __future__ import annotations
51
52import json
53import re
54import sqlite3
55import sys
56import tempfile
57from dataclasses import dataclass, field
58from pathlib import Path
59
60# Force THIS worktree's ``oc_chat`` + ``tools`` ahead of the editable main-repo
61# install, so a direct ``python tools/verify_142_seed_fidelity.py`` resolves the
62# worktree (otherwise ``from tools.seed_142_ingest import …`` -> ModuleNotFoundError).
63sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
64
65from oc_diagnostics import _dbg
66
67import oc_chat.registry.regenerate # noqa: F401 (runs the contracts bootstrap)
68
69from oc_chat.registry.regenerate.pipeline import regenerate
70from oc_chat.registry.source.canonical import format_source_tree
71from oc_chat.registry.source.scanner import scan_source_tree
72from tools.seed_142_ingest import emit_source, ingest_oracle
73
74_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
75
76_DEFAULT_ROADMAP = Path("oc_chat/roadmap")
77_DEFAULT_SOURCE = Path("oc_chat/roadmap/source")
78
79
80@dataclass
82 ok: bool = True
83 checks: dict = field(default_factory=dict)
84 promotions: int = 0
85 classified_drops: int = 0
86 unclassified_drops: list = field(default_factory=list)
87 notes: list = field(default_factory=list)
88
89 def fail(self, name: str, detail: object) -> None:
90 self.ok = False
91 self.checks[name] = {"pass": False, "detail": detail}
92
93 def passed(self, name: str, detail: object = None) -> None:
94 self.checks[name] = {"pass": True, "detail": detail}
95
96
97def _sanitize_id(raw: str) -> str:
98 candidate = raw.replace("–", "-").replace("—", "-")
99 candidate = re.sub(r"[^A-Za-z0-9._-]", "_", candidate).strip("._-")
100 return candidate or "x"
101
102
103def _store_item_ids(db_path: Path) -> set[str]:
104 conn = sqlite3.connect(str(db_path))
105 try:
106 return {row[0] for row in conn.execute("SELECT item_id FROM roadmap_items_v3")}
107 finally:
108 conn.close()
109
110
111def verify(roadmap_dir: Path = _DEFAULT_ROADMAP, source_dir: Path = _DEFAULT_SOURCE) -> FidelityResult:
112 """Run the §9 CI fidelity checks against the committed seed."""
113 func = "verify"
114 result = FidelityResult()
115 source_dir = Path(source_dir)
116 with tempfile.TemporaryDirectory() as raw_tmp:
117 tmp = Path(raw_tmp)
118 # Oracle: ingest the live v3.38 set + re-emit (for the migration report / promotions).
119 rdir = tmp / "roadmap"
120 rdir.mkdir()
121 for name in ("ROADMAP-v3.38.md", "ROADMAP-EXEC-ORDER-v3.38.md",
122 "ROADMAP-CHANGELOG-v3.38.md", "ROADMAP-TODO-v3.38.md"):
123 (rdir / name).write_text((Path(roadmap_dir) / name).read_text(encoding="utf-8"), encoding="utf-8")
124 oracle_db = tmp / "oracle.sqlite"
125 ingest_oracle(rdir, oracle_db)
126 oracle_ids = {_sanitize_id(i) for i in _store_item_ids(oracle_db)}
127 regen_report = emit_source(oracle_db, tmp / "reemit_source", Path(roadmap_dir) / "ROADMAP-v3.38.md")
128 result.promotions = len(regen_report.promotions)
129 result.classified_drops = len(regen_report.dropped_parent_child_deps)
130
131 # Check 1 — the committed source is canonical (format --check).
132 records = scan_source_tree(source_dir)
133 non_canonical = format_source_tree(records, check=False) # report mode (no rewrite)
134 if non_canonical:
135 result.fail("source_canonical", non_canonical[:10])
136 else:
137 result.passed("source_canonical", f"{len(records)} records canonical")
138
139 # Check 2 — regenerate is deterministic + idempotent (byte-stable source->store).
140 seed_db = tmp / "seed.sqlite"
141 first = regenerate(source_dir, seed_db)
142 if not first.ok:
143 result.fail("regenerate", first.envelope)
144 _dbg("OC_CHAT.MAIN", func, "regenerate failed", 2)
145 return result
146 second = regenerate(source_dir, seed_db)
147 if first.source_hash == second.source_hash and first.logical_state_hash == second.logical_state_hash:
148 result.passed("regenerate_idempotent", {"source_hash": first.source_hash,
149 "logical_state_hash": first.logical_state_hash})
150 else:
151 result.fail("regenerate_idempotent", "hashes differ across rebuilds")
152
153 # Check 3 — lossless: every v3.38 item appears in the regenerated store (zero unclassified drops).
154 regen_ids = _store_item_ids(seed_db)
155 missing = sorted(oracle_ids - regen_ids)
156 if missing:
157 result.unclassified_drops = missing
158 result.fail("lossless_items", {"missing": missing[:20], "count": len(missing)})
159 else:
160 result.passed("lossless_items", f"{len(oracle_ids)} v3.38 items all present")
161
162 # §9 operator-staged precursors (NOT self-attested here).
163 result.notes = [
164 "§9 (1) renderer-diff vs the current files is operator-staged (run after P1.6 render lands on the seed).",
165 "§9 (3) render-based 4-tuple is operator-staged; this verifier proves store-level losslessness instead.",
166 "§9 (3b) per-phase item-ordering-matches-intent is operator-staged.",
167 f"§9 (3c) sub-phase promotions: {result.promotions}; classified parent-child dep drops: {result.classified_drops}.",
168 ]
169 _dbg("OC_CHAT.MAIN", func, f"fidelity ok={result.ok}", 2)
170 return result
171
172
173def main(argv: list[str] | None = None) -> int:
174 """CI entry point: exit 0 if all gates pass, 1 otherwise."""
175 args = argv if argv is not None else sys.argv[1:]
176 roadmap = Path(args[0]) if len(args) > 0 else _DEFAULT_ROADMAP
177 source = Path(args[1]) if len(args) > 1 else _DEFAULT_SOURCE
178 result = verify(roadmap, source)
179 print(json.dumps({
180 "ok": result.ok, "checks": result.checks, "promotions": result.promotions,
181 "classified_drops": result.classified_drops, "unclassified_drops": result.unclassified_drops,
182 "notes": result.notes,
183 }, indent=2, sort_keys=True))
184 return 0 if result.ok else 1
185
186
187if __name__ == "__main__":
188 raise SystemExit(main())
None passed(self, str name, object detail=None)
None fail(self, str name, object detail)
set[str] _store_item_ids(Path db_path)
FidelityResult verify(Path roadmap_dir=_DEFAULT_ROADMAP, Path source_dir=_DEFAULT_SOURCE)
int main(list[str]|None argv=None)