7The v3.38 binary -> AuthoredSource seed migration (Plan 142 P1.7, §9 / §5.8).
11Ingest the live ``ROADMAP-v3.38*`` four-file set into a worktree-local store
12(the oracle), then EMIT the per-record ``oc_chat/roadmap/source/**`` tree: 14
13top-level phases as ``Phase`` records (sub-phases nested as ``sub_phases`` or
14promoted to ``phases/<code>/<sub_id>.md`` per §5.6), non-phase items as
15``RoadmapItem`` records, plus the era/tracks/version — backfilling a non-empty
16``purpose`` for every phase + item (AC §8.22) and sanitising legacy ids/dates.
20- ``ingest_oracle(roadmap_dir, db)`` — build the oracle store.
21- ``emit_source(db, out_dir, roadmap_md)`` — write the canonical source tree;
22 return a migration report (counts, promotions, id/date sanitisations).
28 L2 - migration lifecycle
29 L3 - per-entity emission
30 L4 - per-record sanitisation
34- Deterministic: identical inputs -> identical source bytes.
35- Never writes outside ``out_dir``; the oracle DB is worktree-local + temporary.
36- Every emitted record validates against the P1.1 / P1.4 models.
40- oc_diagnostics (structured debug logging via _dbg)
41- oc_chat.registry.regenerate (imported for the contracts bootstrap it runs)
42- option_c_registry_contracts (the record models, SCHEMA_VERSION, ID_RE and TERMINAL_STATUSES for the emitted source tree)
43- oc_chat.registry (Registry - the oracle registry over the SQLite backend)
44- oc_chat.registry.backends.sqlite (SQLiteBackend and RegistrySeed - the worktree-local oracle store)
45- oc_chat.registry.regenerate.promoted (PromotedItem - the record for a sub-phase promoted to its own file)
46- oc_chat.registry.roadmap.ingest (ingest_corpus - ingests the v3.38 four-file set into the oracle)
47- oc_chat.registry.source.canonical (format_record_text - writes each emitted record canonically)
49from __future__
import annotations
55from dataclasses
import dataclass, field
56from pathlib
import Path
58from oc_diagnostics
import _dbg
61import option_c_registry_contracts
as rc
69_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
71_ISO_DATE_RE = re.compile(
r"(\d{4}-\d{2}-\d{2})")
72_PHASE_HEADER_RE = re.compile(
74 r"^#\s+PHASE\s+(?P<code>M\S+)\s*[—-]\s*(?P<title>.+?)\s*$", re.MULTILINE
76_ID_OK_RE = re.compile(rc.ID_RE)
77_PRIORITIES = {
"critical",
"high",
"medium",
"lower"}
78_DEFAULT_DATE =
"2026-06-02"
83 counts: dict = field(default_factory=dict)
84 promotions: list = field(default_factory=list)
85 id_sanitisations: list = field(default_factory=list)
86 purpose_backfilled: int = 0
87 dropped_parent_child_deps: list = field(default_factory=list)
88 reconciled_statuses: list = field(default_factory=list)
91def _one_line(text: object, max_len: int = 240) -> str:
92 collapsed =
" ".join(str(text
or "").split())
93 return collapsed[:max_len].strip()
97 match = _ISO_DATE_RE.search(str(value
or ""))
98 return match.group(1)
if match
else _DEFAULT_DATE
102 """Map a legacy id to the ASCII id shape (en/em-dash -> '-', illegal -> '_')."""
103 candidate = raw.replace(
"–",
"-").replace(
"—",
"-")
104 candidate = re.sub(
r"[^A-Za-z0-9._-]",
"_", candidate).strip(
"._-")
108 report.id_sanitisations.append({
"from": raw,
"to": candidate})
113 """Non-empty single-line ≤240 purpose: prose first sentence -> title (AC §8.22)."""
115 if prose
and str(prose).strip():
116 first = str(prose).strip().splitlines()[0]
117 text = re.split(
r"(?<=[.!?])\s", first)[0]
119 text = str(title
or "")
122 purpose =
"Seed item (purpose pending operator backfill)"
123 report.purpose_backfilled += 1
128 text = roadmap_md.read_text(encoding=
"utf-8")
130 match.group(
"code"):
_one_line(match.group(
"title"))
131 for match
in _PHASE_HEADER_RE.finditer(text)
139 workspace_id=str(uuid.uuid4()), registry_id=str(uuid.uuid4()),
140 workspace_root=str(db_path.parent), git_common_dir=str(db_path.parent /
".git"),
141 registry_path=str(db_path), checkpoint_path=str(db_path.parent /
".option_c" /
"cp.json"),
142 operator_slot=
"operator-seed", hmac_key_id=str(uuid.uuid4()),
143 secret_fingerprint=hashlib.sha256(b
"seed").hexdigest(), app_version=
"seed",
146 registry =
Registry(backend, clock=backend.clock)
147 result = ingest_corpus(registry, roadmap_dir, actor_slot=
"operator-seed")
149 raise RuntimeError(f
"v3.38 ingest failed: {result.errors}")
153def _deps_and_tracks(conn: sqlite3.Connection, report: MigrationReport) -> tuple[dict, dict]:
155 row[
"item_id"]: row[
"parent_item_id"]
156 for row
in conn.execute(
"SELECT item_id, parent_item_id FROM roadmap_items_v3")
158 deps: dict[str, list] = {}
159 for row
in conn.execute(
"SELECT from_item_id, to_item_id, kind FROM roadmap_item_dependencies"):
160 pred, dep = row[
"from_item_id"], row[
"to_item_id"]
163 if parent_of.get(dep) == pred
or parent_of.get(pred) == dep:
164 report.dropped_parent_child_deps.append({
"from": pred,
"to": dep,
"kind": row[
"kind"]})
166 deps.setdefault(dep, []).append((pred, row[
"kind"]))
167 tracks: dict[str, list] = {}
168 sql = (
"SELECT it.item_id, t.track_code FROM roadmap_item_tracks it "
169 "JOIN roadmap_tracks t ON t.track_id = it.track_id ORDER BY t.track_code")
170 for row
in conn.execute(sql):
171 tracks.setdefault(row[
"item_id"], []).append(row[
"track_code"])
175def _depends_on(item_id: str, deps: dict, sanitized: dict) -> list:
177 for predecessor, kind
in deps.get(item_id, []):
178 pid = sanitized.get(predecessor, predecessor)
179 out.append(pid
if kind ==
"blocks" else rc.DependencyEdge(id=pid, kind=kind))
184 """§9 3c: promote only on item-specific data the nested form cannot hold —
185 description >240, a (non-inherited) priority, or dependencies. The inherited
186 parent track + the global ``v3.38`` version stamp are NOT triggers (they are
187 classified dedup drops, not per-item data)."""
188 return bool(len((row[
"title"]
or "")) > 240
or row[
"priority"]
is not None or deps.get(row[
"item_id"]))
191def _write(out_dir: Path, rel: str, model: rc.RegistryModel, body: str =
"") ->
None:
193 path.parent.mkdir(parents=
True, exist_ok=
True)
194 path.write_text(format_record_text(model, body=body), encoding=
"utf-8", newline=
"")
197def emit_source(db_path: Path, out_dir: Path, roadmap_md: Path) -> MigrationReport:
198 """Emit the canonical source tree from the oracle store; return the report."""
202 conn = sqlite3.connect(str(db_path))
203 conn.row_factory = sqlite3.Row
208 for row
in conn.execute(
"SELECT item_id FROM roadmap_items_v3")
211 version_row = conn.execute(
213 "SELECT version_id FROM roadmap_versions ORDER BY status='current' DESC, version_id DESC LIMIT 1"
215 roadmap_version = version_row[
"version_id"]
if version_row
else "v3.38"
216 _write(out_dir,
"manifest.md", rc.Manifest(
217 schema_version=rc.SCHEMA_VERSION, roadmap_version=roadmap_version))
219 era_row = conn.execute(
"SELECT * FROM roadmap_eras LIMIT 1").fetchone()
220 _write(out_dir, f
"eras/{era_row['era_id']}.md", rc.Era(
221 schema_version=rc.SCHEMA_VERSION, id=era_row[
"era_id"],
222 era_label=
_one_line(era_row[
"era_label"])
or "roadmap era",
223 started_at=
_iso_date(era_row[
"started_at"]), order=era_row[
"sort_order"]))
225 for trow
in conn.execute(
"SELECT * FROM roadmap_tracks ORDER BY sort_order"):
226 _write(out_dir, f
"tracks/{trow['track_code']}.md", rc.Track(
227 schema_version=rc.SCHEMA_VERSION, track_code=trow[
"track_code"],
228 label=
_one_line(trow[
"label"])
or trow[
"track_code"],
229 description=
_one_line(trow[
"description"])
or None,
230 order=trow[
"sort_order"], active=bool(trow[
"active"])))
232 for vrow
in conn.execute(
"SELECT * FROM roadmap_versions ORDER BY version_id"):
233 _write(out_dir, f
"changelog/roadmap/{vrow['version_id']}.md", rc.Version(
234 schema_version=rc.SCHEMA_VERSION, version=vrow[
"version_id"],
235 date=
_iso_date(vrow[
"released_at"]), era=vrow[
"era_id"], kind=
"roadmap",
236 round=vrow[
"source_round_no"], notes=
_one_line(vrow[
"notes"], 200)
or None,
237 entries=[rc.ChangelogEntry(scope=
"roadmap", text=f
"{vrow['version_id']} seed import")]))
239 _emit_phases(conn, out_dir, titles, deps, tracks, sanitized, report)
240 _emit_items(conn, out_dir, deps, tracks, sanitized, report)
242 counts = {t: conn.execute(f
"SELECT COUNT(*) FROM {t}").fetchone()[0]
243 for t
in (
"roadmap_eras",
"roadmap_tracks",
"roadmap_versions",
"roadmap_phases",
"roadmap_items_v3")}
244 report.counts = counts
246 _dbg(
"OC_CHAT.MAIN", func, f
"emitted source (promotions={len(report.promotions)})", 2)
250def _sub_rows(conn: sqlite3.Connection, phase_code: str) -> list:
253 "SELECT * FROM roadmap_items_v3 WHERE parent_item_id = ? ORDER BY sort_order, item_id",
258def _emit_phases(conn, out_dir, titles, deps, tracks, sanitized, report) -> None:
259 phase_rows = conn.execute(
261 "SELECT p.*, i.status FROM roadmap_phases p JOIN roadmap_items_v3 i ON i.item_id = p.phase_code "
262 "WHERE i.parent_item_id IS NULL ORDER BY p.sort_order"
264 for prow
in phase_rows:
265 code = prow[
"phase_code"]
266 parent_done = prow[
"status"] ==
"done"
267 sub_models: list = []
269 sid = sanitized.get(srow[
"item_id"], srow[
"item_id"])
274 schema_version=rc.SCHEMA_VERSION, id=sid, title=
_one_line(srow[
"title"], 240)
or sid,
275 purpose=
_derive_purpose(srow[
"title"], srow[
"prose"], report), status=status,
276 priority=
_priority(srow[
"priority"]), order=srow[
"sort_order"],
277 depends_on=
_depends_on(srow[
"item_id"], deps, sanitized)),
280 sub_models.append(rc.SubPhase(id=sid, status=status,
281 description=
_one_line(srow[
"title"], 240)
or sid))
282 priority =
_priority(prow[
"priority_label"])
285 title = titles.get(code)
or _one_line(prow[
"label"])
or code
286 _write(out_dir, f
"phases/{code}.md", rc.Phase(
287 schema_version=rc.SCHEMA_VERSION, id=code, era=conn.execute(
"SELECT era_id FROM roadmap_eras LIMIT 1").fetchone()[0],
288 title=title, purpose=
_one_line(title, 240), priority=priority, status=prow[
"status"],
289 depends_on=
_depends_on(code, deps, sanitized), track_codes=tracks.get(code, []),
290 order=prow[
"sort_order"],
291 sub_phases=sub_models))
294def _emit_items(conn, out_dir, deps, tracks, sanitized, report) -> None:
297 "SELECT * FROM roadmap_items_v3 WHERE track_kind != 'm_phase' ORDER BY track_kind, sort_order, item_id"
300 iid = sanitized.get(row[
"item_id"], row[
"item_id"])
301 _write(out_dir, f
"items/{row['track_kind']}/{iid}.md", rc.RoadmapItem(
302 schema_version=rc.SCHEMA_VERSION, id=iid, track_kind=row[
"track_kind"],
303 title=
_one_line(row[
"title"], 240)
or iid,
305 status=row[
"status"], priority=
_priority(row[
"priority"]),
306 depends_on=
_depends_on(row[
"item_id"], deps, sanitized),
307 track_codes=tracks.get(row[
"item_id"], []), order=row[
"sort_order"]),
312 """A done parent's non-terminal child is reconciled to ``deferred`` (it was
313 deferred when the milestone shipped) — preserves the done milestone signal
314 while satisfying the done-parent⇒children-terminal invariant (AC §8.50).
315 Flagged for operator review (§9 3b)."""
316 if parent_done
and status
not in rc.TERMINAL_STATUSES:
317 report.reconciled_statuses.append({
"id": item_id,
"from": status,
"to":
"deferred"})
323 return value
if value
in _PRIORITIES
else None
328 if len((row[
"title"]
or "")) > 240:
329 reasons.append(
"title>240")
330 if row[
"priority"]
is not None:
331 reasons.append(
"priority")
332 if deps.get(row[
"item_id"]):
333 reasons.append(
"dependencies")
334 return ",".join(reasons)
None _emit_phases(conn, out_dir, titles, deps, tracks, sanitized, report)
Registry ingest_oracle(Path roadmap_dir, Path db_path)
None _emit_items(conn, out_dir, deps, tracks, sanitized, report)
dict[str, str] phase_title_map(Path roadmap_md)
str _promotion_reason(sqlite3.Row row, dict deps)
str _iso_date(object value)
MigrationReport emit_source(Path db_path, Path out_dir, Path roadmap_md)
tuple[dict, dict] _deps_and_tracks(sqlite3.Connection conn, MigrationReport report)
object _priority(object value)
str _reconcile_child_status(str item_id, str status, bool parent_done, MigrationReport report)
list _sub_rows(sqlite3.Connection conn, str phase_code)
bool _needs_promotion(sqlite3.Row row, dict deps)
str _derive_purpose(object title, object prose, MigrationReport report)
str _one_line(object text, int max_len=240)
str _sanitize_id(str raw, MigrationReport report)
None _write(Path out_dir, str rel, rc.RegistryModel model, str body="")
list _depends_on(str item_id, dict deps, dict sanitized)