8The V0_3(.1) invariant-preservation matrix generator (Plan 142 P1.5, §5.3.1).
12Close the ``regenerate``-bypass class by construction for the DDL half and
13machine-gate it against drift for the application-layer half (Registry Source &
14Storage Spec V0.22 §5.3.1 / AC §8.49). ``regenerate`` builds the temp DB by
15**direct insert**, not through ``execute_domain_command``, so every
16application-layer-only invariant is silently bypassed on rebuild and must be
17independently handled. This module builds the **two machine-extracted
18inventories** that anchor the matrix:
201. A **DDL-constraint inventory** parsed from the consolidated
21 ``REGISTRY_SCHEMA_V0_3_1.sql`` (the post-amendment baseline) plus the
22 ``registry_meta.store_scope`` constraint emitted by
23 ``v0_3_2_roadmap_source_fields.migration_sql()`` — one stable-ID record for
24 every enforceable constraint (CHECK / unique index / ``UNIQUE`` / PK / FK /
25 ``NOT NULL`` / trigger); plain non-unique performance indexes are excluded.
262. An **application-layer ``@invariant`` inventory** extracted from
27 ``@invariant: <id>`` markers in the ``REGISTRY_ROADMAP_SCHEMA_V0_3_1``
28 amendment doc (HTML-comment + SQL-fence forms) and the rebuild enforcement
29 code (code-comment form).
31Each inventory ID is classified exactly once (``D``/``R``/``X``/``NA``) by the
32authored :data:`CLASSIFICATION` map; :func:`generate_matrix` joins the parsed
33inventories to that map and applies the D-safety predicate, so the test suite
34(``test_invariant_matrix.py``) can assert correctness, the two mutation gates,
35the ``{code} ⊆ {matrix}`` drift gate, and the reverse-inclusion R gate.
37Why ``.1.sql`` (not ``V0_3.sql`` + ``.1.sql``)
38----------------------------------------------
39AC §8.49 names "``REGISTRY_SCHEMA_V0_3.sql`` + the consolidated
40``REGISTRY_SCHEMA_V0_3_1.sql``". The consolidated ``.1.sql`` is, by its own
41header, the fresh-create state **after** applying ``V0_3.sql`` then the
42``v0_3_2`` migration — i.e. a superset of every ``V0_3`` roadmap constraint with
43the amendments folded in. Parsing both would double-count (duplicate stable
44IDs); parsing the consolidated baseline alone covers every ``V0_3`` constraint
45with no loss. The one constraint the consolidated ``.1.sql`` omits is the base
46``registry_meta.store_scope`` (a base-capability ALTER, not part of the roadmap
47snapshot — umbrella §12 wave-1 follow-up), which is recovered from
52- :func:`parse_ddl_inventory` — parse a ``.sql`` file into ``DdlConstraint`` records.
53- :func:`extract_markers` — extract ``@invariant: <id>`` markers from md/sql/py text.
54- :func:`generate_matrix` — join inventories to :data:`CLASSIFICATION`; return a
55 :class:`Matrix` with the first-class derive-verify rows + the validity hashes.
56- :func:`build_audit_payload` — the §9.1 audit ``.json`` payload (counts + hashes).
62 L2 - generation lifecycle
63 L3 - inventory + classification joins
64 L4 - per-constraint parse tracing
68- Pure / read-only: parses files + the authored classification; never mutates them.
69- Deterministic: inventories + matrix rows are order-stable (sorted by stable ID).
73- oc_diagnostics (structured debug logging via _dbg)
74- oc_chat.registry.migrations.v0_3_2_roadmap_source_fields (store_scope DDL)
76from __future__
import annotations
80from dataclasses
import dataclass, field
81from pathlib
import Path
82from typing
import Optional
84from oc_diagnostics
import _dbg
88_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
91_ORCH = Path(__file__).resolve().parents[1] /
"docs" /
"architecture" /
"orchestration"
93SCHEMA_V0_3_1_SQL = _ORCH /
"REGISTRY_SCHEMA_V0_3_1.sql"
95V0_3_1_DOC = _ORCH /
"REGISTRY_ROADMAP_SCHEMA_V0_3_1.md"
97_REGEN = Path(__file__).resolve().parent /
"regenerate"
103ENFORCEMENT_CODE_FILES: tuple[Path, ...] = (
104 _REGEN /
"cross_record.py",
105 _REGEN /
"derive_verify.py",
107 _REGEN /
"pipeline.py",
112SOURCE_POPULATED_TABLES: frozenset[str] = frozenset({
113 "roadmap_eras",
"roadmap_versions",
"roadmap_phases",
"roadmap_tracks",
114 "roadmap_items_v3",
"roadmap_item_tracks",
"roadmap_item_dependencies",
115 "roadmap_render_sections",
118NEVER_WRITTEN_TABLES: frozenset[str] = frozenset({
119 "roadmap_item_events",
"roadmap_plan_links",
122RUNTIME_TABLES: frozenset[str] = frozenset({
123 "agents",
"rounds",
"plans",
"operation_log",
127VALID_CLASSES: frozenset[str] = frozenset({
"D",
"R",
"X",
"NA"})
129WITNESS_TYPES: frozenset[str] = frozenset({
130 "source_validation_rule",
"projection_fill_rule",
"derive_verify_rule",
137@dataclass(frozen=True)
139 """One enforceable DDL constraint with a stable ID (the CI anchor)."""
145 target_table: Optional[str] =
None
146 partial: bool =
False
149@dataclass(frozen=True)
151 """One extracted ``@invariant: <id>`` marker and where it was found."""
158@dataclass(frozen=True)
160 """An authored classification of one inventory ID (the human-readable view,
161 machine-precise). The ``test_v0_3_invariant_matrix_complete`` predicate runs
167 witness_type: Optional[str] =
None
168 witness_id: Optional[str] =
None
170 source_validation_rule: Optional[str] =
None
171 acceptance_criterion: Optional[str] =
None
172 failure_row: Optional[str] =
None
173 negative_fixture: Optional[str] =
None
175 ratification: Optional[str] =
None
177 derive_rule_id: Optional[str] =
None
178 source_fields: tuple[str, ...] = ()
179 stored_columns: tuple[str, ...] = ()
180 verify_phase: Optional[str] =
None
181 read_barrier: Optional[str] =
None
182 positive_fixtures: tuple[str, ...] = ()
183 error_code: Optional[str] =
None
186@dataclass(frozen=True)
188 """The generated matrix: the joined inventories + their classifications."""
190 ddl: tuple[DdlConstraint, ...]
191 doc_markers: tuple[Marker, ...]
192 code_markers: tuple[Marker, ...]
193 rows: dict[str, MatrixRow]
194 constraint_inventory_hash: str
195 app_invariant_inventory_hash: str
201_CREATE_TABLE_RE = re.compile(
202 r"CREATE TABLE(?: IF NOT EXISTS)?\s+(\w+)\s*\((.*?)\n\)\s*;",
203 re.DOTALL | re.IGNORECASE,
205_CREATE_UNIQUE_INDEX_RE = re.compile(
206 r"CREATE\s+UNIQUE\s+INDEX(?: IF NOT EXISTS)?\s+(\w+)\s+ON\s+(\w+)\s*\(([^)]*)\)"
207 r"(?:\s+WHERE\s+(.+?))?\s*;",
208 re.DOTALL | re.IGNORECASE,
210_CREATE_TRIGGER_RE = re.compile(
211 r"CREATE\s+TRIGGER(?: IF NOT EXISTS)?\s+(\w+)\s+(BEFORE|AFTER)\s+(\w+)\s+ON\s+(\w+)",
214_ALTER_ADD_COLUMN_RE = re.compile(
215 r"ALTER TABLE\s+(\w+)\s+ADD COLUMN\s+(.+?);",
216 re.DOTALL | re.IGNORECASE,
221 """Deterministic readable slug from a SQL expression (stable across runs)."""
222 lowered = text.lower()
223 lowered = lowered.replace(
"!=",
"_ne_").replace(
"=",
"_eq_")
224 cleaned = re.sub(
r"[^a-z0-9]+",
"_", lowered).strip(
"_")
225 return re.sub(
r"_+",
"_", cleaned)
229 """Split a CREATE TABLE body into top-level comma-separated clauses (paren-aware)."""
230 clauses: list[str] = []
232 current: list[str] = []
238 if char ==
"," and depth == 0:
239 clauses.append(
"".join(current).strip())
243 tail =
"".join(current).strip()
249_SQL_RESERVED_CLAUSE_HEADS = (
"CHECK",
"PRIMARY",
"UNIQUE",
"FOREIGN",
"CONSTRAINT")
253 """Parse a single column-definition clause into its constraints."""
254 out: list[DdlConstraint] = []
255 name = clause.split()[0]
256 upper = clause.upper()
258 if "PRIMARY KEY" in upper:
259 out.append(
DdlConstraint(f
"ddl:{table}:pk:{name}", table,
"pk", name))
261 if re.search(
r"\bUNIQUE\b", upper)
and "PRIMARY KEY" not in upper:
262 out.append(
DdlConstraint(f
"ddl:{table}:unique_col:{name}", table,
"unique_col", name))
264 if "NOT NULL" in upper
and "PRIMARY KEY" not in upper:
265 out.append(
DdlConstraint(f
"ddl:{table}:notnull:{name}", table,
"notnull", name))
267 ref = re.search(
r"REFERENCES\s+(\w+)", clause, re.IGNORECASE)
269 target = ref.group(1)
271 f
"ddl:{table}:fk:{name}->{target}", table,
"fk", f
"{name} -> {target}",
275 col_check = re.search(
r"CHECK\s*\((.+)\)", clause, re.IGNORECASE | re.DOTALL)
277 expr =
" ".join(col_check.group(1).split())
279 f
"ddl:{table}:check:{name}", table,
"check", expr,
285 """Parse a table-level clause (composite PK, table CHECK, table FK)."""
286 out: list[DdlConstraint] = []
287 upper = clause.upper()
288 if upper.startswith(
"PRIMARY KEY"):
289 cols = re.search(
r"PRIMARY KEY\s*\((.+)\)", clause, re.IGNORECASE)
290 col_text =
_slug(cols.group(1))
if cols
else "composite"
291 out.append(
DdlConstraint(f
"ddl:{table}:pk:{col_text}", table,
"pk", clause.strip()))
292 elif upper.startswith(
"CHECK"):
293 expr_m = re.search(
r"CHECK\s*\((.+)\)\s*$", clause, re.IGNORECASE | re.DOTALL)
294 expr =
" ".join((expr_m.group(1)
if expr_m
else clause).split())
296 f
"ddl:{table}:check:{_slug(expr)}", table,
"check", expr,
298 elif upper.startswith(
"UNIQUE"):
299 cols = re.search(
r"UNIQUE\s*\((.+)\)", clause, re.IGNORECASE)
300 col_text =
_slug(cols.group(1))
if cols
else "cols"
302 f
"ddl:{table}:unique_cols:{col_text}", table,
"unique_index", clause.strip(),
304 elif upper.startswith(
"FOREIGN KEY"):
305 fk = re.search(
r"FOREIGN KEY\s*\((.+?)\)\s*REFERENCES\s+(\w+)", clause, re.IGNORECASE)
307 col_text =
_slug(fk.group(1))
310 f
"ddl:{table}:fk:{col_text}->{target}", table,
"fk",
311 clause.strip(), target_table=target,
317 out: list[DdlConstraint] = []
318 for match
in _CREATE_TABLE_RE.finditer(sql):
319 table = match.group(1)
320 if table.endswith(
"_new"):
322 body = match.group(2)
326 head = clause.split(
"(", 1)[0].strip().upper().split()
327 head_word = head[0]
if head
else ""
328 if head_word
in _SQL_RESERVED_CLAUSE_HEADS:
336 out: list[DdlConstraint] = []
337 for match
in _CREATE_UNIQUE_INDEX_RE.finditer(sql):
338 index_name, table, cols, where = match.groups()
339 if table.endswith(
"_new"):
341 partial = where
is not None
343 f
"ddl:{table}:unique_index:{index_name}", table,
"unique_index",
344 f
"{index_name}({' '.join(cols.split())})"
345 + (f
" WHERE {' '.join(where.split())}" if partial
else ""),
348 for match
in _CREATE_TRIGGER_RE.finditer(sql):
349 trigger_name, _timing, _event, table = match.groups()
350 if table.endswith(
"_new"):
353 f
"ddl:{table}:trigger:{trigger_name}", table,
"trigger", trigger_name,
359 """Recover ``registry_meta.store_scope`` from the v0_3_2 migration ALTER (the
360 consolidated ``.1.sql`` omits the base ``registry_meta`` table — umbrella §12)."""
361 out: list[DdlConstraint] = []
362 sql = v0_3_2_roadmap_source_fields.migration_sql()
363 for match
in _ALTER_ADD_COLUMN_RE.finditer(sql):
364 table, coldef = match.groups()
365 if table !=
"registry_meta":
373 sql_path: Path = SCHEMA_V0_3_1_SQL,
375 include_store_scope: bool =
True,
376) -> list[DdlConstraint]:
377 """Parse ``sql_path`` (+ the ``registry_meta.store_scope`` migration ALTER)
378 into a deterministic, de-duplicated DDL-constraint inventory."""
379 sql = sql_path.read_text(encoding=
"utf-8")
381 if include_store_scope:
384 seen: dict[str, DdlConstraint] = {}
385 for constraint
in constraints:
386 seen.setdefault(constraint.stable_id, constraint)
387 return sorted(seen.values(), key=
lambda c: c.stable_id)
395_HTML_MARKER_RE = re.compile(
r"<!--\s*@invariant:\s*([A-Za-z0-9._:>-]+)\s*-->")
396_SQL_MARKER_RE = re.compile(
r"--\s*@invariant:\s*([A-Za-z0-9._:>-]+)")
398_CODE_MARKER_RE = re.compile(
r"#\s*@invariant:\s*([A-Za-z0-9._:>-]+)")
402 """Extract ``@invariant: <id>`` markers from ``text`` in the given ``form``
403 (``html`` = Markdown prose, ``sql`` = SQL fence, ``code`` = Python comment)."""
405 ids = _HTML_MARKER_RE.findall(text)
407 ids += _SQL_MARKER_RE.findall(text)
409 ids = _SQL_MARKER_RE.findall(text)
411 ids = _CODE_MARKER_RE.findall(text)
413 raise ValueError(f
"unknown marker form {form!r}")
414 return [
Marker(invariant_id=mid, source=source, form=form)
for mid
in ids]
418 """Extract the prose ``@invariant`` markers from the V0_3_1 amendment doc."""
420 doc_path.read_text(encoding=
"utf-8"), form=
"html", source=doc_path.name
425 """Extract ``@invariant`` markers from the rebuild enforcement code."""
426 markers: list[Marker] = []
428 if not path.exists():
431 path.read_text(encoding=
"utf-8"), form=
"code", source=path.name
440 digest = hashlib.sha256()
441 for line
in sorted(lines):
442 digest.update(line.encode(
"utf-8"))
444 return digest.hexdigest()
448 """A stable hash over the DDL inventory (stable IDs) — the stamp + audit key."""
453 """A stable hash over the extracted app-layer ``@invariant`` ids."""
454 return _hash_lines(sorted({m.invariant_id
for m
in markers}))
469WITNESS_REGISTRY: dict[str, tuple[str, str]] = {
471 "sv.status_enum": (
"source_validation_rule",
472 "option_c_registry_contracts.base.Status Literal (§7 enum)"),
473 "sv.priority_enum": (
"source_validation_rule",
474 "option_c_registry_contracts.base.Priority Literal (§7 enum)"),
475 "sv.track_kind_enum": (
"source_validation_rule",
477 "base.NonPhaseTrackKind + builder 'm_phase' (§7 enum)"),
478 "sv.dep_kind_enum": (
"source_validation_rule",
479 "base.DepKind 3-token Literal ⊂ the 4-token DDL CHECK"),
480 "sv.lifecycle": (
"source_validation_rule",
481 "base.check_item_lifecycle (self-ref, superseded⇒superseded_by, "
482 "successor⇒deferred)"),
483 "sv.stem_eq_id": (
"source_validation_rule",
484 "canonical.load_record / pipeline._load_promoted stem==id "
485 "(filename-keyed natural id — a duplicate needs two same-named files)"),
486 "sv.required_fields": (
"source_validation_rule",
487 "pydantic required strict types (RequiredLine / Line300 / "
488 "EntityId / IsoDate / int) — a NULL/missing authored value is rejected on load"),
490 "pf.timestamps": (
"projection_fill_rule",
492 "build.SOURCE_EPOCH fill for created_at/updated_at/last_updated_at (EXCLUDED)"),
493 "pf.created_by_slot": (
"projection_fill_rule",
495 "build.SOURCE_SLOT 'source' placeholder NOT-NULL fill (FK half is X)"),
496 "pf.store_scope": (
"projection_fill_rule",
497 "store_scope.stamp_authored_projection writes 'authored_projection'"),
498 "pf.parallel_track": (
"projection_fill_rule",
"build sets parallel_track=0 (no source field)"),
499 "pf.parent_item_id_derived": (
"projection_fill_rule",
500 "parent_item_id set only from sub_phase nesting / promoted dir "
501 "(never authored) — a self-parent is unreachable"),
502 "pf.phase_parent_null": (
"projection_fill_rule",
503 "build always sets parent_phase_id=NULL (v1 has no nested phases)"),
504 "pf.surrogate_pk": (
"projection_fill_rule",
506 "SQLite AUTOINCREMENT assigns the surrogate phase_id/track_id/id"),
507 "pf.item_phase_id": (
"projection_fill_rule",
509 "phase fan-out sets phase_id for every m_phase row; non-phase items get NULL "
510 "(no source phase_id field) — C17/C18 unreachable"),
511 "pf.insert_or_ignore": (
"projection_fill_rule",
512 "build INSERT OR IGNORE dedups the composite membership/dependency PK"),
513 "pf.track_id_resolved": (
"projection_fill_rule",
515 "build resolves track_code→track_id (SRC_BAD_TRACK_CODES if unknown); "
516 "the surrogate is never NULL"),
517 "pf.render_sections": (
"projection_fill_rule",
519 "render_sections.seed_render_sections deterministic builder-fill from "
520 "the constant manifest (no §3.1 source entity, never authored); every "
521 "file_kind/query_kind/active value satisfies its CHECK by construction "
522 "and required_min_version is NULL → the FK never fires (P1.6 seam)"),
523 "sv.track_active": (
"source_validation_rule",
524 "base.Track.active bool (default True) → always a valid 0/1"),
526 "inv.version_lineage_derived": (
"derive_verify_rule",
527 "derive_verify.derive_versions + verify_versions (step-7 publish-gate)"),
534def _D(witness_id: str, handling: str, **kw: object) -> MatrixRow:
535 wtype = WITNESS_REGISTRY[witness_id][0]
537 classification=
"D", handling=handling,
538 witness_type=wtype, witness_id=witness_id, **kw,
542def _R(rule: str, ac: str, code: str, neg_fixture: str, handling: str) -> MatrixRow:
544 classification=
"R", handling=handling, source_validation_rule=rule,
545 acceptance_criterion=ac, failure_row=code, negative_fixture=neg_fixture,
549def _X(ratification: str, handling: str) -> MatrixRow:
550 return MatrixRow(classification=
"X", handling=handling, ratification=ratification)
553def _NA(handling: str) -> MatrixRow:
554 return MatrixRow(classification=
"NA", handling=handling)
562 handling=
"version status / lineage (C3–C6) / one-current-per-era / bump_kind are "
563 "DERIVED from the ordered changelog (numeric-per-field) then recomputed-and-"
564 "verified at step-7 before any use; a violating value is unreachable in the built store",
565 witness_type=
"derive_verify_rule",
566 witness_id=
"inv.version_lineage_derived",
567 derive_rule_id=
"derive_verify.derive_versions",
568 source_fields=(
"version",
"era",
"date",
"round",
"bump_override"),
569 stored_columns=(
"status",
"parent_version_id",
"supersedes_version_id",
570 "superseded_by_version_id",
"bump_kind"),
571 verify_phase=
"step-7",
573 read_barrier=
"verified_projection.assert_verified_projection (REGEN_UNVERIFIED_PROJECTION)",
574 positive_fixtures=(
"test_version_ordering_numeric_across_major_boundary",
575 "test_version_selection_skip_superseded",
576 "test_version_selection_per_era_cardinality"),
577 error_code=
"REGEN_BUILD_FAILED",
580_DV =
"inv.version_lineage_derived"
585_EXPLICIT: dict[str, MatrixRow] = {
587 "ddl:registry_meta:check:store_scope":
_D(
"pf.store_scope",
588 "builder stamps a valid scope token; author cannot write registry_meta"),
589 "ddl:registry_meta:notnull:store_scope":
_D(
"pf.store_scope",
590 "builder always stamps store_scope before any runtime open"),
592 "ddl:roadmap_eras:notnull:era_label":
_D(
"sv.required_fields",
"Era.era_label RequiredLine"),
593 "ddl:roadmap_eras:notnull:sort_order":
_D(
"sv.required_fields",
"Era.order required int"),
594 "ddl:roadmap_eras:notnull:started_at":
_D(
"sv.required_fields",
"Era.started_at IsoDate"),
595 "ddl:roadmap_eras:pk:era_id":
_D(
"sv.stem_eq_id",
596 "era_id is the filename stem (stem==id enforced); a duplicate needs two same-named files"),
597 "ddl:roadmap_eras:unique_index:idx_roadmap_eras_sort":
_R(
598 "inv.era_track_order_unique",
"8.56",
"SRC_DUPLICATE_ORDER",
599 "test_duplicate_era_order_rejected",
"two eras with the same order — author-reachable"),
601 "ddl:roadmap_versions:check:bump_kind":
_D(_DV,
"bump_kind derived (verified step-7)"),
602 "ddl:roadmap_versions:check:not_status_eq_superseded_and_superseded_by_version_id_is_null":
_D(
603 _DV,
"C6: lineage derived — superseded⇒superseded_by unreachable in built store"),
604 "ddl:roadmap_versions:check:status":
_D(_DV,
"status derived (draft/current/superseded)"),
605 "ddl:roadmap_versions:check:version_id_ne_parent_version_id":
_D(_DV,
"C3: lineage derived (no authored field)"),
606 "ddl:roadmap_versions:check:version_id_ne_superseded_by_version_id":
_D(_DV,
"C5: lineage derived"),
607 "ddl:roadmap_versions:check:version_id_ne_supersedes_version_id":
_D(_DV,
"C4: lineage derived"),
608 "ddl:roadmap_versions:fk:era_id->roadmap_eras":
_R(
609 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
610 "test_dangling_version_era_fails",
"version.era may reference a missing era — author-reachable"),
611 "ddl:roadmap_versions:fk:parent_version_id->roadmap_versions":
_D(_DV,
"lineage derived within the version set"),
612 "ddl:roadmap_versions:fk:source_round_no->rounds":
_X(
"V0_3_1 §9.2",
613 "runtime-reference relaxation (Option A): authored provenance, FK not enforced on the authored rebuild"),
614 "ddl:roadmap_versions:fk:superseded_by_version_id->roadmap_versions":
_D(_DV,
"lineage derived"),
615 "ddl:roadmap_versions:fk:supersedes_version_id->roadmap_versions":
_D(_DV,
"lineage derived"),
616 "ddl:roadmap_versions:notnull:bump_kind":
_D(_DV,
"bump_kind derived (always set)"),
617 "ddl:roadmap_versions:notnull:era_id":
_D(
"sv.required_fields",
"Version.era required EntityId"),
618 "ddl:roadmap_versions:notnull:last_updated_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill (EXCLUDED)"),
619 "ddl:roadmap_versions:notnull:released_at":
_D(
"sv.required_fields",
"Version.date IsoDate (released_at)"),
620 "ddl:roadmap_versions:notnull:status":
_D(_DV,
"status derived (always set)"),
621 "ddl:roadmap_versions:pk:version_id":
_R(
622 "inv.version_metadata_agreement",
"8.38",
"SRC_VERSION_METADATA_CONFLICT",
623 "test_version_metadata_conflict",
"a roadmap+product pair shares version_id — merged to one row, agreement-checked"),
624 "ddl:roadmap_versions:unique_index:idx_roadmap_versions_one_current_per_era":
_D(
625 _DV,
"one-current-per-era derived + verified step-7 (AC §8.26)"),
627 "ddl:roadmap_phases:check:parallel_track":
_D(
"pf.parallel_track",
"builder sets 0 (no source field)"),
628 "ddl:roadmap_phases:check:phase_id_ne_parent_phase_id":
_D(
"pf.phase_parent_null",
629 "C9: parent_phase_id always NULL on the rebuild"),
630 "ddl:roadmap_phases:check:priority_label":
_D(
"sv.priority_enum",
"Phase.priority Priority enum"),
631 "ddl:roadmap_phases:fk:introduced_in_version_id->roadmap_versions":
_R(
632 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
633 "test_dangling_phase_version_pointer_fails",
"introduced_in_version may dangle — author-reachable"),
634 "ddl:roadmap_phases:fk:parent_phase_id->roadmap_phases":
_D(
"pf.phase_parent_null",
635 "parent_phase_id always NULL (v1 has no nested phases) — resolves trivially"),
636 "ddl:roadmap_phases:fk:retired_in_version_id->roadmap_versions":
_R(
637 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
638 "test_dangling_phase_version_pointer_fails",
"retired_in_version may dangle — author-reachable"),
639 "ddl:roadmap_phases:notnull:created_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill"),
640 "ddl:roadmap_phases:notnull:label":
_D(
"sv.required_fields",
"Phase.title RequiredLine (label)"),
641 "ddl:roadmap_phases:notnull:parallel_track":
_D(
"pf.parallel_track",
"builder sets 0"),
642 "ddl:roadmap_phases:notnull:phase_code":
_D(
"sv.required_fields",
"Phase.id EntityId (phase_code)"),
643 "ddl:roadmap_phases:notnull:purpose":
_D(
"sv.required_fields",
"Phase.purpose Line300 (non-empty)"),
644 "ddl:roadmap_phases:notnull:sort_order":
_D(
"sv.required_fields",
"Phase.order required int"),
645 "ddl:roadmap_phases:notnull:updated_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill"),
647 "ddl:roadmap_phases:pk:phase_id":
_D(
"pf.surrogate_pk",
"AUTOINCREMENT surrogate (source-populated)"),
648 "ddl:roadmap_phases:unique_col:phase_code":
_R(
649 "inv.phase_track_code_unique",
"8.56",
"SRC_DUPLICATE_CODE",
650 "test_duplicate_phase_code_rejected",
"two phases with the same code — author-reachable"),
652 "ddl:roadmap_tracks:check:active":
_D(
"sv.track_active",
"Track.active bool → 0/1"),
653 "ddl:roadmap_tracks:notnull:active":
_D(
"sv.track_active",
"Track.active bool (default True)"),
654 "ddl:roadmap_tracks:notnull:label":
_D(
"sv.required_fields",
"Track.label RequiredLine"),
655 "ddl:roadmap_tracks:notnull:sort_order":
_D(
"sv.required_fields",
"Track.order required int"),
656 "ddl:roadmap_tracks:notnull:track_code":
_D(
"sv.required_fields",
"Track.track_code EntityId"),
657 "ddl:roadmap_tracks:pk:track_id":
_D(
"pf.surrogate_pk",
"AUTOINCREMENT surrogate"),
658 "ddl:roadmap_tracks:unique_col:track_code":
_R(
659 "inv.phase_track_code_unique",
"8.56",
"SRC_DUPLICATE_CODE",
660 "test_duplicate_track_code_rejected",
"two tracks with the same code — author-reachable"),
661 "ddl:roadmap_tracks:unique_index:idx_roadmap_tracks_sort":
_R(
662 "inv.era_track_order_unique",
"8.56",
"SRC_DUPLICATE_ORDER",
663 "test_duplicate_track_order_rejected",
"two tracks with the same order — author-reachable"),
665 "ddl:roadmap_items_v3:check:item_id_ne_parent_item_id":
_D(
"pf.parent_item_id_derived",
666 "C14: parent_item_id is builder-derived; a self-parent is unreachable"),
667 "ddl:roadmap_items_v3:check:item_id_ne_superseded_by_item_id":
_D(
"sv.lifecycle",
668 "C15: check_item_lifecycle rejects superseded_by==id"),
669 "ddl:roadmap_items_v3:check:priority":
_D(
"sv.priority_enum",
"RoadmapItem.priority Priority enum (nullable)"),
670 "ddl:roadmap_items_v3:check:status":
_D(
"sv.status_enum",
"Status enum"),
671 "ddl:roadmap_items_v3:check:status_ne_superseded_or_superseded_by_item_id_is_not_null":
_D(
672 "sv.lifecycle",
"C16: check_item_lifecycle requires superseded_by when status=superseded"),
673 "ddl:roadmap_items_v3:check:track_kind":
_D(
"sv.track_kind_enum",
"track_kind enum"),
674 "ddl:roadmap_items_v3:check:track_kind_ne_m_phase_or_phase_id_is_not_null":
_D(
"pf.item_phase_id",
675 "C17: phase fan-out always sets phase_id for m_phase rows"),
676 "ddl:roadmap_items_v3:check:track_kind_not_in_backlog_vision_or_phase_id_is_null":
_D(
"pf.item_phase_id",
677 "C18: non-phase items have no source phase_id field → builder sets NULL"),
678 "ddl:roadmap_items_v3:fk:closed_in_version_id->roadmap_versions":
_R(
679 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
680 "test_dangling_item_version_pointer_fails",
"closed_in_version may dangle — author-reachable"),
681 "ddl:roadmap_items_v3:fk:closed_round_no->rounds":
_X(
"V0_3_1 §9.2",
683 "runtime-reference relaxation (Option A): always NULL on a pure source rebuild, EXCLUDED from the hash"),
684 "ddl:roadmap_items_v3:fk:introduced_in_version_id->roadmap_versions":
_R(
685 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
686 "test_dangling_item_version_pointer_fails",
"introduced_in_version may dangle — author-reachable"),
687 "ddl:roadmap_items_v3:fk:parent_item_id->roadmap_items_v3":
_D(
"pf.parent_item_id_derived",
688 "parent_item_id derived from nesting/promoted dir → always resolves to a built item"),
689 "ddl:roadmap_items_v3:fk:phase_id->roadmap_phases":
_D(
"pf.item_phase_id",
690 "phase_id derived from the fan-out → always resolves"),
691 "ddl:roadmap_items_v3:fk:successor_item_id->roadmap_items_v3":
_R(
692 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
693 "test_dangling_successor_pointer_fails",
"successor is authored and may dangle — author-reachable"),
694 "ddl:roadmap_items_v3:fk:superseded_by_item_id->roadmap_items_v3":
_R(
695 "inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
696 "test_dangling_superseded_by_pointer_fails",
"superseded_by is authored and may dangle — author-reachable"),
697 "ddl:roadmap_items_v3:notnull:created_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill"),
698 "ddl:roadmap_items_v3:notnull:purpose":
_D(
"sv.required_fields",
"purpose Line300 (non-empty)"),
699 "ddl:roadmap_items_v3:notnull:sort_order":
_D(
"sv.required_fields",
"order required int"),
700 "ddl:roadmap_items_v3:notnull:status":
_D(
"sv.status_enum",
"status enum (always set)"),
701 "ddl:roadmap_items_v3:notnull:title":
_D(
"sv.required_fields",
"title RequiredLine"),
702 "ddl:roadmap_items_v3:notnull:track_kind":
_D(
"sv.track_kind_enum",
"track_kind enum (always set)"),
703 "ddl:roadmap_items_v3:notnull:updated_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill"),
704 "ddl:roadmap_items_v3:pk:item_id":
_R(
705 "inv.item_id_unique",
"8.39",
"SRC_ITEM_ID_COLLISION",
706 "test_item_id_union_collision_case_insensitive",
"an id may collide across file ids ∪ nested sub_phase ids ∪ promoted"),
708 "ddl:roadmap_item_tracks:fk:item_id->roadmap_items_v3":
_D(
"pf.parent_item_id_derived",
709 "the membership owner is the item being built — always present"),
710 "ddl:roadmap_item_tracks:fk:track_id->roadmap_tracks":
_R(
711 "inv.track_codes_resolve",
"8.31",
"SRC_BAD_TRACK_CODES",
712 "test_unknown_track_code_rejected",
"track_codes may reference an unknown track — author-reachable"),
713 "ddl:roadmap_item_tracks:notnull:created_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill"),
714 "ddl:roadmap_item_tracks:notnull:item_id":
_D(
"sv.required_fields",
"membership owner id"),
715 "ddl:roadmap_item_tracks:notnull:track_id":
_D(
"pf.track_id_resolved",
"resolved track_code→track_id"),
716 "ddl:roadmap_item_tracks:pk:item_id_track_id":
_D(
"pf.insert_or_ignore",
"INSERT OR IGNORE dedups duplicates"),
718 "ddl:roadmap_item_dependencies:check:from_item_id_ne_to_item_id":
_R(
719 "inv.dependency_acyclic",
"8.47",
"SRC_DEPENDENCY_CYCLE",
720 "test_dependency_self_edge_rejected",
"C20: a self-edge is a degenerate cycle — author-reachable"),
721 "ddl:roadmap_item_dependencies:check:kind":
_D(
"sv.dep_kind_enum",
722 "the 3 authorable kinds are a subset of the 4-token DDL CHECK; the builder never writes part_of"),
723 "ddl:roadmap_item_dependencies:fk:created_by_slot->agents":
_X(
"V0_3_1 §9.2",
725 "runtime-reference relaxation (Option A): FK-to-agents half (the NOT-NULL half is D via 'source' placeholder)"),
726 "ddl:roadmap_item_dependencies:fk:from_item_id->roadmap_items_v3":
_R(
727 "inv.depends_on_resolves",
"8.3",
"SRC_DANGLING_DEPENDS_ON",
728 "test_dangling_depends_on_rejected",
"depends_on predecessor may be unknown — author-reachable"),
729 "ddl:roadmap_item_dependencies:fk:to_item_id->roadmap_items_v3":
_R(
730 "inv.depends_on_resolves",
"8.3",
"SRC_DANGLING_DEPENDS_ON",
731 "test_dangling_depends_on_rejected",
"the dependent is the owning item; resolution shares the depends_on check"),
732 "ddl:roadmap_item_dependencies:notnull:created_at":
_D(
"pf.timestamps",
"SOURCE_EPOCH fill"),
733 "ddl:roadmap_item_dependencies:notnull:created_by_slot":
_D(
"pf.created_by_slot",
"'source' placeholder fill"),
734 "ddl:roadmap_item_dependencies:notnull:from_item_id":
_D(
"sv.required_fields",
"depends_on edge id (EntityId)"),
735 "ddl:roadmap_item_dependencies:notnull:kind":
_D(
"sv.dep_kind_enum",
"DepKind defaults to 'blocks' (always set)"),
736 "ddl:roadmap_item_dependencies:notnull:to_item_id":
_D(
"sv.required_fields",
"the owning item id"),
737 "ddl:roadmap_item_dependencies:pk:from_item_id_to_item_id_kind":
_D(
"pf.insert_or_ignore",
738 "INSERT OR IGNORE dedups a repeated (from,to,kind) edge"),
743_APP_LAYER: dict[str, MatrixRow] = {
745 _DV: _VERSION_LINEAGE_ROW,
747 "inv.b_closed_version_required":
_X(
"V0_3_1 §9.1",
748 "inv-(b) done/deferred⇒closed_in_version: a deliberate post-cutover tightening, "
749 "grandfathered by the terminal-at-cutover seed ledger; v1 ships lenient (dormant)"),
751 "inv.item_id_unique":
_R(
"inv.item_id_unique",
"8.39",
"SRC_ITEM_ID_COLLISION",
752 "test_item_id_union_collision_case_insensitive",
"id unique across file ids ∪ nested ∪ promoted (step-6)"),
753 "inv.depends_on_resolves":
_R(
"inv.depends_on_resolves",
"8.3",
"SRC_DANGLING_DEPENDS_ON",
754 "test_dangling_depends_on_rejected",
"every depends_on predecessor resolves (step-6)"),
755 "inv.dependency_acyclic":
_R(
"inv.dependency_acyclic",
"8.47",
"SRC_DEPENDENCY_CYCLE",
756 "test_dependency_cycle_rejected_from_source",
"kind-unscoped dependency acyclicity (step-6, OQ-R-07)"),
757 "inv.successor_acyclic":
_R(
"inv.successor_acyclic",
"8.42",
"SRC_SUCCESSOR_CYCLE",
758 "test_successor_cycle_rejected",
"successor-chain acyclicity (step-6)"),
759 "inv.done_parent_terminal_children":
_R(
"inv.done_parent_terminal_children",
"8.50",
760 "SRC_DONE_PARENT_LIVE_CHILD",
"test_done_parent_requires_terminal_children",
761 "a done parent has only terminal children (step-6, OQ-R-07)"),
762 "inv.era_track_order_unique":
_R(
"inv.era_track_order_unique",
"8.56",
"SRC_DUPLICATE_ORDER",
763 "test_duplicate_era_order_rejected",
"era/track order uniqueness pre-check (step-6)"),
764 "inv.phase_track_code_unique":
_R(
"inv.phase_track_code_unique",
"8.56",
"SRC_DUPLICATE_CODE",
765 "test_duplicate_phase_code_rejected",
"phase_code/track_code uniqueness pre-check (step-6)"),
766 "inv.version_metadata_agreement":
_R(
"inv.version_metadata_agreement",
"8.38",
767 "SRC_VERSION_METADATA_CONFLICT",
"test_version_metadata_conflict",
768 "same-version roadmap+product metadata agreement → one row (step-6)"),
769 "inv.track_codes_resolve":
_R(
"inv.track_codes_resolve",
"8.31",
"SRC_BAD_TRACK_CODES",
770 "test_unknown_track_code_rejected",
"every track_code resolves to a track (build)"),
771 "inv.intra_source_fk_resolves":
_R(
"inv.intra_source_fk_resolves",
"8.32",
"REGEN_BUILD_FAILED",
772 "test_dangling_version_era_fails",
773 "intra-source FK resolution backstop (post-build foreign_key_check; Option A tolerates runtime FKs)"),
778 """Assemble the authoritative classification: explicit source-populated +
779 app-layer rows, plus programmatic NA for the never-written + v1-empty tables.
781 Built once from the canonical ``.1.sql`` inventory so the mutation tests
782 (which parse a *fixture* ``.sql``) see injected ids absent from this map.
784 classification: dict[str, MatrixRow] = dict(_EXPLICIT)
785 classification.update(_APP_LAYER)
787 if constraint.stable_id
in classification:
789 if constraint.table
in NEVER_WRITTEN_TABLES:
790 classification[constraint.stable_id] =
_NA(
791 f
"{constraint.table} is never written on the authored rebuild "
792 "(events/plan-links are runtime-only; AC §8.23) — never exercised")
793 elif constraint.table ==
"roadmap_render_sections":
794 classification[constraint.stable_id] =
_D(
"pf.render_sections",
796 "roadmap_render_sections has no §3.1 source entity but is SEEDED deterministically "
797 "on every rebuild by P1.6 render_sections.seed_render_sections (the P1.4↔P1.6 seam, "
798 "wired in build.build_roadmap); the constant manifest satisfies every CHECK/NOT-NULL "
799 "by construction and seeds required_min_version NULL → the FK never fires")
800 return classification
809 sql_path: Path = SCHEMA_V0_3_1_SQL,
810 doc_path: Path = V0_3_1_DOC,
811 code_files: tuple[Path, ...] = ENFORCEMENT_CODE_FILES,
813 """Join the two machine-extracted inventories to :data:`CLASSIFICATION`."""
814 func =
"generate_matrix"
818 all_markers = doc_markers + code_markers
820 cid: CLASSIFICATION[cid]
821 for cid
in ({c.stable_id
for c
in ddl} | {m.invariant_id
for m
in all_markers})
822 if cid
in CLASSIFICATION
824 _dbg(
"OC_CHAT.MAIN", func,
825 f
"ddl={len(ddl)} doc_markers={len(doc_markers)} code_markers={len(code_markers)}", 3)
828 doc_markers=tuple(doc_markers),
829 code_markers=tuple(code_markers),
840 """Run the §5.3.1 correctness predicate over ``matrix``; return a list of
841 violation strings (empty == the matrix is complete + correct).
843 Asserts: (a) every DDL-inventory id + every extracted ``@invariant`` id is
844 classified **exactly once**; (b) every ``D`` satisfies the D-safety predicate
845 (a registered witness; a ``derive_verify_rule`` names stored columns + a
846 positive fixture per branch); (c) no catch-all (every row carries a specific
847 handling); (d) every ``R`` names a source-validation rule + AC + §10 code +
848 negative fixture; (e) every ``X`` names a ``V0_3_1`` ratification; (f) every
849 ``NA`` names why the rebuild never exercises it.
851 violations: list[str] = []
852 classified = matrix.rows
853 inventory_ids = {c.stable_id
for c
in matrix.ddl}
854 inventory_ids |= {m.invariant_id
for m
in matrix.doc_markers}
855 inventory_ids |= {m.invariant_id
for m
in matrix.code_markers}
856 for cid
in sorted(inventory_ids):
857 if cid
not in classified:
858 violations.append(f
"unclassified inventory id (no matrix row): {cid}")
859 for cid, row
in sorted(classified.items()):
860 if row.classification
not in VALID_CLASSES:
861 violations.append(f
"{cid}: invalid class {row.classification!r}")
862 if not row.handling.strip():
863 violations.append(f
"{cid}: catch-all — no handling rationale")
864 if row.classification ==
"D":
865 if row.witness_id
not in WITNESS_REGISTRY:
866 violations.append(f
"{cid}: D names no registered witness ({row.witness_id!r})")
867 elif row.witness_type != WITNESS_REGISTRY[row.witness_id][0]:
868 violations.append(f
"{cid}: D witness_type mismatch vs registry")
869 if row.witness_type ==
"derive_verify_rule":
873 witness_row = classified.get(row.witness_id
or "")
874 if witness_row
is None:
875 violations.append(f
"{cid}: derive_verify witness {row.witness_id!r} has no first-class row")
877 if not witness_row.stored_columns:
878 violations.append(f
"{row.witness_id}: derive_verify_rule names no stored_columns")
879 if not witness_row.positive_fixtures:
881 f
"{row.witness_id}: derive_verify_rule names no positive_fixture (branch coverage)")
882 elif row.classification ==
"R":
883 if not row.source_validation_rule:
884 violations.append(f
"{cid}: R names no source_validation_rule")
885 if not row.acceptance_criterion:
886 violations.append(f
"{cid}: R names no acceptance criterion")
887 if not row.failure_row:
888 violations.append(f
"{cid}: R names no §10 failure code")
889 if not row.negative_fixture:
890 violations.append(f
"{cid}: R names no negative fixture")
891 elif row.classification ==
"X":
892 if not row.ratification:
893 violations.append(f
"{cid}: X names no V0_3_1 ratification")
898 """{code-enforced @invariant ids} ⊄ {matrix ids} — the drift gate (AC §8.49)."""
900 m.invariant_id
for m
in matrix.code_markers
if m.invariant_id
not in matrix.rows
905 """Matrix ``R`` ids whose ``source_validation_rule`` has no enforcement-code
906 ``@invariant`` marker — the reverse-inclusion R-completeness gate (AC §8.49)."""
907 code_ids = {m.invariant_id
for m
in matrix.code_markers}
909 cid
for cid, row
in matrix.rows.items()
910 if row.classification ==
"R" and row.source_validation_rule
not in code_ids
915 """The §9.1 app-layer-invariant checklist denominator: every invariant the
916 rebuild bypasses unless independently handled — the ``inv.*`` rows (R +
917 derive-verify) plus the ``X`` conscious relaxations. The pure-DDL ``D``/``NA``
918 constraints are by-construction and are NOT app-layer invariants (§5.3.1)."""
919 rows = classification
if classification
is not None else CLASSIFICATION
921 cid
for cid, row
in rows.items()
922 if cid.startswith(
"inv.")
or row.classification ==
"X"
927 """The machine-readable §9.1 audit ``.json`` payload: the frozen checklist,
928 the generated-inventory counts, and the audit's own validity-key hashes
929 (the Accepted-doc + enforcement-code marker hashes + the generated-inventory
930 hash — NOT the roadmap source_hash, V0.19 Codex V16 F4)."""
932 counts: dict[str, int] = {
"D": 0,
"R": 0,
"X": 0,
"NA": 0}
933 for row
in matrix.rows.values():
934 counts[row.classification] += 1
937 "spec_version":
"REGISTRY_SOURCE_AND_STORAGE_SPEC_V0_22",
938 "generated_by":
"oc_chat/registry/invariant_matrix.py",
940 "v0_3_1_doc": V0_3_1_DOC.name,
943 "constraint_inventory_hash": matrix.constraint_inventory_hash,
945 "inventory_counts": {
946 "ddl_constraints": len(matrix.ddl),
947 "classified": len(matrix.rows),
948 **{f
"class_{k}": v
for k, v
in counts.items()},
949 "app_layer_checklist": len(checklist),
951 "app_layer_checklist": checklist,
957 "independent_of_author":
None,
959 "freeze_point":
"142/P1 implementation-acceptance commit",
list[Marker] extract_markers(str text, *, str form, str source)
dict[str, MatrixRow] _build_classification()
list[Marker] extract_code_markers(tuple[Path,...] files=ENFORCEMENT_CODE_FILES)
str constraint_inventory_hash(list[DdlConstraint] ddl)
set[str] app_layer_invariant_ids(Optional[dict[str, MatrixRow]] classification=None)
list[str] unimplemented_r_rows(Matrix matrix)
str _hash_lines(list[str] lines)
list[DdlConstraint] parse_ddl_inventory(Path sql_path=SCHEMA_V0_3_1_SQL, *, bool include_store_scope=True)
MatrixRow _NA(str handling)
list[Marker] extract_doc_markers(Path doc_path=V0_3_1_DOC)
MatrixRow _R(str rule, str ac, str code, str neg_fixture, str handling)
list[str] verify_matrix(Matrix matrix)
dict[str, object] build_audit_payload(Optional[Matrix] matrix=None)
list[DdlConstraint] _parse_create_tables(str sql)
list[DdlConstraint] _parse_store_scope()
Matrix generate_matrix(Path sql_path=SCHEMA_V0_3_1_SQL, Path doc_path=V0_3_1_DOC, tuple[Path,...] code_files=ENFORCEMENT_CODE_FILES)
list[DdlConstraint] _parse_table_constraint_clause(str table, str clause)
list[DdlConstraint] _parse_indexes_and_triggers(str sql)
MatrixRow _D(str witness_id, str handling, **object kw)
str app_invariant_inventory_hash(list[Marker] markers)
list[DdlConstraint] _parse_column_clause(str table, str clause)
list[str] _split_table_body(str body)
MatrixRow _X(str ratification, str handling)
list[str] code_markers_not_in_matrix(Matrix matrix)