core-runtime-engine 11.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. core_runtime/__init__.py +10 -0
  2. core_runtime/__version__.py +17 -0
  3. core_runtime/cli/__init__.py +7 -0
  4. core_runtime/cli/__main__.py +22 -0
  5. core_runtime/cli/bump_version.py +176 -0
  6. core_runtime/cli/contract_preflight.py +69 -0
  7. core_runtime/cli/create_domain.py +66 -0
  8. core_runtime/cli/doctor.py +44 -0
  9. core_runtime/cli/inventory.py +85 -0
  10. core_runtime/cli/lint.py +8 -0
  11. core_runtime/cli/main.py +579 -0
  12. core_runtime/cli/release_check.py +65 -0
  13. core_runtime/cli/repair_artifact_paths.py +48 -0
  14. core_runtime/cli/sync_template.py +67 -0
  15. core_runtime/cli/validate.py +73 -0
  16. core_runtime/core/__init__.py +34 -0
  17. core_runtime/core/audit_event.py +760 -0
  18. core_runtime/core/audit_trail_index.py +100 -0
  19. core_runtime/core/canonicalization.py +55 -0
  20. core_runtime/core/contract_evaluator.py +945 -0
  21. core_runtime/core/contract_executability.py +307 -0
  22. core_runtime/core/contract_loader.py +57 -0
  23. core_runtime/core/contract_probes.py +630 -0
  24. core_runtime/core/contract_program.py +131 -0
  25. core_runtime/core/contract_program_registry.py +104 -0
  26. core_runtime/core/contract_program_v2.py +126 -0
  27. core_runtime/core/dsk_v3.py +142 -0
  28. core_runtime/core/explainability.py +2115 -0
  29. core_runtime/core/numeric_normalization.py +120 -0
  30. core_runtime/core/rule_anchor.py +1388 -0
  31. core_runtime/core/schema_fingerprint.py +69 -0
  32. core_runtime/core/sensor_evidence.py +548 -0
  33. core_runtime/data/contracts/CoreAnchor.sol +109 -0
  34. core_runtime/data/contracts/CoreRuleAnchor.abi.json +111 -0
  35. core_runtime/data/contracts/CoreRuleAnchor.bin +1 -0
  36. core_runtime/data/contracts/CoreRuleAnchor.build.json +20 -0
  37. core_runtime/data/contracts/CoreRuleAnchor.runtime.bin +1 -0
  38. core_runtime/data/contracts/CoreRuleAnchor.sol +74 -0
  39. core_runtime/data/package_data_manifest.v1.json +173 -0
  40. core_runtime/data/schemas/core/causal_trace.v1.json +141 -0
  41. core_runtime/data/schemas/core/context_gate.v1.json +38 -0
  42. core_runtime/data/schemas/core/context_threshold.v1.json +46 -0
  43. core_runtime/data/schemas/core/contract_program.v1.json +186 -0
  44. core_runtime/data/schemas/core/contract_program.v2.json +187 -0
  45. core_runtime/data/schemas/core/control_decision.v1.json +114 -0
  46. core_runtime/data/schemas/core/dsk.v3.json +105 -0
  47. core_runtime/data/schemas/core/effect_result.v1.json +39 -0
  48. core_runtime/data/schemas/core/entropy_signal.v1.json +108 -0
  49. core_runtime/data/schemas/core/execution_receipt.v1.json +93 -0
  50. core_runtime/data/schemas/core/frozen_release_manifest.v1.json +87 -0
  51. core_runtime/data/schemas/core/frozen_release_manifest.v2.json +72 -0
  52. core_runtime/data/schemas/core/frozen_release_manifest.v3.json +38 -0
  53. core_runtime/data/schemas/core/frozen_release_manifest.v4.json +72 -0
  54. core_runtime/data/schemas/core/frozen_release_manifest.v5.json +38 -0
  55. core_runtime/data/schemas/core/frozen_release_manifest.v6.json +116 -0
  56. core_runtime/data/schemas/core/frozen_release_manifest.v7.json +37 -0
  57. core_runtime/data/schemas/core/frozen_release_manifest.v8.json +114 -0
  58. core_runtime/data/schemas/core/frozen_rule_set.v1.json +282 -0
  59. core_runtime/data/schemas/core/memory_artifact.v1.json +120 -0
  60. core_runtime/data/schemas/core/memory_generation_result.v1.json +37 -0
  61. core_runtime/data/schemas/core/operational_learning_event.v1.json +63 -0
  62. core_runtime/data/schemas/core/pattern_candidate.v1.json +114 -0
  63. core_runtime/data/schemas/core/physical_safety_assurance_case.v1.json +676 -0
  64. core_runtime/data/schemas/core/policy_lifecycle.v1.json +99 -0
  65. core_runtime/data/schemas/core/retention_manifest.v1.json +53 -0
  66. core_runtime/data/schemas/core/reversibility_policy.v1.json +107 -0
  67. core_runtime/data/schemas/core/rule_anchor_batch.v1.json +93 -0
  68. core_runtime/data/schemas/core/rule_anchor_chain_evidence.v1.json +56 -0
  69. core_runtime/data/schemas/core/rule_approval.v1.json +49 -0
  70. core_runtime/data/schemas/core/rule_approval_request.v1.json +42 -0
  71. core_runtime/data/schemas/core/state_transition.v1.json +115 -0
  72. core_runtime/data/schemas/core/task_closeout.v1.json +47 -0
  73. core_runtime/data/schemas/core/template_promotion_candidate.v1.json +83 -0
  74. core_runtime/data/schemas/core/unsigned_rule_anchor_deployment.v1.json +106 -0
  75. core_runtime/data/schemas/core/unsigned_rule_anchor_transaction.v1.json +116 -0
  76. core_runtime/tooling/__init__.py +48 -0
  77. core_runtime/tooling/bump_version.py +900 -0
  78. core_runtime/tooling/contract_preflight.py +293 -0
  79. core_runtime/tooling/create_domain.py +254 -0
  80. core_runtime/tooling/diagnostics.py +129 -0
  81. core_runtime/tooling/doctor.py +482 -0
  82. core_runtime/tooling/file_inventory.py +182 -0
  83. core_runtime/tooling/json_checks.py +100 -0
  84. core_runtime/tooling/release_check.py +1017 -0
  85. core_runtime/tooling/repair_artifact_paths.py +458 -0
  86. core_runtime/tooling/report_writer.py +176 -0
  87. core_runtime/tooling/repository_inventory.py +399 -0
  88. core_runtime/tooling/safety_checks.py +172 -0
  89. core_runtime/tooling/sync_template.py +303 -0
  90. core_runtime/tooling/validation.py +507 -0
  91. core_runtime/tooling/version_inventory.py +256 -0
  92. core_runtime_engine-11.5.1.dist-info/METADATA +35 -0
  93. core_runtime_engine-11.5.1.dist-info/RECORD +96 -0
  94. core_runtime_engine-11.5.1.dist-info/WHEEL +5 -0
  95. core_runtime_engine-11.5.1.dist-info/entry_points.txt +2 -0
  96. core_runtime_engine-11.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,945 @@
1
+ """Executable semantics for public CORE contracts.
2
+
3
+ JSON Schema answers whether an artifact has a compatible shape. This module
4
+ answers the separate question that matters operationally: whether the values
5
+ form a coherent, evidence-bound decision under deterministic rules.
6
+
7
+ The evaluator never grants execution, deployment, legal, or moral authority.
8
+ It also never turns finite observations into universal truth. A formal proof
9
+ may demonstrate a proposition only inside its declared model and assumptions.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import copy
15
+ import re
16
+ from collections.abc import Callable, Mapping
17
+ from datetime import datetime
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ from jsonschema import FormatChecker
22
+ from jsonschema.validators import validator_for
23
+
24
+ from core_runtime.core.canonicalization import canonical_json_hash
25
+ from core_runtime.core.contract_loader import available_contracts, load_contract_schema
26
+ from core_runtime.core.contract_program_registry import validate_registry_program
27
+
28
+
29
+ FINGERPRINT_RE = re.compile(r"^sha256:[a-f0-9]{64}$")
30
+ WINDOWS_ABSOLUTE_RE = re.compile(r"^[A-Za-z]:[\\/]")
31
+ ABSOLUTE_CLAIM_RE = re.compile(
32
+ r"\b(?:unhackable|zero[ -]risk|impossible to hack|guaranteed safe|"
33
+ r"all circumstances|cero riesgo|imposible de hackear|seguridad absoluta|"
34
+ r"verdad absoluta)\b",
35
+ re.IGNORECASE,
36
+ )
37
+
38
+ SAFETY_REQUIRED_SCENARIOS = frozenset(
39
+ {
40
+ "llm_prompt_injection",
41
+ "network_compromise",
42
+ "general_compute_compromise",
43
+ "sensor_fault",
44
+ "communication_loss",
45
+ "power_loss",
46
+ "update_tamper",
47
+ "replay_attack",
48
+ "emergency_stop",
49
+ "out_of_distribution_input",
50
+ }
51
+ )
52
+ SAFETY_REQUIRED_LEDGER_EVENTS = frozenset(
53
+ {
54
+ "safety_policy_changed",
55
+ "hazardous_command_rejected",
56
+ "safety_interlock_activated",
57
+ "unexpected_physical_outcome",
58
+ "assurance_invalidated",
59
+ }
60
+ )
61
+ ASSURANCE_RANK = {
62
+ "rejected": 0,
63
+ "simulation_only": 1,
64
+ "evidence_ready": 2,
65
+ "independent_evidence_ready": 3,
66
+ }
67
+
68
+
69
+ Error = dict[str, Any]
70
+ SemanticResult = tuple[list[Error], list[Error], dict[str, Any]]
71
+ SemanticValidator = Callable[[dict[str, Any]], SemanticResult]
72
+
73
+
74
+ def error(code: str, message: str, field: str = "$", **extra: Any) -> Error:
75
+ """Build a stable error or warning entry."""
76
+
77
+ item: Error = {"code": code, "message": message, "field": field}
78
+ item.update(extra)
79
+ return item
80
+
81
+
82
+ def artifact_fingerprint(payload: Mapping[str, Any]) -> str:
83
+ """Fingerprint canonical artifact content, excluding its own fingerprint."""
84
+
85
+ body = {key: value for key, value in payload.items() if key != "fingerprint"}
86
+ return f"sha256:{canonical_json_hash(body)}"
87
+
88
+
89
+ def input_fingerprint(payload: Any) -> str:
90
+ """Fingerprint the exact parsed input used by the evaluator."""
91
+
92
+ return f"sha256:{canonical_json_hash(payload)}"
93
+
94
+
95
+ def _timezone_datetime(value: Any) -> datetime | None:
96
+ if not isinstance(value, str):
97
+ return None
98
+ normalized = value[:-1] + "+00:00" if value.endswith("Z") else value
99
+ try:
100
+ parsed = datetime.fromisoformat(normalized)
101
+ except ValueError:
102
+ return None
103
+ if parsed.tzinfo is None or parsed.utcoffset() is None:
104
+ return None
105
+ return parsed
106
+
107
+
108
+ def _schema_version_map() -> dict[str, str]:
109
+ result: dict[str, str] = {}
110
+ for contract_name in available_contracts():
111
+ schema = load_contract_schema(contract_name)
112
+ version = schema.get("properties", {}).get("schema_version", {}).get("const")
113
+ if isinstance(version, str):
114
+ result[version] = contract_name
115
+ return result
116
+
117
+
118
+ def _schema_errors(payload: Any, schema: dict[str, Any]) -> list[Error]:
119
+ validator_cls = validator_for(schema)
120
+ validator_cls.check_schema(schema)
121
+ validator = validator_cls(schema, format_checker=FormatChecker())
122
+ errors: list[Error] = []
123
+ for item in sorted(validator.iter_errors(payload), key=lambda entry: list(entry.absolute_path)):
124
+ field = ".".join(str(part) for part in item.absolute_path) or "$"
125
+ errors.append(error("schema_validation_error", item.message, field))
126
+ return errors
127
+
128
+
129
+ def validate_contract_structure(payload: Any) -> list[Error]:
130
+ """Validate only the published JSON Schema, without semantic decisions."""
131
+
132
+ if not isinstance(payload, dict):
133
+ return [error("invalid_artifact", "Contract artifact must be an object.")]
134
+ version = payload.get("schema_version")
135
+ contract_name = _schema_version_map().get(version)
136
+ if contract_name is None:
137
+ return [error("unknown_schema_version", f"Unknown schema_version: {version!r}.", "schema_version")]
138
+ return _schema_errors(payload, load_contract_schema(contract_name))
139
+
140
+
141
+ def _resolve_local_ref(schema: dict[str, Any], root: dict[str, Any]) -> dict[str, Any]:
142
+ ref = schema.get("$ref")
143
+ if not isinstance(ref, str) or not ref.startswith("#/"):
144
+ return schema
145
+ current: Any = root
146
+ for part in ref[2:].split("/"):
147
+ token = part.replace("~1", "/").replace("~0", "~")
148
+ if not isinstance(current, dict) or token not in current:
149
+ return schema
150
+ current = current[token]
151
+ return current if isinstance(current, dict) else schema
152
+
153
+
154
+ def _strict_shape_errors(
155
+ value: Any,
156
+ schema: dict[str, Any],
157
+ root: dict[str, Any],
158
+ field: str = "$",
159
+ ) -> list[Error]:
160
+ """Close legacy extension points when the strict executable profile is used."""
161
+
162
+ schema = _resolve_local_ref(schema, root)
163
+ errors: list[Error] = []
164
+ if isinstance(value, dict):
165
+ properties = schema.get("properties")
166
+ if isinstance(properties, dict) and properties:
167
+ unknown = sorted(set(value) - set(properties))
168
+ for key in unknown:
169
+ errors.append(
170
+ error(
171
+ "undeclared_field",
172
+ "Strict contract evaluation rejects undeclared fields.",
173
+ f"{field}.{key}",
174
+ )
175
+ )
176
+ for key, child in value.items():
177
+ child_schema = properties.get(key)
178
+ if isinstance(child_schema, dict):
179
+ errors.extend(_strict_shape_errors(child, child_schema, root, f"{field}.{key}"))
180
+ elif isinstance(value, list):
181
+ item_schema = schema.get("items")
182
+ if isinstance(item_schema, dict):
183
+ for index, child in enumerate(value):
184
+ errors.extend(_strict_shape_errors(child, item_schema, root, f"{field}[{index}]"))
185
+ return errors
186
+
187
+
188
+ def _unsafe_ref(value: str) -> bool:
189
+ if not value or value.startswith("/") or WINDOWS_ABSOLUTE_RE.match(value):
190
+ return True
191
+ normalized = value.replace("\\", "/")
192
+ return ".." in normalized.split("/") or "\x00" in value
193
+
194
+
195
+ def _reference_errors(value: Any, field: str = "$", key: str = "") -> list[Error]:
196
+ errors: list[Error] = []
197
+ if isinstance(value, dict):
198
+ for child_key, child in value.items():
199
+ errors.extend(_reference_errors(child, f"{field}.{child_key}", child_key))
200
+ elif isinstance(value, list):
201
+ if key.endswith("_refs") and all(isinstance(item, str) for item in value):
202
+ if len(value) != len(set(value)):
203
+ errors.append(error("duplicate_reference", "Reference arrays must be unique.", field))
204
+ singular_key = key[:-1] if key.endswith("_refs") else key
205
+ for index, child in enumerate(value):
206
+ errors.extend(_reference_errors(child, f"{field}[{index}]", singular_key))
207
+ elif isinstance(value, str) and (key.endswith("_ref") or key in {"root_ref", "before_ref", "after_ref"}):
208
+ if _unsafe_ref(value):
209
+ errors.append(error("unsafe_reference", "References must be bounded and relative.", field))
210
+ return errors
211
+
212
+
213
+ def _fingerprint_errors(payload: dict[str, Any]) -> list[Error]:
214
+ if "fingerprint" not in payload:
215
+ return []
216
+ declared = payload.get("fingerprint")
217
+ if not isinstance(declared, str) or not FINGERPRINT_RE.fullmatch(declared):
218
+ return [error("invalid_fingerprint", "fingerprint must be sha256:<64 lowercase hex>.", "fingerprint")]
219
+ computed = artifact_fingerprint(payload)
220
+ if declared != computed:
221
+ return [
222
+ error(
223
+ "fingerprint_mismatch",
224
+ "fingerprint does not match canonical artifact content.",
225
+ "fingerprint",
226
+ declared=declared,
227
+ computed=computed,
228
+ )
229
+ ]
230
+ return []
231
+
232
+
233
+ def _timestamp_error(payload: dict[str, Any], key: str) -> list[Error]:
234
+ if key in payload and _timezone_datetime(payload.get(key)) is None:
235
+ return [error("invalid_timestamp", "Timestamp must include an explicit timezone.", key)]
236
+ return []
237
+
238
+
239
+ def _empty_result() -> SemanticResult:
240
+ return [], [], {}
241
+
242
+
243
+ def _validate_causal_trace(payload: dict[str, Any]) -> SemanticResult:
244
+ errors: list[Error] = []
245
+ nodes = payload.get("nodes", [])
246
+ refs: list[str] = []
247
+ ids: list[str] = []
248
+ if isinstance(nodes, list):
249
+ refs = [item.get("ref") for item in nodes if isinstance(item, dict) and isinstance(item.get("ref"), str)]
250
+ ids = [item.get("node_id") for item in nodes if isinstance(item, dict) and isinstance(item.get("node_id"), str)]
251
+ if len(ids) != len(set(ids)):
252
+ errors.append(error("duplicate_node_id", "node_id values must be unique.", "nodes"))
253
+ if len(refs) != len(set(refs)):
254
+ errors.append(error("duplicate_node_ref", "Node ref values must be unique.", "nodes"))
255
+ if payload.get("root_ref") not in set(refs):
256
+ errors.append(error("missing_root_node", "root_ref must resolve to a node ref.", "root_ref"))
257
+
258
+ graph: dict[str, list[str]] = {ref: [] for ref in refs}
259
+ for index, edge in enumerate(payload.get("edges", [])):
260
+ if not isinstance(edge, dict):
261
+ continue
262
+ source = edge.get("from_ref")
263
+ target = edge.get("to_ref")
264
+ if source not in graph or target not in graph:
265
+ errors.append(error("dangling_edge_ref", "Every edge endpoint must resolve to a node ref.", f"edges[{index}]"))
266
+ continue
267
+ if source == target:
268
+ errors.append(error("causal_self_loop", "A causal edge cannot reference itself.", f"edges[{index}]"))
269
+ graph[source].append(target)
270
+
271
+ visiting: set[str] = set()
272
+ visited: set[str] = set()
273
+
274
+ def visit(node: str) -> bool:
275
+ if node in visiting:
276
+ return True
277
+ if node in visited:
278
+ return False
279
+ visiting.add(node)
280
+ if any(visit(child) for child in graph.get(node, [])):
281
+ return True
282
+ visiting.remove(node)
283
+ visited.add(node)
284
+ return False
285
+
286
+ if any(visit(node) for node in sorted(graph)):
287
+ errors.append(error("causal_cycle", "Causal traces must be acyclic.", "edges"))
288
+ errors.extend(_timestamp_error(payload, "created_at"))
289
+ return errors, [], {"node_count": len(refs), "edge_count": len(payload.get("edges", []))}
290
+
291
+
292
+ def _validate_entropy_signal(payload: dict[str, Any]) -> SemanticResult:
293
+ errors: list[Error] = []
294
+ measurement = payload.get("measurement")
295
+ if not isinstance(measurement, dict) or not measurement:
296
+ errors.append(error("measurement_required", "Entropy signals require a non-empty measurement.", "measurement"))
297
+ response = str(payload.get("suggested_response", "")).strip().lower()
298
+ if payload.get("severity") == "critical" and response in {"ignore", "allow", "continue"}:
299
+ errors.append(error("critical_signal_cannot_be_ignored", "A critical signal cannot recommend continuation.", "suggested_response"))
300
+ errors.extend(_timestamp_error(payload, "timestamp"))
301
+ return errors, [], {}
302
+
303
+
304
+ def _validate_control_decision(payload: dict[str, Any]) -> SemanticResult:
305
+ errors: list[Error] = []
306
+ decision = payload.get("decision")
307
+ reversibility = payload.get("reversibility_class")
308
+ if decision == "allow" and reversibility in {"irreversible", "unknown"}:
309
+ errors.append(error("unsafe_allow_decision", "Unknown or irreversible actions cannot be directly allowed.", "decision"))
310
+ if decision in {"allow", "require_confirmation"} and not payload.get("evidence_refs"):
311
+ errors.append(error("required_evidence_missing", "A non-blocked decision must bind observed evidence.", "evidence_refs"))
312
+ errors.extend(_timestamp_error(payload, "created_at"))
313
+ return errors, [], {"execution_authorized": False}
314
+
315
+
316
+ def _validate_execution_receipt(payload: dict[str, Any]) -> SemanticResult:
317
+ errors: list[Error] = []
318
+ status = payload.get("status")
319
+ transitions = payload.get("state_transition_refs", [])
320
+ if status == "succeeded" and not transitions:
321
+ errors.append(error("successful_receipt_requires_transition", "A successful effect requires at least one state transition.", "state_transition_refs"))
322
+ if status in {"skipped", "simulated"} and transitions:
323
+ errors.append(error("simulated_transition_forbidden", "Skipped or simulated execution cannot claim state changes.", "state_transition_refs"))
324
+ errors.extend(_timestamp_error(payload, "created_at"))
325
+ return errors, [], {"execution_authorized": False}
326
+
327
+
328
+ def _validate_memory_artifact(payload: dict[str, Any]) -> SemanticResult:
329
+ errors: list[Error] = []
330
+ retention = payload.get("retention", {})
331
+ protected = any(payload.get(key) for key in ("stable_facts", "decisions", "invariants", "open_risks"))
332
+ if isinstance(retention, dict) and retention.get("retention_class") == "forget" and protected:
333
+ errors.append(error("unsafe_memory_forgetting", "Memory containing facts, decisions, invariants, or risks cannot be marked forget.", "retention.retention_class"))
334
+ errors.extend(_timestamp_error(payload, "created_at"))
335
+ return errors, [], {"authority": "reference_only"}
336
+
337
+
338
+ def _validate_task_closeout(payload: dict[str, Any]) -> SemanticResult:
339
+ errors: list[Error] = []
340
+ if payload.get("status") not in {"passed", "failed", "blocked", "partial"}:
341
+ errors.append(error("invalid_closeout_status", "Closeout status must be passed, failed, blocked, or partial.", "status"))
342
+ evidence_fields = ("report_ref", "events_ref", "effect_results", "memory_generation_result")
343
+ if payload.get("status") == "passed" and not any(payload.get(key) for key in evidence_fields):
344
+ errors.append(error("closeout_evidence_required", "A passed closeout requires an evidence-bearing result or report reference.", "status"))
345
+ return errors, [], {}
346
+
347
+
348
+ def _validate_effect_result(payload: dict[str, Any]) -> SemanticResult:
349
+ errors: list[Error] = []
350
+ status = payload.get("status")
351
+ dry_run = payload.get("dry_run")
352
+ if (status == "dry_run") != (dry_run is True):
353
+ errors.append(error("effect_status_dry_run_mismatch", "status=dry_run and dry_run=true must agree.", "dry_run"))
354
+ if status in {"sent", "applied"}:
355
+ if dry_run is not False:
356
+ errors.append(error("applied_effect_cannot_be_dry_run", "Applied effects must declare dry_run=false.", "dry_run"))
357
+ if not payload.get("provider") or not payload.get("target_ref"):
358
+ errors.append(error("effect_destination_required", "Applied effects require provider and target_ref.", "target_ref"))
359
+ if status == "failed" and not payload.get("error"):
360
+ errors.append(error("failed_effect_requires_error", "Failed effects require an error description.", "error"))
361
+ return errors, [], {"execution_authorized": False}
362
+
363
+
364
+ def _validate_memory_generation_result(payload: dict[str, Any]) -> SemanticResult:
365
+ errors: list[Error] = []
366
+ status = payload.get("status")
367
+ if status not in {"passed", "failed", "skipped"}:
368
+ errors.append(error("invalid_memory_result_status", "Memory result status must be passed, failed, or skipped.", "status"))
369
+ if status == "passed" and (not payload.get("memory_id") or not payload.get("memory_ref")):
370
+ errors.append(error("memory_reference_required", "A passed memory result requires memory_id and memory_ref.", "memory_ref"))
371
+ if payload.get("reused") is True and (not payload.get("memory_id") or not payload.get("memory_ref")):
372
+ errors.append(error("reused_memory_reference_required", "Reused memory must resolve to an immutable memory reference.", "memory_ref"))
373
+ if status == "failed" and payload.get("reused") is True:
374
+ errors.append(error("failed_memory_cannot_be_reused", "A failed result cannot claim reuse.", "reused"))
375
+ return errors, [], {}
376
+
377
+
378
+ def _validate_operational_learning_event(payload: dict[str, Any]) -> SemanticResult:
379
+ errors: list[Error] = []
380
+ if payload.get("status") not in {"recorded", "candidate", "accepted", "rejected", "quarantined"}:
381
+ errors.append(error("invalid_learning_status", "Learning status is not recognized.", "status"))
382
+ event_payload = payload.get("payload")
383
+ if not isinstance(event_payload, dict) or not event_payload:
384
+ errors.append(error("learning_payload_required", "Operational learning requires a non-empty payload.", "payload"))
385
+
386
+ def scan(value: Any, field: str) -> None:
387
+ if isinstance(value, dict):
388
+ for key, child in value.items():
389
+ child_field = f"{field}.{key}"
390
+ if key in {"auto_execute", "self_modify"} and child is True:
391
+ errors.append(error("learning_event_authority_escalation", "Learning events cannot grant execution or self-modification.", child_field))
392
+ if key in {"authority", "activation_default"} and str(child).lower() in {"binding", "execution_authority", "enabled", "automatic"}:
393
+ errors.append(error("learning_event_authority_escalation", "Learning events remain candidate-only.", child_field))
394
+ scan(child, child_field)
395
+ elif isinstance(value, list):
396
+ for index, child in enumerate(value):
397
+ scan(child, f"{field}[{index}]")
398
+
399
+ scan(event_payload, "payload")
400
+ errors.extend(_timestamp_error(payload, "timestamp"))
401
+ return errors, [], {"authority": "candidate_only"}
402
+
403
+
404
+ def _validate_policy_lifecycle(payload: dict[str, Any]) -> SemanticResult:
405
+ errors: list[Error] = []
406
+ start = _timezone_datetime(payload.get("effective_from"))
407
+ end = _timezone_datetime(payload.get("effective_to")) if payload.get("effective_to") is not None else None
408
+ if start is None:
409
+ errors.append(error("invalid_effective_from", "effective_from must include a timezone.", "effective_from"))
410
+ if payload.get("effective_to") is not None and end is None:
411
+ errors.append(error("invalid_effective_to", "effective_to must include a timezone.", "effective_to"))
412
+ if start is not None and end is not None and end <= start:
413
+ errors.append(error("invalid_effective_interval", "effective_to must be later than effective_from.", "effective_to"))
414
+ if payload.get("status") in {"superseded", "retired"} and end is None:
415
+ errors.append(error("closed_policy_requires_end", "Superseded or retired policies require effective_to.", "effective_to"))
416
+ if payload.get("supersedes") == payload.get("policy_id"):
417
+ errors.append(error("policy_cannot_supersede_itself", "A policy cannot supersede itself.", "supersedes"))
418
+ return errors, [], {}
419
+
420
+
421
+ def _validate_context_threshold(payload: dict[str, Any]) -> SemanticResult:
422
+ errors: list[Error] = []
423
+ for key in ("current_usage_percent", "previous_usage_percent", "ema_usage_percent", "compression_threshold_percent"):
424
+ value = payload.get(key)
425
+ if value is not None and (not isinstance(value, (int, float)) or isinstance(value, bool) or value < 0 or value > 100):
426
+ errors.append(error("invalid_percentage", "Percentages must be between 0 and 100.", key))
427
+ current = payload.get("current_usage_percent")
428
+ threshold = payload.get("compression_threshold_percent")
429
+ if isinstance(current, (int, float)) and isinstance(threshold, (int, float)):
430
+ expected = current >= threshold
431
+ if payload.get("should_compress_now") is not expected:
432
+ errors.append(error("threshold_decision_mismatch", "should_compress_now must be derived from current usage and threshold.", "should_compress_now"))
433
+ if payload.get("status") not in {"passed", "failed", "skipped"}:
434
+ errors.append(error("invalid_threshold_status", "Threshold status must be passed, failed, or skipped.", "status"))
435
+ return errors, [], {}
436
+
437
+
438
+ def _validate_context_gate(payload: dict[str, Any]) -> SemanticResult:
439
+ errors: list[Error] = []
440
+ status = payload.get("status")
441
+ mode = payload.get("mode")
442
+ if status not in {"passed", "applied", "skipped", "blocked", "failed"}:
443
+ errors.append(error("invalid_context_gate_status", "Context gate status is not recognized.", "status"))
444
+ if mode == "dry-run" and status == "applied":
445
+ errors.append(error("dry_run_cannot_apply", "A dry-run gate cannot report an applied mutation.", "status"))
446
+ if mode == "apply" and status == "applied" and not payload.get("memory_generation_result"):
447
+ errors.append(error("apply_result_required", "An applied gate requires the resulting memory artifact reference.", "memory_generation_result"))
448
+ return errors, [], {"execution_authorized": False}
449
+
450
+
451
+ def _validate_retention_manifest(payload: dict[str, Any]) -> SemanticResult:
452
+ errors: list[Error] = []
453
+ entries = payload.get("entries", [])
454
+ if not entries:
455
+ errors.append(error("retention_entries_required", "A retention manifest cannot be empty.", "entries"))
456
+ refs = [item.get("artifact_ref") for item in entries if isinstance(item, dict)]
457
+ if len(refs) != len(set(refs)):
458
+ errors.append(error("duplicate_artifact_ref", "Each artifact may have only one retention decision.", "entries"))
459
+ for index, item in enumerate(entries):
460
+ if not isinstance(item, dict):
461
+ continue
462
+ retention_class = item.get("retention_class")
463
+ if retention_class in {"compress", "forget", "quarantine"} and not item.get("checksum"):
464
+ errors.append(error("retention_checksum_required", "Mutating retention actions require the source checksum.", f"entries[{index}].checksum"))
465
+ if retention_class in {"forget", "quarantine"} and not item.get("restore_ref"):
466
+ errors.append(error("retention_restore_required", "Destructive or isolating retention actions require a restore reference.", f"entries[{index}].restore_ref"))
467
+ return errors, [], {}
468
+
469
+
470
+ def _validate_reversibility_policy(payload: dict[str, Any]) -> SemanticResult:
471
+ errors: list[Error] = []
472
+ reversibility = payload.get("reversibility_class")
473
+ if reversibility in {"irreversible", "unknown"} and payload.get("human_approval_required") is not True:
474
+ errors.append(error("responsible_approval_required", "Unknown or irreversible actions require responsible-person approval.", "human_approval_required"))
475
+ if reversibility == "compensable" and payload.get("compensation_required") is not True:
476
+ errors.append(error("compensation_plan_required", "Compensable actions require compensation.", "compensation_required"))
477
+ errors.extend(_timestamp_error(payload, "created_at"))
478
+ return errors, [], {"execution_authorized": False}
479
+
480
+
481
+ def _validate_state_transition(payload: dict[str, Any]) -> SemanticResult:
482
+ errors: list[Error] = []
483
+ if payload.get("before_ref") == payload.get("after_ref"):
484
+ errors.append(error("state_transition_noop", "before_ref and after_ref must differ.", "after_ref"))
485
+ actor = str(payload.get("actor_kind", "")).lower()
486
+ responsible_actors = {"human", "human_operator", "responsible_operator", "authorized_signer", "human_directed_software"}
487
+ if payload.get("reversibility_class") in {"irreversible", "unknown"} and actor not in responsible_actors:
488
+ errors.append(error("irreversible_actor_not_responsible", "Irreversible transitions require an explicitly accountable actor kind.", "actor_kind"))
489
+ errors.extend(_timestamp_error(payload, "timestamp"))
490
+ return errors, [], {"execution_authorized": False}
491
+
492
+
493
+ def _validate_contract_program(payload: dict[str, Any]) -> SemanticResult:
494
+ """Validate the closed, effect-free instruction language before replay."""
495
+
496
+ errors: list[Error] = []
497
+ policy = payload.get("effect_policy", {})
498
+ if not isinstance(policy, dict) or any(
499
+ policy.get(key) is not False
500
+ for key in ("external_effects", "network_access", "filesystem_access", "state_apply")
501
+ ):
502
+ errors.append(error("effect_policy_forbidden", "Contract programs are validation-only and cannot grant effects, network, filesystem, or state application.", "effect_policy"))
503
+
504
+ instructions = payload.get("instructions", [])
505
+ limits = payload.get("limits", {})
506
+ capabilities = set(payload.get("capabilities", []))
507
+ if isinstance(instructions, list) and isinstance(limits, dict):
508
+ if len(instructions) > limits.get("max_steps", 0):
509
+ errors.append(error("program_step_limit_exceeded", "Instruction count exceeds max_steps.", "limits.max_steps"))
510
+ emits = sum(item.get("opcode") == "emit" for item in instructions if isinstance(item, dict))
511
+ if emits > limits.get("max_emits", 0):
512
+ errors.append(error("program_emit_limit_exceeded", "Emit count exceeds max_emits.", "limits.max_emits"))
513
+ halt_positions = [index for index, item in enumerate(instructions) if isinstance(item, dict) and item.get("opcode") == "halt"]
514
+ if halt_positions != [len(instructions) - 1]:
515
+ errors.append(error("program_halt_must_be_terminal", "A contract program must have exactly one terminal halt instruction.", "instructions"))
516
+ required_capabilities = {
517
+ "load": "read_input",
518
+ "assert": "assert",
519
+ "derive": "derive",
520
+ "transition": "stage_transition",
521
+ "emit": "emit_result",
522
+ }
523
+ produced: set[str] = set()
524
+ for index, instruction in enumerate(instructions):
525
+ if not isinstance(instruction, dict):
526
+ continue
527
+ opcode = instruction.get("opcode")
528
+ capability = required_capabilities.get(opcode)
529
+ if capability and capability not in capabilities:
530
+ errors.append(error("program_capability_missing", "Instruction requires a declared capability.", f"instructions[{index}].opcode", capability=capability))
531
+ output = instruction.get("output")
532
+ if isinstance(output, str):
533
+ if output in produced:
534
+ errors.append(error("program_output_redefined", "Instruction outputs must be unique.", f"instructions[{index}].output"))
535
+ produced.add(output)
536
+ if opcode == "transition" and instruction.get("before_ref") == instruction.get("after_ref"):
537
+ errors.append(error("program_transition_noop", "Staged before_ref and after_ref must differ.", f"instructions[{index}].after_ref"))
538
+ if opcode == "derive" and instruction.get("operation") == "copy" and len(instruction.get("input_keys", [])) != 1:
539
+ errors.append(error("program_copy_arity", "copy derivation requires exactly one input key.", f"instructions[{index}].input_keys"))
540
+ if opcode == "derive" and instruction.get("operation") == "registry":
541
+ registry_errors = validate_registry_program({"instructions": [instruction]})
542
+ errors.extend(
543
+ error("registry_instruction_invalid", message, f"instructions[{index}]")
544
+ for message in registry_errors
545
+ )
546
+
547
+ return errors, [], {
548
+ "execution_authorized": False,
549
+ "state_application_authorized": False,
550
+ "instruction_count": len(instructions) if isinstance(instructions, list) else 0,
551
+ }
552
+
553
+
554
+ def _validate_template_promotion(payload: dict[str, Any]) -> SemanticResult:
555
+ errors: list[Error] = []
556
+ for key in ("required_inputs", "expected_evidence", "stop_conditions"):
557
+ if not payload.get(key):
558
+ errors.append(error("promotion_contract_incomplete", f"{key} cannot be empty.", key))
559
+ if payload.get("risk_tier") in {"medium", "high"} and payload.get("human_approval_required") is not True:
560
+ errors.append(error("promotion_approval_required", "Medium and high-risk templates require responsible approval.", "human_approval_required"))
561
+ if payload.get("risk_tier") == "high" and not payload.get("source_refs"):
562
+ errors.append(error("promotion_source_evidence_required", "High-risk promotion requires source_refs.", "source_refs"))
563
+ return errors, [], {"activation_authorized": False}
564
+
565
+
566
+ def _validate_pattern_candidate(payload: dict[str, Any]) -> SemanticResult:
567
+ errors: list[Error] = []
568
+ signature = payload.get("normalized_signature")
569
+ if not isinstance(signature, dict) or not signature:
570
+ errors.append(error("normalized_signature_required", "Pattern candidates require a non-empty normalized signature.", "normalized_signature"))
571
+ classification = payload.get("classification")
572
+ if classification == "candidate_for_template":
573
+ if payload.get("occurrences", 0) < 2 or payload.get("confidence", 0) < 0.8:
574
+ errors.append(error("insufficient_pattern_support", "Template candidates require at least two occurrences and confidence >= 0.8.", "confidence"))
575
+ if not payload.get("evidence_refs") or not payload.get("example_refs"):
576
+ errors.append(error("pattern_evidence_required", "Template candidates require evidence_refs and example_refs.", "evidence_refs"))
577
+ action = str(payload.get("suggested_action", "")).lower()
578
+ if classification == "too_ambiguous" and any(token in action for token in ("promote", "activate", "auto", "template")):
579
+ errors.append(error("ambiguous_pattern_cannot_promote", "Ambiguous patterns may request clarification only.", "suggested_action"))
580
+ errors.extend(_timestamp_error(payload, "observed_at"))
581
+ return errors, [], {"authority": "candidate_only"}
582
+
583
+
584
+ def _duplicate_ids(items: Any, key: str, field: str) -> tuple[set[str], list[Error]]:
585
+ values = [item.get(key) for item in items if isinstance(item, dict) and isinstance(item.get(key), str)] if isinstance(items, list) else []
586
+ errors = []
587
+ if len(values) != len(set(values)):
588
+ errors.append(error("duplicate_identifier", f"{key} values must be unique.", field))
589
+ return set(values), errors
590
+
591
+
592
+ def _validate_physical_safety_case(payload: dict[str, Any]) -> SemanticResult:
593
+ errors: list[Error] = []
594
+ warnings: list[Error] = []
595
+ claim = payload.get("claim", {})
596
+ envelope = payload.get("observed_envelope", {})
597
+ hazards = payload.get("hazards", [])
598
+ barriers = payload.get("barriers", [])
599
+ tests = payload.get("verification_tests", [])
600
+ evidence = payload.get("evidence", [])
601
+
602
+ evidence_ids, id_errors = _duplicate_ids(evidence, "evidence_id", "evidence")
603
+ errors.extend(id_errors)
604
+ barrier_ids, id_errors = _duplicate_ids(barriers, "barrier_id", "barriers")
605
+ errors.extend(id_errors)
606
+ hazard_ids, id_errors = _duplicate_ids(hazards, "hazard_id", "hazards")
607
+ errors.extend(id_errors)
608
+ test_ids, id_errors = _duplicate_ids(tests, "test_id", "verification_tests")
609
+ errors.extend(id_errors)
610
+
611
+ evidence_by_id = {item.get("evidence_id"): item for item in evidence if isinstance(item, dict)}
612
+ barrier_by_id = {item.get("barrier_id"): item for item in barriers if isinstance(item, dict)}
613
+ test_by_id = {item.get("test_id"): item for item in tests if isinstance(item, dict)}
614
+
615
+ scope = str(claim.get("scope", "")) if isinstance(claim, dict) else ""
616
+ if ABSOLUTE_CLAIM_RE.search(scope):
617
+ errors.append(error("absolute_safety_claim_forbidden", "Safety scope cannot claim universal truth, zero risk, or unhackability.", "claim.scope"))
618
+ if isinstance(claim, dict) and claim.get("claim_status") == "demonstrated_within_model":
619
+ if not claim.get("proof_refs") or "formal_model" not in claim.get("reference_classes", []):
620
+ errors.append(error("formal_demonstration_evidence_required", "Demonstrated-within-model claims require formal_model and proof_refs.", "claim.proof_refs"))
621
+
622
+ extremes = envelope.get("known_extremes", []) if isinstance(envelope, dict) else []
623
+ extreme_ids, id_errors = _duplicate_ids(extremes, "extreme_id", "observed_envelope.known_extremes")
624
+ errors.extend(id_errors)
625
+ directions = {item.get("direction") for item in extremes if isinstance(item, dict)}
626
+ if len(directions) < 2:
627
+ errors.append(error("extreme_diversity_required", "The observed envelope must preserve at least two distinct extreme directions.", "observed_envelope.known_extremes"))
628
+ if isinstance(envelope, dict) and isinstance(envelope.get("sample_count"), int) and envelope.get("sample_count", 0) < len(extreme_ids):
629
+ errors.append(error("sample_count_below_extremes", "sample_count cannot be smaller than the number of extreme observations.", "observed_envelope.sample_count"))
630
+
631
+ evaluated_at = _timezone_datetime(payload.get("evaluated_at"))
632
+ if evaluated_at is None:
633
+ errors.append(error("invalid_evaluated_at", "evaluated_at must include a timezone.", "evaluated_at"))
634
+ evidence_fingerprints: list[str] = []
635
+ for index, item in enumerate(evidence):
636
+ if not isinstance(item, dict):
637
+ continue
638
+ fp = item.get("fingerprint")
639
+ if isinstance(fp, str):
640
+ evidence_fingerprints.append(fp)
641
+ if fp == "sha256:" + ("0" * 64):
642
+ errors.append(error("zero_evidence_fingerprint", "Evidence fingerprints cannot be all zero.", f"evidence[{index}].fingerprint"))
643
+ captured = _timezone_datetime(item.get("captured_at"))
644
+ if captured is None:
645
+ errors.append(error("invalid_evidence_timestamp", "Evidence timestamps require a timezone.", f"evidence[{index}].captured_at"))
646
+ elif evaluated_at is not None and captured > evaluated_at:
647
+ errors.append(error("future_evidence_forbidden", "Evidence cannot be captured after evaluated_at.", f"evidence[{index}].captured_at"))
648
+ if len(evidence_fingerprints) != len(set(evidence_fingerprints)):
649
+ errors.append(error("duplicate_evidence_fingerprint", "Independent evidence items cannot reuse the same fingerprint.", "evidence"))
650
+
651
+ def require_evidence_ref(ref: Any, field: str) -> None:
652
+ if ref not in evidence_ids:
653
+ errors.append(error("unresolved_evidence_ref", "Evidence reference does not resolve inside this frozen case.", field, ref=ref))
654
+
655
+ for index, item in enumerate(extremes):
656
+ if isinstance(item, dict):
657
+ require_evidence_ref(item.get("evidence_ref"), f"observed_envelope.known_extremes[{index}].evidence_ref")
658
+ if _timezone_datetime(item.get("observed_at")) is None:
659
+ errors.append(error("invalid_extreme_timestamp", "Extreme observations require a timezone.", f"observed_envelope.known_extremes[{index}].observed_at"))
660
+
661
+ if isinstance(claim, dict):
662
+ for index, ref in enumerate(claim.get("proof_refs", [])):
663
+ require_evidence_ref(ref, f"claim.proof_refs[{index}]")
664
+
665
+ for index, barrier in enumerate(barriers):
666
+ if not isinstance(barrier, dict):
667
+ continue
668
+ barrier_id = barrier.get("barrier_id")
669
+ for relation_index, ref in enumerate(barrier.get("independent_from", [])):
670
+ if ref == barrier_id:
671
+ errors.append(error("barrier_self_independence", "A barrier cannot be independent from itself.", f"barriers[{index}].independent_from[{relation_index}]"))
672
+ elif ref not in barrier_ids:
673
+ errors.append(error("unresolved_barrier_ref", "independent_from must resolve to another barrier.", f"barriers[{index}].independent_from[{relation_index}]"))
674
+ elif barrier_id not in barrier_by_id.get(ref, {}).get("independent_from", []):
675
+ errors.append(error("asymmetric_barrier_independence", "Barrier independence must be declared by both barriers.", f"barriers[{index}].independent_from[{relation_index}]"))
676
+ for evidence_index, ref in enumerate(barrier.get("evidence_refs", [])):
677
+ require_evidence_ref(ref, f"barriers[{index}].evidence_refs[{evidence_index}]")
678
+
679
+ methods_by_hazard_scenario: dict[str, dict[str, set[str]]] = {
680
+ hazard_id: {scenario: set() for scenario in SAFETY_REQUIRED_SCENARIOS}
681
+ for hazard_id in hazard_ids
682
+ }
683
+ independent_evidence_by_test: dict[str, bool] = {}
684
+ for index, test in enumerate(tests):
685
+ if not isinstance(test, dict):
686
+ continue
687
+ test_id = test.get("test_id")
688
+ evidence_refs = test.get("evidence_refs", [])
689
+ for evidence_index, ref in enumerate(evidence_refs):
690
+ require_evidence_ref(ref, f"verification_tests[{index}].evidence_refs[{evidence_index}]")
691
+ independent_evidence_by_test[str(test_id)] = any(
692
+ isinstance(evidence_by_id.get(ref), dict)
693
+ and evidence_by_id[ref].get("source_kind") == "independent_assessor"
694
+ and evidence_by_id[ref].get("reference_class") == "independent_assessment"
695
+ for ref in evidence_refs
696
+ )
697
+ if test.get("result") != "passed":
698
+ errors.append(error("safety_test_not_passed", "Failed or inconclusive safety tests reject the assurance case.", f"verification_tests[{index}].result"))
699
+ if test.get("hazardous_actuation_observed") is not False:
700
+ errors.append(error("hazardous_actuation_observed", "Any observed hazardous actuation rejects the assurance case.", f"verification_tests[{index}].hazardous_actuation_observed"))
701
+ if test.get("expected_safe_state") != test.get("observed_safe_state"):
702
+ errors.append(error("safe_state_mismatch", "Observed and expected safe states must match exactly.", f"verification_tests[{index}].observed_safe_state"))
703
+ for hazard_ref in test.get("hazard_refs", []):
704
+ if hazard_ref not in hazard_ids:
705
+ errors.append(error("unresolved_hazard_ref", "Test hazard_refs must resolve inside the case.", f"verification_tests[{index}].hazard_refs"))
706
+ continue
707
+ scenario = test.get("scenario")
708
+ method = test.get("method")
709
+ if scenario in SAFETY_REQUIRED_SCENARIOS and isinstance(method, str):
710
+ methods_by_hazard_scenario[hazard_ref][scenario].add(method)
711
+
712
+ for index, hazard in enumerate(hazards):
713
+ if not isinstance(hazard, dict):
714
+ continue
715
+ hazard_id = hazard.get("hazard_id")
716
+ resolved_barriers: list[dict[str, Any]] = []
717
+ for barrier_index, ref in enumerate(hazard.get("barrier_refs", [])):
718
+ barrier = barrier_by_id.get(ref)
719
+ if not isinstance(barrier, dict):
720
+ errors.append(error("unresolved_barrier_ref", "Hazard barrier_refs must resolve inside the case.", f"hazards[{index}].barrier_refs[{barrier_index}]"))
721
+ else:
722
+ resolved_barriers.append(barrier)
723
+ for test_index, ref in enumerate(hazard.get("test_refs", [])):
724
+ test = test_by_id.get(ref)
725
+ if not isinstance(test, dict):
726
+ errors.append(error("unresolved_test_ref", "Hazard test_refs must resolve inside the case.", f"hazards[{index}].test_refs[{test_index}]"))
727
+ elif hazard_id not in test.get("hazard_refs", []):
728
+ errors.append(error("test_hazard_link_mismatch", "Hazard and test links must be bidirectional.", f"hazards[{index}].test_refs[{test_index}]"))
729
+
730
+ if hazard.get("severity") == "catastrophic":
731
+ non_bypassable = [barrier for barrier in resolved_barriers if barrier.get("bypassable_by_general_compute") is False]
732
+ domains = {barrier.get("enforcement_domain") for barrier in non_bypassable}
733
+ kinds = {barrier.get("kind") for barrier in non_bypassable}
734
+ if len(non_bypassable) < 2 or len(domains) < 2:
735
+ errors.append(error("catastrophic_hazard_needs_independent_barriers", "Catastrophic hazards require at least two non-bypassable barriers in distinct enforcement domains.", f"hazards[{index}].barrier_refs"))
736
+ if "isolated_safety_controller" not in kinds:
737
+ errors.append(error("isolated_safety_controller_required", "Catastrophic hazards require an isolated safety controller.", f"hazards[{index}].barrier_refs"))
738
+ if not kinds.intersection({"physical_energy_isolation", "mechanical_limit"}):
739
+ errors.append(error("physical_isolation_required", "Catastrophic hazards require hardware energy isolation or a mechanical limit.", f"hazards[{index}].barrier_refs"))
740
+
741
+ coverage = methods_by_hazard_scenario.get(str(hazard_id), {})
742
+ missing = sorted(scenario for scenario in SAFETY_REQUIRED_SCENARIOS if not coverage.get(scenario))
743
+ if missing:
744
+ errors.append(error("required_scenario_missing", "Every hazard must cover the mandatory adversarial and fault scenarios.", f"hazards[{index}].test_refs", missing=missing))
745
+
746
+ epistemic = payload.get("epistemic_dignity", {})
747
+ if isinstance(epistemic, dict):
748
+ for key in (
749
+ "plain_language_disclosure_ref",
750
+ "evidence_limitations_ref",
751
+ "contestability_ref",
752
+ "local_stop_ref",
753
+ ):
754
+ require_evidence_ref(epistemic.get(key), f"epistemic_dignity.{key}")
755
+ lifecycle = payload.get("lifecycle", {})
756
+ if isinstance(lifecycle, dict):
757
+ for key in (
758
+ "secure_boot_evidence_ref",
759
+ "signed_update_evidence_ref",
760
+ "unique_credentials_evidence_ref",
761
+ "sbom_evidence_ref",
762
+ "vulnerability_process_evidence_ref",
763
+ ):
764
+ require_evidence_ref(lifecycle.get(key), f"lifecycle.{key}")
765
+ traceability = payload.get("traceability", {})
766
+ if isinstance(traceability, dict):
767
+ require_evidence_ref(traceability.get("immutable_event_ledger_ref"), "traceability.immutable_event_ledger_ref")
768
+ recorded = set(traceability.get("recorded_event_types", []))
769
+ missing_events = sorted(SAFETY_REQUIRED_LEDGER_EVENTS - recorded)
770
+ if missing_events:
771
+ errors.append(error("critical_ledger_event_missing", "The immutable ledger contract omits required safety events.", "traceability.recorded_event_types", missing=missing_events))
772
+
773
+ achieved = "simulation_only"
774
+ if hazard_ids and all(
775
+ all(methods_by_hazard_scenario[hazard_id][scenario] - {"simulation"} for scenario in SAFETY_REQUIRED_SCENARIOS)
776
+ for hazard_id in hazard_ids
777
+ ):
778
+ achieved = "evidence_ready"
779
+ if hazard_ids and all(
780
+ "independent_assessment" in methods_by_hazard_scenario[hazard_id][scenario]
781
+ for hazard_id in hazard_ids
782
+ for scenario in SAFETY_REQUIRED_SCENARIOS
783
+ ):
784
+ independent_tests = [test for test in tests if isinstance(test, dict) and test.get("method") == "independent_assessment"]
785
+ if independent_tests and all(independent_evidence_by_test.get(str(test.get("test_id")), False) for test in independent_tests):
786
+ achieved = "independent_evidence_ready"
787
+
788
+ requested = payload.get("requested_assurance_level")
789
+ if isinstance(requested, str) and ASSURANCE_RANK.get(achieved, 0) < ASSURANCE_RANK.get(requested, 0):
790
+ errors.append(error("requested_assurance_level_not_met", "Observed evidence does not meet the requested assurance level.", "requested_assurance_level", requested=requested, achieved=achieved))
791
+ if achieved == "simulation_only":
792
+ warnings.append(error("simulation_is_not_deployment_evidence", "Simulation can discover hazards but cannot authorize physical deployment.", "requested_assurance_level"))
793
+
794
+ details = {
795
+ "requested_assurance_level": requested,
796
+ "achieved_assurance_level": "rejected" if errors else achieved,
797
+ "hazard_count": len(hazard_ids),
798
+ "barrier_count": len(barrier_ids),
799
+ "test_count": len(test_ids),
800
+ "evidence_count": len(evidence_ids),
801
+ "execution_authorized": False,
802
+ "deployment_authorized": False,
803
+ }
804
+ return errors, warnings, details
805
+
806
+
807
+ SEMANTIC_VALIDATORS: dict[str, SemanticValidator] = {
808
+ "core.causal_trace.v1": _validate_causal_trace,
809
+ "core.contract_program.v1": _validate_contract_program,
810
+ "core.context_gate.v1": _validate_context_gate,
811
+ "core.context_threshold.v1": _validate_context_threshold,
812
+ "core.control_decision.v1": _validate_control_decision,
813
+ "core.effect_result.v1": _validate_effect_result,
814
+ "core.entropy_signal.v1": _validate_entropy_signal,
815
+ "core.execution_receipt.v1": _validate_execution_receipt,
816
+ "core.memory_artifact.v1": _validate_memory_artifact,
817
+ "core.memory_generation_result.v1": _validate_memory_generation_result,
818
+ "core.operational_learning_event.v1": _validate_operational_learning_event,
819
+ "core.pattern_candidate.v1": _validate_pattern_candidate,
820
+ "core.physical_safety_assurance_case.v1": _validate_physical_safety_case,
821
+ "core.policy_lifecycle.v1": _validate_policy_lifecycle,
822
+ "core.retention_manifest.v1": _validate_retention_manifest,
823
+ "core.reversibility_policy.v1": _validate_reversibility_policy,
824
+ "core.state_transition.v1": _validate_state_transition,
825
+ "core.task_closeout.v1": _validate_task_closeout,
826
+ "core.template_promotion_candidate.v1": _validate_template_promotion,
827
+ }
828
+
829
+ SEMANTIC_RULE_IDS: dict[str, tuple[str, ...]] = {
830
+ "core.causal_trace.v1": ("resolved_graph", "acyclic_graph", "timezone", "fingerprint"),
831
+ "core.contract_program.v1": ("closed_instruction_language", "bounded_steps", "terminal_halt", "declared_capabilities", "no_effect_authority"),
832
+ "core.context_gate.v1": ("mode_status_consistency", "result_binding"),
833
+ "core.context_threshold.v1": ("bounded_percentages", "derived_threshold_decision"),
834
+ "core.control_decision.v1": ("reversibility_gate", "evidence_binding", "no_execution_authority"),
835
+ "core.effect_result.v1": ("dry_run_consistency", "destination_binding", "failure_evidence"),
836
+ "core.entropy_signal.v1": ("measurement_required", "critical_fail_closed", "timezone"),
837
+ "core.execution_receipt.v1": ("status_transition_consistency", "timezone", "fingerprint"),
838
+ "core.memory_artifact.v1": ("reference_only", "protected_retention", "timezone"),
839
+ "core.memory_generation_result.v1": ("result_reference_binding", "reuse_consistency"),
840
+ "core.operational_learning_event.v1": ("candidate_only", "no_self_authority", "timezone"),
841
+ "core.pattern_candidate.v1": ("support_threshold", "ambiguity_gate", "candidate_only"),
842
+ "core.physical_safety_assurance_case.v1": (
843
+ "bounded_claim",
844
+ "extreme_preservation",
845
+ "fail_closed_out_of_distribution",
846
+ "independent_physical_barriers",
847
+ "mandatory_adversarial_coverage",
848
+ "epistemic_dignity",
849
+ "immutable_critical_trace",
850
+ "no_deployment_authority",
851
+ ),
852
+ "core.policy_lifecycle.v1": ("temporal_order", "closed_policy_end", "no_self_supersession"),
853
+ "core.retention_manifest.v1": ("unique_decision", "mutation_checksum", "restore_path"),
854
+ "core.reversibility_policy.v1": ("responsible_approval", "compensation_gate", "no_execution_authority"),
855
+ "core.state_transition.v1": ("actual_transition", "responsible_irreversible_actor", "timezone"),
856
+ "core.task_closeout.v1": ("bounded_status", "passed_requires_evidence"),
857
+ "core.template_promotion_candidate.v1": ("complete_candidate", "risk_approval", "no_auto_activation"),
858
+ }
859
+
860
+
861
+ def executable_contract_versions() -> tuple[str, ...]:
862
+ """Return schema versions with registered semantic evaluators."""
863
+
864
+ return tuple(sorted(SEMANTIC_VALIDATORS))
865
+
866
+
867
+ def evaluate_contract_payload(payload: Any, *, strict: bool = True) -> dict[str, Any]:
868
+ """Evaluate structure, invariants, evidence links, and authority limits."""
869
+
870
+ errors: list[Error] = []
871
+ warnings: list[Error] = []
872
+ details: dict[str, Any] = {}
873
+ version = payload.get("schema_version") if isinstance(payload, dict) else None
874
+ contract_name = _schema_version_map().get(version)
875
+ if not isinstance(payload, dict):
876
+ errors.append(error("invalid_artifact", "Contract artifact must be an object."))
877
+ elif contract_name is None:
878
+ errors.append(error("unknown_schema_version", f"Unknown schema_version: {version!r}.", "schema_version"))
879
+ else:
880
+ schema = load_contract_schema(contract_name)
881
+ structural_errors = _schema_errors(payload, schema)
882
+ if strict:
883
+ structural_errors.extend(_strict_shape_errors(payload, schema, schema))
884
+ errors.extend(structural_errors)
885
+ errors.extend(_reference_errors(payload))
886
+ errors.extend(_fingerprint_errors(payload))
887
+ semantic = SEMANTIC_VALIDATORS.get(str(version))
888
+ if semantic is None:
889
+ errors.append(error("semantic_evaluator_missing", "Contract has no executable semantic evaluator.", "schema_version"))
890
+ elif not structural_errors:
891
+ semantic_errors, semantic_warnings, details = semantic(payload)
892
+ errors.extend(semantic_errors)
893
+ warnings.extend(semantic_warnings)
894
+
895
+ status = "passed" if not errors else "failed"
896
+ knowledge_status = "bounded_artifact"
897
+ if isinstance(payload, dict) and version == "core.physical_safety_assurance_case.v1":
898
+ claim = payload.get("claim")
899
+ if isinstance(claim, dict):
900
+ knowledge_status = str(claim.get("claim_status", "bounded_artifact"))
901
+ report: dict[str, Any] = {
902
+ "schema": "core.contract_evaluation.v1",
903
+ "contract_schema": version,
904
+ "status": status,
905
+ "decision": "accepted" if status == "passed" else "rejected",
906
+ "authority": "validation_only",
907
+ "knowledge_status": knowledge_status,
908
+ "truth_claimed": False,
909
+ "execution_authorized": False,
910
+ "deployment_authorized": False,
911
+ "input_fingerprint": input_fingerprint(payload),
912
+ "evaluated_rules": list(SEMANTIC_RULE_IDS.get(str(version), ())),
913
+ "details": details,
914
+ "errors": errors,
915
+ "warnings": warnings,
916
+ }
917
+ report["report_fingerprint"] = f"sha256:{canonical_json_hash(report)}"
918
+ return report
919
+
920
+
921
+ def evaluate_contract_file(path: Path, *, strict: bool = True) -> dict[str, Any]:
922
+ """Read and evaluate one JSON contract artifact."""
923
+
924
+ import json
925
+
926
+ try:
927
+ payload = json.loads(path.read_text(encoding="utf-8"))
928
+ except FileNotFoundError:
929
+ return evaluate_contract_payload({"schema_version": "unknown", "read_error": "file_not_found"}, strict=strict)
930
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
931
+ result = evaluate_contract_payload({"schema_version": "unknown", "read_error": exc.__class__.__name__}, strict=strict)
932
+ result["errors"] = [error("invalid_json", "Contract file is not valid readable JSON.", "path")]
933
+ result["status"] = "failed"
934
+ result["decision"] = "rejected"
935
+ result["report_fingerprint"] = f"sha256:{canonical_json_hash({key: value for key, value in result.items() if key != 'report_fingerprint'})}"
936
+ return result
937
+ return evaluate_contract_payload(payload, strict=strict)
938
+
939
+
940
+ def bind_artifact_fingerprint(payload: Mapping[str, Any]) -> dict[str, Any]:
941
+ """Return a deep-copied artifact with its canonical fingerprint bound."""
942
+
943
+ result = copy.deepcopy(dict(payload))
944
+ result["fingerprint"] = artifact_fingerprint(result)
945
+ return result