125 envelope: dict[str, Any]
126 db_path: Optional[Path] =
None
127 source_hash: Optional[str] =
None
128 logical_state_hash: Optional[str] =
None
129 semantic_hash: Optional[str] =
None
133 lock_events: list[
"lock.LockReclaimEvent"] = field(default_factory=list)
134 error: Optional[BaseException] = field(default=
None, repr=
False)
138 raw = scanned.path.read_text(encoding=
"utf-8")
139 frontmatter, body = split_record_text(raw)
143 _assert_no_yaml_features(frontmatter, scanned.rel_path)
144 data = yaml.safe_load(frontmatter)
or {}
146 model = PromotedItem.model_validate(data)
147 except ValidationError
as error:
149 f
"{scanned.rel_path}: promoted-item validation failed: {error}",
150 code=SRC_STRICT_TYPE,
151 records=[scanned.rel_path],
153 if model.id != scanned.stem:
155 f
"{scanned.rel_path}: stem {scanned.stem!r} != id {model.id!r}",
156 code=SRC_STRICT_TYPE,
157 records=[scanned.rel_path],
163 categorized: dict[str, Any] = {
164 "manifest":
None,
"eras": [],
"tracks": [],
"versions": [],
165 "phases": [],
"items": [],
"promoted": [],
"framing": [],
"notes": [],
167 for scanned
in records:
168 if scanned.kind ==
"promoted_item":
170 categorized[
"promoted"].append((scanned, model, body))
172 model, body = load_record(scanned)
173 if scanned.kind ==
"manifest":
174 categorized[
"manifest"] = model
175 elif scanned.kind ==
"era":
176 categorized[
"eras"].append(model)
177 elif scanned.kind ==
"track":
178 categorized[
"tracks"].append(model)
179 elif scanned.kind ==
"version":
180 categorized[
"versions"].append(model)
181 elif scanned.kind ==
"framing":
184 categorized[
"framing"].append(model)
185 elif scanned.kind ==
"phase":
186 categorized[
"phases"].append((model, body))
187 elif scanned.kind ==
"roadmap_item":
188 categorized[
"items"].append((model, body))
189 elif scanned.kind ==
"note":
190 categorized[
"notes"].append((model, body))
198 workspace_id=str(uuid.uuid4()),
199 registry_id=str(uuid.uuid4()),
200 workspace_root=str(temp_path.parent),
201 git_common_dir=str(temp_path.parent /
".git"),
202 registry_path=str(temp_path),
203 checkpoint_path=str(temp_path.parent /
".option_c" /
"checkpoint.json"),
204 operator_slot=
"operator-source",
205 hmac_key_id=str(uuid.uuid4()),
206 secret_fingerprint=hashlib.sha256(b
"authored-projection").hexdigest(),
207 app_version=
"regenerate",
232 categorized: dict[str, list],
234 computed_source_hash: str,
236 expected_raw_hash: str,
238 """Step 7: build temp DB -> derive-verify -> stamp -> hash -> WAL-safe swap."""
241 categorized = {**categorized,
"versions": dedup_version_records(categorized.get(
"versions", []))}
242 temp_path = db_path.with_name(db_path.name +
".regen-tmp.sqlite")
243 for suffix
in (
"",
"-wal",
"-shm"):
246 derived = derive_versions(categorized.get(
"versions", []))
247 conn = sqlite3.connect(str(temp_path))
249 conn.execute(
"PRAGMA foreign_keys = OFF")
251 stamp_authored_projection(conn)
252 build_roadmap(conn, categorized, derived)
253 verify_versions(conn, categorized.get(
"versions", []))
254 computed_logical_hash = logical_state_hash(conn)
255 write_verification_stamp(
256 conn, source_hash=computed_source_hash, logical_state_hash=computed_logical_hash
258 assert_verified_projection(conn)
260 conn.execute(
"PRAGMA wal_checkpoint(TRUNCATE)")
268 verify_temp = sqlite3.connect(str(temp_path))
270 if verify_temp.execute(
"PRAGMA integrity_check").fetchone()[0] !=
"ok":
272 "integrity_check failed on temp DB before swap", code=REGEN_BUILD_FAILED
279 for suffix
in (
"-wal",
"-shm"):
289 if raw_source_tree_hash(source_dir) != expected_raw_hash:
291 "source/ mutated during regenerate (raw source-tree hash changed "
292 "since the pre-build scan)",
293 code=REGEN_SOURCE_MUTATED,
294 hint=
"re-run regenerate on a quiescent source tree",
296 os.replace(str(temp_path), str(db_path))
298 verify = sqlite3.connect(str(db_path))
300 if verify.execute(
"PRAGMA integrity_check").fetchone()[0] !=
"ok":
301 raise RegenerateError(
"integrity_check failed after swap", code=REGEN_BUILD_FAILED)
305 return computed_logical_hash
320 backend: str =
"sqlite",
321 scope: str =
"authored",
322 lock_path: Optional[Path] =
None,
324) -> RegenerateResult:
325 """Run the 7-step pipeline; build the worktree-local store at ``db_path``.
327 ``lock_path`` (R5): the regenerate lock file. The CLI (and any caller wanting
328 same-worktree mutual exclusion) MUST pass the explicit workspace lock path
329 (``<workspace>/.option_c/orchestration/regenerate.lock``, independent of
330 ``--db``). ``lock_path=None`` is a **test/library compatibility default only**
331 — resolved by the side-effect-free rule ``db_path.parent / "regenerate.lock"``
332 (never source_dir-derived, never workspace discovery); it must never silently
333 become the production contract (Codex-R04-P1). ``force`` reclaims a stale lock
334 (logged as a ``LockReclaimEvent``; NEVER skips validation/derive-verify).
337 source_dir = Path(source_dir)
338 db_path = Path(db_path)
339 raw_hash: Optional[str] =
None
340 src_hash: Optional[str] =
None
341 if backend !=
"sqlite":
343 f
"unsupported backend {backend!r}; v1 ships only sqlite",
344 code=REGEN_UNSUPPORTED_BACKEND,
347 if scope !=
"authored":
352 f
"--scope {scope!r} is not supported; regenerate rebuilds only the "
353 "authored projection",
354 code=REGEN_SCOPE_CONFLICT,
355 hint=
"drop --scope or pass --scope authored",
358 resolved_lock = Path(lock_path)
if lock_path
is not None else db_path.parent /
"regenerate.lock"
360 handle = lock.acquire(resolved_lock, force=force)
361 except RegenerateError
as error:
363 lock_events = [handle.reclaim_event]
if handle.reclaim_event
is not None else []
366 records = scan_source_tree(source_dir)
367 raw_hash = raw_source_tree_hash(source_dir)
369 format_source_tree(records, check=
True)
370 src_hash = source_hash(records)
372 validate_cross_record(categorized)
374 logical_hash =
_build_and_publish(categorized, db_path, src_hash, source_dir, raw_hash)
375 except (SourceError, RegenerateError)
as error:
377 envelope = build_envelope(
379 raw_source_tree_hash=
None if stage ==
"scan" else raw_hash,
380 source_hash=src_hash
if stage
in (
"cross_record",
"build")
else None,
382 return RegenerateResult(
False, envelope, error=error, lock_events=lock_events)
385 _dbg(
"OC_CHAT.MAIN", func,
"regenerate ok", 2)
386 envelope = build_envelope(
387 None, raw_source_tree_hash=raw_hash, source_hash=src_hash,
388 logical_state_hash=logical_hash,
391 True, envelope, db_path=db_path, source_hash=src_hash,
392 logical_state_hash=logical_hash, records=len(records),
393 lock_events=lock_events,