8The durable error-code enum + JSON envelope for ``regenerate`` (§10).
12Define the complete, granular ``SRC_*``/``REGEN_*``/``SCHEMA_*`` code enum
13(Registry Source & Storage Spec V0.22 §10 — two conditions never share a code,
14AC §8.53), the ``RegenerateError``/``SchemaError`` types, and the single JSON
15envelope ``{ok, code, message, records, raw_source_tree_hash, source_hash,
16logical_state_hash, hint, scan_error?}`` every non-zero exit emits, plus the
17exit-code mapping (§5.3).
21- Re-export the P1.3 ``SRC_*`` codes + add the cross-record / rebuild / schema
22 codes; expose ``ALL_CODES`` (the enum-completeness anchor).
23- ``build_envelope`` (success or error) + ``exit_code_for``.
29 L2 - envelope assembly
30 L3 - code/exit mapping
31 L4 - hash-field nullability
35- ``code`` is the durable machine token, NOT the exception class name.
36- The three hash fields follow the §10 stage nullability (via P1.3 stage_hashes).
40- oc_diagnostics (structured debug logging via _dbg)
41- oc_chat.registry.source.errors (the P1.3 SourceError hierarchy + SRC_* codes)
43from __future__
import annotations
45from typing
import Any, Optional, Sequence
47from oc_diagnostics
import _dbg
54 SRC_DUP_OR_STEM_MISMATCH,
61 SourceValidationError,
64_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
67SRC_BAD_TRACK_CODES =
"SRC_BAD_TRACK_CODES"
68SRC_DANGLING_DEPENDS_ON =
"SRC_DANGLING_DEPENDS_ON"
69SRC_BAD_DEPENDENCY_KIND =
"SRC_BAD_DEPENDENCY_KIND"
70SRC_DEPENDENCY_CYCLE =
"SRC_DEPENDENCY_CYCLE"
71SRC_SUCCESSOR_CYCLE =
"SRC_SUCCESSOR_CYCLE"
72SRC_DONE_PARENT_LIVE_CHILD =
"SRC_DONE_PARENT_LIVE_CHILD"
73SRC_DONE_MISSING_CLOSED_VERSION =
"SRC_DONE_MISSING_CLOSED_VERSION"
74SRC_NON_PHASE_HAS_PHASE =
"SRC_NON_PHASE_HAS_PHASE"
75SRC_DUPLICATE_ORDER =
"SRC_DUPLICATE_ORDER"
76SRC_DUPLICATE_CODE =
"SRC_DUPLICATE_CODE"
77SRC_DUPLICATE_RENDER_SECTION =
"SRC_DUPLICATE_RENDER_SECTION"
78SRC_LIFECYCLE_CROSSFIELD =
"SRC_LIFECYCLE_CROSSFIELD"
79SRC_EMPTY_PURPOSE =
"SRC_EMPTY_PURPOSE"
80SRC_VERSION_METADATA_CONFLICT =
"SRC_VERSION_METADATA_CONFLICT"
81SRC_ITEM_ID_COLLISION =
"SRC_ITEM_ID_COLLISION"
84SRC_ROADMAP_CHANGELOG_SEQ =
"SRC_ROADMAP_CHANGELOG_SEQ"
88SRC_PRODUCT_CHANGELOG_SEQ =
"SRC_PRODUCT_CHANGELOG_SEQ"
92SRC_FRAMING_DANGLING_ANCHOR =
"SRC_FRAMING_DANGLING_ANCHOR"
93SRC_FRAMING_DUPLICATE_ANCHOR =
"SRC_FRAMING_DUPLICATE_ANCHOR"
100SRC_TREE_DIRTY =
"SRC_TREE_DIRTY"
105SRC_TREE_NOT_VERSIONED =
"SRC_TREE_NOT_VERSIONED"
106SRC_NO_MIGRATION =
"SRC_NO_MIGRATION"
107SRC_SOURCE_MUTATED =
"SRC_SOURCE_MUTATED"
108SRC_UNSAFE_PATH =
"SRC_UNSAFE_PATH"
109SRC_MIGRATION_INVALID =
"SRC_MIGRATION_INVALID"
110SRC_MIGRATION_OUTPUT_INVALID =
"SRC_MIGRATION_OUTPUT_INVALID"
111SRC_JOURNAL_UNRESOLVED =
"SRC_JOURNAL_UNRESOLVED"
112SRC_JOURNAL_WRITE_FAILED =
"SRC_JOURNAL_WRITE_FAILED"
113SRC_MIGRATION_WRITE_FAILED =
"SRC_MIGRATION_WRITE_FAILED"
114SRC_MIGRATION_TARGET_INVALID =
"SRC_MIGRATION_TARGET_INVALID"
116SRC_LEDGER_ENTRY_INVALID =
"SRC_LEDGER_ENTRY_INVALID"
117SRC_LEDGER_BASELINE_DRIFT =
"SRC_LEDGER_BASELINE_DRIFT"
118SRC_ACTIVE_EXEMPTION_NOT_TERMINAL =
"SRC_ACTIVE_EXEMPTION_NOT_TERMINAL"
119SRC_LEDGER_HISTORY_UNAVAILABLE =
"SRC_LEDGER_HISTORY_UNAVAILABLE"
120SRC_LEDGER_REOPEN_DETECTED_NULL_CLOSE =
"SRC_LEDGER_REOPEN_DETECTED_NULL_CLOSE"
121SRC_LEDGER_LIFECYCLE_LOG_DIVERGENCE =
"SRC_LEDGER_LIFECYCLE_LOG_DIVERGENCE"
124REGEN_BUILD_FAILED =
"REGEN_BUILD_FAILED"
125REGEN_NOT_BYTE_STABLE =
"REGEN_NOT_BYTE_STABLE"
126REGEN_SCOPE_CONFLICT =
"REGEN_SCOPE_CONFLICT"
127REGEN_LOCK_HELD =
"REGEN_LOCK_HELD"
132REGEN_SOURCE_MUTATED =
"REGEN_SOURCE_MUTATED"
133REGEN_UNVERIFIED_PROJECTION =
"REGEN_UNVERIFIED_PROJECTION"
134REGEN_UNSUPPORTED_BACKEND =
"REGEN_UNSUPPORTED_BACKEND"
135SCHEMA_TOO_OLD =
"SCHEMA_TOO_OLD"
136SCHEMA_TOO_NEW =
"SCHEMA_TOO_NEW"
139ALL_CODES: frozenset[str] = frozenset(
141 SRC_SCAN_FAILED, SRC_UNKNOWN_FILE, SRC_BAD_PHASE_PATH, SRC_BAD_YAML,
142 SRC_BAD_YAML_FEATURE, SRC_STRICT_TYPE, SRC_NOT_CANONICAL,
143 SRC_DUP_OR_STEM_MISMATCH, SRC_BAD_TRACK_CODES, SRC_DANGLING_DEPENDS_ON,
144 SRC_BAD_DEPENDENCY_KIND, SRC_DEPENDENCY_CYCLE, SRC_SUCCESSOR_CYCLE,
145 SRC_DONE_PARENT_LIVE_CHILD, SRC_DONE_MISSING_CLOSED_VERSION,
146 SRC_LEDGER_ENTRY_INVALID, SRC_LEDGER_BASELINE_DRIFT,
147 SRC_ACTIVE_EXEMPTION_NOT_TERMINAL, SRC_LEDGER_HISTORY_UNAVAILABLE,
148 SRC_LEDGER_REOPEN_DETECTED_NULL_CLOSE, SRC_LEDGER_LIFECYCLE_LOG_DIVERGENCE,
149 SRC_NON_PHASE_HAS_PHASE, SRC_DUPLICATE_ORDER, SRC_DUPLICATE_CODE,
150 SRC_DUPLICATE_RENDER_SECTION, SRC_LIFECYCLE_CROSSFIELD, SRC_EMPTY_PURPOSE,
151 SRC_VERSION_METADATA_CONFLICT, SRC_BAD_QUERY_PARAMS, SRC_ITEM_ID_COLLISION,
152 SRC_ROADMAP_CHANGELOG_SEQ, SRC_PRODUCT_CHANGELOG_SEQ,
153 SRC_FRAMING_DANGLING_ANCHOR, SRC_FRAMING_DUPLICATE_ANCHOR, SRC_TREE_DIRTY,
154 SRC_TREE_NOT_VERSIONED, SRC_NO_MIGRATION, SRC_SOURCE_MUTATED,
155 SRC_UNSAFE_PATH, SRC_MIGRATION_INVALID, SRC_MIGRATION_OUTPUT_INVALID,
156 SRC_JOURNAL_UNRESOLVED, SRC_JOURNAL_WRITE_FAILED,
157 SRC_MIGRATION_WRITE_FAILED, SRC_MIGRATION_TARGET_INVALID,
158 REGEN_BUILD_FAILED, REGEN_NOT_BYTE_STABLE, REGEN_SCOPE_CONFLICT,
159 REGEN_LOCK_HELD, REGEN_SOURCE_MUTATED, REGEN_UNVERIFIED_PROJECTION,
160 REGEN_UNSUPPORTED_BACKEND, SCHEMA_TOO_OLD, SCHEMA_TOO_NEW,
166 """A rebuild-stage (``REGEN_*``) failure carrying the §10 envelope fields."""
173 records: Optional[Sequence[str]] =
None,
174 hint: Optional[str] =
None,
178 self.
records: list[str] = list(records)
if records
else []
183 """A ``schema_version`` (``SCHEMA_TOO_OLD``/``SCHEMA_TOO_NEW``) failure."""
190 records: Optional[Sequence[str]] =
None,
191 hint: Optional[str] =
None,
195 self.
records: list[str] = list(records)
if records
else []
203_EXIT_BY_CODE: dict[str, int] = {
204 REGEN_NOT_BYTE_STABLE: 3,
205 REGEN_SCOPE_CONFLICT: 2,
208 SRC_TREE_NOT_VERSIONED: 2,
210 SRC_SOURCE_MUTATED: 2,
212 SRC_MIGRATION_INVALID: 2,
213 SRC_MIGRATION_OUTPUT_INVALID: 2,
214 SRC_JOURNAL_UNRESOLVED: 2,
215 SRC_JOURNAL_WRITE_FAILED: 2,
216 SRC_MIGRATION_WRITE_FAILED: 2,
217 SRC_MIGRATION_TARGET_INVALID: 2,
224 """Map an exception to the §5.3 exit code."""
225 if isinstance(error, (SourceScanError, SourceValidationError)):
227 code = getattr(error,
"code",
None)
228 if code
in _EXIT_BY_CODE:
229 return _EXIT_BY_CODE[code]
230 if isinstance(error, (RegenerateError, SchemaError)):
231 return 5
if isinstance(error, SchemaError)
else 2
236 error: Optional[BaseException],
238 raw_source_tree_hash: Optional[str] =
None,
239 source_hash: Optional[str] =
None,
240 logical_state_hash: Optional[str] =
None,
241 message: Optional[str] =
None,
242 rollback: Optional[str] =
None,
243 journal_path: Optional[str] =
None,
244 remaining_paths: Optional[Sequence[str]] =
None,
245 journal_unresolved: Optional[bool] =
None,
247 """Assemble the §10 JSON envelope for a success (``error=None``) or failure.
249 The migrate (Plan 142m) recovery fields ``rollback`` / ``journal_path`` /
250 ``remaining_paths`` / ``journal_unresolved`` are **additive keyword
251 arguments** (Sonnet5-R02-P1 / DeepSeek-R03-NP1-3 Option B): each is emitted
252 into the envelope **only when non-``None``** (``journal_unresolved`` only when
253 ``True``), so a non-migrate caller — which never passes them — sees a
254 byte-identical envelope to before. They describe the recovery *attempt* and
255 are passed explicitly by the migrate runner (NOT ``getattr``-read off the
256 exception, which carries only the error condition).
262 "message": message
or "regenerate ok",
264 "raw_source_tree_hash": raw_source_tree_hash,
265 "source_hash": source_hash,
266 "logical_state_hash": logical_state_hash,
269 envelope: dict[str, Any] = {
271 "code": getattr(error,
"code",
None),
272 "message": str(error),
273 "records": list(getattr(error,
"records", [])
or []),
274 "raw_source_tree_hash": raw_source_tree_hash,
275 "source_hash": source_hash,
276 "logical_state_hash": logical_state_hash,
277 "hint": getattr(error,
"hint",
None),
279 scan_error = getattr(error,
"scan_error",
None)
280 if scan_error
is not None:
281 envelope[
"scan_error"] = scan_error
284 if rollback
is not None:
285 envelope[
"rollback"] = rollback
286 if journal_path
is not None:
287 envelope[
"journal_path"] = journal_path
288 if remaining_paths
is not None:
289 envelope[
"remaining_paths"] = list(remaining_paths)
290 if journal_unresolved:
291 envelope[
"journal_unresolved"] =
True
299 "SourceValidationError",
None __init__(self, str message, *, str code, Optional[Sequence[str]] records=None, Optional[str] hint=None)
None __init__(self, str message, *, str code, Optional[Sequence[str]] records=None, Optional[str] hint=None)
int exit_code_for(BaseException error)
dict[str, Any] build_envelope(Optional[BaseException] error, *, Optional[str] raw_source_tree_hash=None, Optional[str] source_hash=None, Optional[str] logical_state_hash=None, Optional[str] message=None, Optional[str] rollback=None, Optional[str] journal_path=None, Optional[Sequence[str]] remaining_paths=None, Optional[bool] journal_unresolved=None)