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,760 @@
1
+ """Deterministic audit trail primitives.
2
+
3
+ This module is intentionally separate from the operational EventLog.
4
+ It defines a read-only audit schema, canonical fingerprints, and
5
+ derivation helpers for audit artifacts produced from already-existing
6
+ reports.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+ from typing import Any, Mapping
16
+
17
+ from core_runtime.core.numeric_normalization import quantize_float
18
+
19
+
20
+ AUDIT_SCHEMA_VERSION = "4.9.0"
21
+ ALLOWED_CORRELATION_PREFIXES = (
22
+ "proposal::",
23
+ "profile::",
24
+ "pair::",
25
+ "report::",
26
+ "dataset::",
27
+ "graph::",
28
+ "router::",
29
+ "gaia_pipeline::",
30
+ )
31
+
32
+
33
+ class EventAuthority(Enum):
34
+ OBSERVATION = "observation"
35
+ DERIVED = "derived"
36
+ AUTHORITATIVE = "authoritative"
37
+
38
+
39
+ def _normalize_value(value: Any) -> Any:
40
+ if isinstance(value, Enum):
41
+ return value.value
42
+ if isinstance(value, float):
43
+ return quantize_float(value)
44
+ if isinstance(value, dict):
45
+ return {str(key): _normalize_value(item) for key, item in sorted(value.items(), key=lambda item: str(item[0]))}
46
+ if isinstance(value, Mapping):
47
+ return {str(key): _normalize_value(item) for key, item in sorted(value.items(), key=lambda item: str(item[0]))}
48
+ if isinstance(value, list):
49
+ return [_normalize_value(item) for item in value]
50
+ if isinstance(value, tuple):
51
+ return [_normalize_value(item) for item in value]
52
+ if isinstance(value, set):
53
+ return sorted((_normalize_value(item) for item in value), key=canonical_json)
54
+ return value
55
+
56
+
57
+ def canonical_json(data: Any) -> str:
58
+ return json.dumps(
59
+ _normalize_value(data),
60
+ sort_keys=True,
61
+ separators=(",", ":"),
62
+ ensure_ascii=False,
63
+ default=str,
64
+ )
65
+
66
+
67
+ def compute_operational_fingerprint(payload: Any) -> str:
68
+ canonical = canonical_json(payload)
69
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
70
+
71
+
72
+ def compute_audit_fingerprint(payload: Any) -> str:
73
+ canonical = canonical_json(payload)
74
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
75
+
76
+
77
+ def compute_graph_fingerprint(payload: Any) -> str:
78
+ canonical = canonical_json(payload)
79
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
80
+
81
+
82
+ def _validate_correlation_id(correlation_id: str) -> str:
83
+ if not isinstance(correlation_id, str):
84
+ raise TypeError(f"Expected string correlation_id, got {type(correlation_id).__name__}")
85
+ normalized = correlation_id.strip()
86
+ if not normalized:
87
+ raise ValueError("correlation_id must not be empty")
88
+ if not normalized.startswith(ALLOWED_CORRELATION_PREFIXES):
89
+ raise ValueError(
90
+ "correlation_id must use a semantic namespace such as "
91
+ "'proposal::<id>' or 'report::<id>'"
92
+ )
93
+ namespace, _, suffix = normalized.partition("::")
94
+ if not namespace or not suffix.strip():
95
+ raise ValueError("correlation_id must include a non-empty semantic identifier")
96
+ return normalized
97
+
98
+
99
+ def proposal_correlation_id(proposal_id: str) -> str:
100
+ return _validate_correlation_id(f"proposal::{proposal_id.strip()}")
101
+
102
+
103
+ def profile_correlation_id(profile_id: str) -> str:
104
+ return _validate_correlation_id(f"profile::{profile_id.strip()}")
105
+
106
+
107
+ def pair_correlation_id(pair_id: str) -> str:
108
+ return _validate_correlation_id(f"pair::{pair_id.strip()}")
109
+
110
+
111
+ def report_correlation_id(report_id: str) -> str:
112
+ return _validate_correlation_id(f"report::{report_id.strip()}")
113
+
114
+
115
+ def dataset_correlation_id(dataset_id: str) -> str:
116
+ return _validate_correlation_id(f"dataset::{dataset_id.strip()}")
117
+
118
+
119
+ def graph_correlation_id(graph_id: str) -> str:
120
+ return _validate_correlation_id(f"graph::{graph_id.strip()}")
121
+
122
+
123
+ def router_correlation_id(router_id: str) -> str:
124
+ return _validate_correlation_id(f"router::{router_id.strip()}")
125
+
126
+
127
+ def gaia_pipeline_correlation_id(run_id: str) -> str:
128
+ return _validate_correlation_id(f"gaia_pipeline::{run_id.strip()}")
129
+
130
+
131
+ def pair_canonical_key(pair: Mapping[str, Any]) -> tuple[str, str, str, str]:
132
+ return (
133
+ str(pair.get("pair_id", "")),
134
+ str(pair.get("profile_id", "")),
135
+ str(pair.get("proposal_id", "")),
136
+ str(pair.get("actual_status", "")),
137
+ )
138
+
139
+
140
+ def compute_audit_event_id(
141
+ event_type: str,
142
+ correlation_id: str,
143
+ logical_tick: int,
144
+ payload_hash: str,
145
+ ) -> str:
146
+ canonical = canonical_json(
147
+ {
148
+ "correlation_id": _validate_correlation_id(correlation_id),
149
+ "event_type": str(event_type),
150
+ "logical_tick": int(logical_tick),
151
+ "payload_hash": str(payload_hash),
152
+ }
153
+ )
154
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
155
+
156
+
157
+ def _event_body(
158
+ *,
159
+ event_id: str,
160
+ event_type: str,
161
+ authority: EventAuthority,
162
+ logical_tick: int,
163
+ correlation_id: str,
164
+ payload: dict[str, Any],
165
+ ) -> dict[str, Any]:
166
+ return {
167
+ "schema_version": AUDIT_SCHEMA_VERSION,
168
+ "event_id": event_id,
169
+ "event_type": event_type,
170
+ "authority": authority.value,
171
+ "logical_tick": int(logical_tick),
172
+ "correlation_id": correlation_id,
173
+ "payload": payload,
174
+ }
175
+
176
+
177
+ @dataclass(frozen=True)
178
+ class AuditEvent:
179
+ event_id: str
180
+ event_type: str
181
+ authority: EventAuthority
182
+ logical_tick: int
183
+ correlation_id: str
184
+ payload: dict[str, Any] = field(default_factory=dict)
185
+ fingerprint: str = ""
186
+ schema_version: str = AUDIT_SCHEMA_VERSION
187
+
188
+ def __post_init__(self) -> None:
189
+ if not isinstance(self.logical_tick, int):
190
+ raise TypeError(f"Expected int logical_tick, got {type(self.logical_tick).__name__}")
191
+ if self.logical_tick < 0:
192
+ raise ValueError("logical_tick must be non-negative")
193
+
194
+ normalized_authority = self.authority
195
+ if not isinstance(normalized_authority, EventAuthority):
196
+ normalized_authority = EventAuthority(str(normalized_authority))
197
+
198
+ normalized_correlation_id = _validate_correlation_id(self.correlation_id)
199
+ normalized_payload = _normalize_value(dict(self.payload))
200
+ payload_hash = compute_audit_fingerprint(normalized_payload)
201
+ expected_event_id = compute_audit_event_id(
202
+ self.event_type,
203
+ normalized_correlation_id,
204
+ self.logical_tick,
205
+ payload_hash,
206
+ )
207
+ body = _event_body(
208
+ event_id=expected_event_id,
209
+ event_type=self.event_type,
210
+ authority=normalized_authority,
211
+ logical_tick=self.logical_tick,
212
+ correlation_id=normalized_correlation_id,
213
+ payload=normalized_payload,
214
+ )
215
+ expected_fingerprint = compute_audit_fingerprint(body)
216
+
217
+ if self.event_id and self.event_id != expected_event_id:
218
+ raise ValueError("event_id does not match deterministic audit fingerprint")
219
+ if self.fingerprint and self.fingerprint != expected_fingerprint:
220
+ raise ValueError("fingerprint does not match deterministic audit fingerprint")
221
+
222
+ object.__setattr__(self, "authority", normalized_authority)
223
+ object.__setattr__(self, "correlation_id", normalized_correlation_id)
224
+ object.__setattr__(self, "payload", normalized_payload)
225
+ object.__setattr__(self, "event_id", expected_event_id)
226
+ object.__setattr__(self, "fingerprint", expected_fingerprint)
227
+
228
+ def to_dict(self) -> dict[str, Any]:
229
+ return {
230
+ "schema_version": self.schema_version,
231
+ "event_id": self.event_id,
232
+ "event_type": self.event_type,
233
+ "authority": self.authority.value,
234
+ "logical_tick": self.logical_tick,
235
+ "correlation_id": self.correlation_id,
236
+ "payload": _normalize_value(self.payload),
237
+ "fingerprint": self.fingerprint,
238
+ }
239
+
240
+ @classmethod
241
+ def from_dict(cls, data: Mapping[str, Any]) -> "AuditEvent":
242
+ return cls(
243
+ event_id=str(data.get("event_id", "")),
244
+ event_type=str(data.get("event_type", "")),
245
+ authority=EventAuthority(str(data.get("authority", EventAuthority.DERIVED.value))),
246
+ logical_tick=int(data.get("logical_tick", 0)),
247
+ correlation_id=str(data.get("correlation_id", "")),
248
+ payload=dict(data.get("payload", {})),
249
+ fingerprint=str(data.get("fingerprint", "")),
250
+ schema_version=str(data.get("schema_version", AUDIT_SCHEMA_VERSION)),
251
+ )
252
+
253
+
254
+ def canonical_event_sort_key(event: AuditEvent) -> tuple[int, str, str, str]:
255
+ return (
256
+ int(event.logical_tick),
257
+ event.correlation_id,
258
+ event.event_type,
259
+ event.event_id,
260
+ )
261
+
262
+
263
+ def _compatibility_terminal_event_type(pair: Mapping[str, Any]) -> str:
264
+ status = str(pair.get("actual_status", "")).lower()
265
+ profile_id = str(pair.get("profile_id", "")).lower()
266
+ pair_id = str(pair.get("pair_id", "")).lower()
267
+ if status != "compatible":
268
+ return "CompatibilityRejected"
269
+ if "certified" in pair_id or "certified" in profile_id:
270
+ return "CompatibilityCertified"
271
+ return "CompatibilityAccepted"
272
+
273
+
274
+ def derive_compatibility_audit_events(
275
+ report: Mapping[str, Any],
276
+ *,
277
+ source_fingerprint: str | None = None,
278
+ ) -> list[AuditEvent]:
279
+ report_fingerprint = source_fingerprint or compute_operational_fingerprint(report)
280
+ report_id = report_fingerprint
281
+ correlation = report_correlation_id(report_id)
282
+ pairs = list(report.get("matrix", []))
283
+ if not pairs:
284
+ pairs = list(report.get("pairs", []))
285
+ pairs = sorted(
286
+ [pair for pair in pairs if isinstance(pair, Mapping)],
287
+ key=pair_canonical_key,
288
+ )
289
+
290
+ events: list[AuditEvent] = []
291
+ for index, pair in enumerate(pairs):
292
+ pair_id = str(pair.get("pair_id", "")).strip()
293
+ if not pair_id:
294
+ continue
295
+ pair_correlation = pair_correlation_id(pair_id)
296
+ payload = {
297
+ "pair_id": pair_id,
298
+ "profile_id": str(pair.get("profile_id", "")),
299
+ "proposal_id": str(pair.get("proposal_id", "")),
300
+ "expected_status": str(pair.get("expected_status", "")),
301
+ "actual_status": str(pair.get("actual_status", "")),
302
+ "incompatible_reason": pair.get("incompatible_reason"),
303
+ "report_id": report_id,
304
+ }
305
+ events.append(
306
+ AuditEvent(
307
+ event_id="",
308
+ event_type="CompatibilityChecked",
309
+ authority=EventAuthority.DERIVED,
310
+ logical_tick=index * 2,
311
+ correlation_id=pair_correlation,
312
+ payload=payload,
313
+ )
314
+ )
315
+ terminal_type = _compatibility_terminal_event_type(pair)
316
+ if terminal_type == "CompatibilityRejected":
317
+ terminal_payload = {
318
+ **payload,
319
+ "decision": "rejected",
320
+ }
321
+ elif terminal_type == "CompatibilityCertified":
322
+ terminal_payload = {
323
+ **payload,
324
+ "decision": "certified",
325
+ }
326
+ else:
327
+ terminal_payload = {
328
+ **payload,
329
+ "decision": "accepted",
330
+ }
331
+ events.append(
332
+ AuditEvent(
333
+ event_id="",
334
+ event_type=terminal_type,
335
+ authority=EventAuthority.DERIVED,
336
+ logical_tick=index * 2 + 1,
337
+ correlation_id=pair_correlation,
338
+ payload=terminal_payload,
339
+ )
340
+ )
341
+
342
+ report_payload = {
343
+ "report_id": report_id,
344
+ "report_schema": str(report.get("schema", "")),
345
+ "status": str(report.get("status", "")),
346
+ "summary": dict(report.get("summary", {})) if isinstance(report.get("summary", {}), Mapping) else report.get("summary", {}),
347
+ "pair_count": len(pairs),
348
+ }
349
+ events.append(
350
+ AuditEvent(
351
+ event_id="",
352
+ event_type="CompatibilityMatrixReported",
353
+ authority=EventAuthority.DERIVED,
354
+ logical_tick=len(events),
355
+ correlation_id=correlation,
356
+ payload=report_payload,
357
+ )
358
+ )
359
+ return events
360
+
361
+
362
+ def _decision_event_type(decision: str) -> str:
363
+ normalized = decision.lower().strip()
364
+ if normalized == "certified":
365
+ return "ProposalCertified"
366
+ if normalized == "accepted":
367
+ return "ProposalAccepted"
368
+ if normalized == "unsafe":
369
+ return "ProposalUnsafe"
370
+ if normalized == "unsupported":
371
+ return "ProposalUnsupported"
372
+ if normalized == "missing_evidence":
373
+ return "ProposalMissingEvidence"
374
+ return "ProposalRejected"
375
+
376
+
377
+ def derive_expert_report_audit_events(
378
+ report: Mapping[str, Any],
379
+ *,
380
+ source_fingerprint: str | None = None,
381
+ ) -> list[AuditEvent]:
382
+ report_fingerprint = source_fingerprint or compute_operational_fingerprint(report)
383
+ report_id = report_fingerprint
384
+ correlation = report_correlation_id(report_id)
385
+ proposals = list(report.get("proposals", []))
386
+ proposals = sorted(
387
+ [proposal for proposal in proposals if isinstance(proposal, Mapping)],
388
+ key=lambda proposal: (
389
+ str(proposal.get("proposal_id", "")),
390
+ str(proposal.get("file", "")),
391
+ str(proposal.get("decision", "")),
392
+ ),
393
+ )
394
+
395
+ events: list[AuditEvent] = []
396
+ for index, proposal in enumerate(proposals):
397
+ proposal_id = str(proposal.get("proposal_id", "")).strip()
398
+ if not proposal_id:
399
+ continue
400
+ proposal_correlation = proposal_correlation_id(proposal_id)
401
+ payload = {
402
+ "proposal_id": proposal_id,
403
+ "decision": str(proposal.get("decision", "")),
404
+ "reason_code": str(proposal.get("reason_code", "")),
405
+ "status": str(proposal.get("status", "")),
406
+ "file": str(proposal.get("file", "")),
407
+ "report_id": report_id,
408
+ }
409
+ events.append(
410
+ AuditEvent(
411
+ event_id="",
412
+ event_type="ProposalEvaluated",
413
+ authority=EventAuthority.DERIVED,
414
+ logical_tick=index * 2,
415
+ correlation_id=proposal_correlation,
416
+ payload=payload,
417
+ )
418
+ )
419
+ decision_event_type = _decision_event_type(payload["decision"])
420
+ events.append(
421
+ AuditEvent(
422
+ event_id="",
423
+ event_type=decision_event_type,
424
+ authority=EventAuthority.DERIVED,
425
+ logical_tick=index * 2 + 1,
426
+ correlation_id=proposal_correlation,
427
+ payload=payload,
428
+ )
429
+ )
430
+
431
+ report_payload = {
432
+ "report_id": report_id,
433
+ "report_schema": str(report.get("schema", "")),
434
+ "status": str(report.get("status", "")),
435
+ "summary": dict(report.get("summary", {})) if isinstance(report.get("summary", {}), Mapping) else report.get("summary", {}),
436
+ "proposal_count": len(proposals),
437
+ }
438
+ events.append(
439
+ AuditEvent(
440
+ event_id="",
441
+ event_type="ExpertProposalBatchReported",
442
+ authority=EventAuthority.DERIVED,
443
+ logical_tick=len(events),
444
+ correlation_id=correlation,
445
+ payload=report_payload,
446
+ )
447
+ )
448
+ return events
449
+
450
+
451
+ def _router_validation_audit_events(
452
+ payload: Mapping[str, Any],
453
+ *,
454
+ source_fingerprint: str | None = None,
455
+ ) -> list[AuditEvent]:
456
+ routing_id = str(payload.get("routing_id", "")).strip() or source_fingerprint or "router"
457
+ warnings = [item for item in payload.get("warnings", []) if isinstance(item, Mapping)]
458
+ errors = [item for item in payload.get("errors", []) if isinstance(item, Mapping)]
459
+ event_payload = {
460
+ "routing_id": routing_id,
461
+ "validation_schema": str(payload.get("schema", "")),
462
+ "status": str(payload.get("status", "")),
463
+ "error_count": len(errors),
464
+ "warning_count": len(warnings),
465
+ "error_codes": [
466
+ str(item.get("code", "")).strip()
467
+ for item in errors
468
+ if str(item.get("code", "")).strip()
469
+ ],
470
+ "warning_codes": [
471
+ str(item.get("code", "")).strip()
472
+ for item in warnings
473
+ if str(item.get("code", "")).strip()
474
+ ],
475
+ "source_fingerprint": source_fingerprint or compute_operational_fingerprint(payload),
476
+ }
477
+ return [
478
+ AuditEvent(
479
+ event_id="",
480
+ event_type="RouterFixtureValidated",
481
+ authority=EventAuthority.DERIVED,
482
+ logical_tick=0,
483
+ correlation_id=router_correlation_id(routing_id),
484
+ payload=event_payload,
485
+ )
486
+ ]
487
+
488
+
489
+ def _router_evaluation_audit_events(
490
+ payload: Mapping[str, Any],
491
+ *,
492
+ source_fingerprint: str | None = None,
493
+ ) -> list[AuditEvent]:
494
+ routing_id = str(payload.get("routing_id", "")).strip() or source_fingerprint or "router"
495
+ profile_id = str(payload.get("profile_id", "")).strip()
496
+ summary = payload.get("evaluation_summary", {})
497
+ summary_payload = dict(summary) if isinstance(summary, Mapping) else {}
498
+ selected_experts = [item for item in payload.get("selected_experts", []) if isinstance(item, Mapping)]
499
+ rejected_experts = [item for item in payload.get("rejected_experts", []) if isinstance(item, Mapping)]
500
+ selected_expert_records = [
501
+ {
502
+ "decision": "selected",
503
+ "expert_id": str(item.get("expert_id", "")).strip(),
504
+ "proposal_id": str(item.get("proposal_id", "")).strip(),
505
+ "reason": str(item.get("reason", "")).strip(),
506
+ }
507
+ for item in selected_experts
508
+ if str(item.get("expert_id", "")).strip() and str(item.get("proposal_id", "")).strip()
509
+ ]
510
+ rejected_expert_records = [
511
+ {
512
+ "decision": "rejected",
513
+ "expert_id": str(item.get("expert_id", "")).strip(),
514
+ "proposal_id": str(item.get("proposal_id", "")).strip(),
515
+ "reason": str(item.get("reason", "")).strip(),
516
+ }
517
+ for item in rejected_experts
518
+ if str(item.get("expert_id", "")).strip() and str(item.get("proposal_id", "")).strip()
519
+ ]
520
+ event_payload = {
521
+ "routing_id": routing_id,
522
+ "profile_id": profile_id,
523
+ "evaluation_schema": str(payload.get("schema", "")),
524
+ "status": str(payload.get("status", "")),
525
+ "selected_count": int(summary_payload.get("selected_count", len(selected_experts))),
526
+ "rejected_count": int(summary_payload.get("rejected_count", len(rejected_experts))),
527
+ "total_proposals": int(summary_payload.get("total_proposals", len(selected_experts) + len(rejected_experts))),
528
+ "reason_codes": [
529
+ str(code).strip()
530
+ for code in payload.get("reason_codes", [])
531
+ if str(code).strip()
532
+ ],
533
+ "selected_expert_ids": [
534
+ str(item.get("expert_id", "")).strip()
535
+ for item in selected_experts
536
+ if str(item.get("expert_id", "")).strip()
537
+ ],
538
+ "rejected_expert_ids": [
539
+ str(item.get("expert_id", "")).strip()
540
+ for item in rejected_experts
541
+ if str(item.get("expert_id", "")).strip()
542
+ ],
543
+ "selected_experts": selected_expert_records,
544
+ "rejected_experts": rejected_expert_records,
545
+ "source_fingerprint": source_fingerprint or compute_operational_fingerprint(payload),
546
+ }
547
+ return [
548
+ AuditEvent(
549
+ event_id="",
550
+ event_type="RouterEligibilityEvaluated",
551
+ authority=EventAuthority.DERIVED,
552
+ logical_tick=0,
553
+ correlation_id=router_correlation_id(routing_id),
554
+ payload=event_payload,
555
+ )
556
+ ]
557
+
558
+
559
+ def _router_report_audit_events(
560
+ payload: Mapping[str, Any],
561
+ *,
562
+ source_fingerprint: str | None = None,
563
+ ) -> list[AuditEvent]:
564
+ report_id = source_fingerprint or compute_operational_fingerprint(payload)
565
+ summary = payload.get("summary", {})
566
+ summary_payload = dict(summary) if isinstance(summary, Mapping) else {}
567
+ fixtures = [item for item in payload.get("fixtures", []) if isinstance(item, Mapping)]
568
+ event_payload = {
569
+ "report_id": report_id,
570
+ "report_schema": str(payload.get("schema", "")),
571
+ "status": str(payload.get("status", "")),
572
+ "summary": summary_payload,
573
+ "fixture_count": len(fixtures),
574
+ "fixture_files": [
575
+ str(item.get("file", "")).strip()
576
+ for item in fixtures
577
+ if str(item.get("file", "")).strip()
578
+ ],
579
+ "source_fingerprint": source_fingerprint or compute_operational_fingerprint(payload),
580
+ }
581
+ return [
582
+ AuditEvent(
583
+ event_id="",
584
+ event_type="RouterBatchReportGenerated",
585
+ authority=EventAuthority.DERIVED,
586
+ logical_tick=0,
587
+ correlation_id=report_correlation_id(report_id),
588
+ payload=event_payload,
589
+ )
590
+ ]
591
+
592
+
593
+ def _router_replay_audit_events(
594
+ payload: Mapping[str, Any],
595
+ *,
596
+ source_fingerprint: str | None = None,
597
+ ) -> list[AuditEvent]:
598
+ routing_id = str(payload.get("routing_id", "")).strip()
599
+ event_type = "RouterReplayCertified" if str(payload.get("status", "")).strip() == "certified" else "RouterReplayDiverged"
600
+ run_1 = payload.get("run_1", {})
601
+ run_2 = payload.get("run_2", {})
602
+ event_payload = {
603
+ "replay_schema": str(payload.get("replay_schema", payload.get("schema", ""))),
604
+ "status": str(payload.get("status", "")),
605
+ "fixture": str(payload.get("fixture", "")).strip(),
606
+ "routing_id": routing_id,
607
+ "fingerprint": payload.get("fingerprint"),
608
+ "diff": payload.get("diff"),
609
+ "run_1_output_hash": run_1.get("output_hash") if isinstance(run_1, Mapping) else None,
610
+ "run_2_output_hash": run_2.get("output_hash") if isinstance(run_2, Mapping) else None,
611
+ "source_fingerprint": source_fingerprint or compute_operational_fingerprint(payload),
612
+ }
613
+ correlation_seed = routing_id or source_fingerprint or compute_operational_fingerprint(payload)
614
+ return [
615
+ AuditEvent(
616
+ event_id="",
617
+ event_type=event_type,
618
+ authority=EventAuthority.DERIVED,
619
+ logical_tick=0,
620
+ correlation_id=router_correlation_id(correlation_seed),
621
+ payload=event_payload,
622
+ )
623
+ ]
624
+
625
+
626
+ def _gaia_surrogate_sort_key(entry: Mapping[str, Any]) -> tuple[int, str, int, str]:
627
+ source_id = str(entry.get("source_id", "")).strip()
628
+ row_index = entry.get("row_index", 0)
629
+ try:
630
+ numeric_source_id = int(source_id)
631
+ except ValueError:
632
+ numeric_source_id = 0
633
+ try:
634
+ numeric_row_index = int(row_index)
635
+ except (TypeError, ValueError):
636
+ numeric_row_index = 0
637
+ return (
638
+ numeric_row_index,
639
+ source_id,
640
+ numeric_source_id,
641
+ str(entry.get("proposal_id", "")),
642
+ )
643
+
644
+
645
+ def derive_gaia_surrogate_audit_events(
646
+ evaluation: Mapping[str, Any],
647
+ *,
648
+ source_fingerprint: str | None = None,
649
+ ) -> list[AuditEvent]:
650
+ evaluation_fingerprint = source_fingerprint or compute_operational_fingerprint(evaluation)
651
+ run_id = str(evaluation.get("run_id", "")).strip() or evaluation_fingerprint
652
+ correlation = gaia_pipeline_correlation_id(run_id)
653
+ proposal_id = str(evaluation.get("proposal_id", "")).strip()
654
+ proposal_hash = str(evaluation.get("proposal_hash", "")).strip()
655
+ weights_hash = str(evaluation.get("weights_hash", "")).strip()
656
+ mismatches = list(evaluation.get("mismatches", []))
657
+ mismatches = sorted(
658
+ [entry for entry in mismatches if isinstance(entry, Mapping)],
659
+ key=_gaia_surrogate_sort_key,
660
+ )
661
+
662
+ total_evaluated = int(evaluation.get("total_evaluated", len(mismatches)))
663
+ correct_predictions = int(evaluation.get("correct_predictions", max(total_evaluated - len(mismatches), 0)))
664
+ agreement_rate = float(evaluation.get("agreement_rate", 0.0))
665
+ decision_threshold = float(evaluation.get("decision_threshold", 0.5))
666
+
667
+ summary_payload = {
668
+ "run_id": run_id,
669
+ "proposal_id": proposal_id,
670
+ "proposal_hash": proposal_hash,
671
+ "evaluation_hash": evaluation_fingerprint,
672
+ "weights_hash": weights_hash,
673
+ "feature_columns": list(evaluation.get("feature_columns", [])),
674
+ "decision_threshold": decision_threshold,
675
+ "total_evaluated": total_evaluated,
676
+ "correct_predictions": correct_predictions,
677
+ "mismatch_count": len(mismatches),
678
+ "agreement_rate": agreement_rate,
679
+ }
680
+ events: list[AuditEvent] = [
681
+ AuditEvent(
682
+ event_id="",
683
+ event_type="GaiaSurrogateProposalEvaluated",
684
+ authority=EventAuthority.DERIVED,
685
+ logical_tick=0,
686
+ correlation_id=correlation,
687
+ payload=summary_payload,
688
+ )
689
+ ]
690
+
691
+ for index, mismatch in enumerate(mismatches, start=1):
692
+ mismatch_payload = {
693
+ "run_id": run_id,
694
+ "proposal_id": proposal_id,
695
+ "source_id": str(mismatch.get("source_id", "")).strip(),
696
+ "row_index": int(mismatch.get("row_index", index)),
697
+ "predicted_nearby": bool(mismatch.get("predicted_nearby", False)),
698
+ "actual_nearby": bool(mismatch.get("actual_nearby", False)),
699
+ "probability": mismatch.get("probability"),
700
+ "distance_pc": mismatch.get("distance_pc"),
701
+ "decision_threshold": decision_threshold,
702
+ "evaluation_hash": evaluation_fingerprint,
703
+ }
704
+ events.append(
705
+ AuditEvent(
706
+ event_id="",
707
+ event_type="GaiaSurrogatePredictionMismatch",
708
+ authority=EventAuthority.DERIVED,
709
+ logical_tick=index,
710
+ correlation_id=correlation,
711
+ payload=mismatch_payload,
712
+ )
713
+ )
714
+
715
+ completion_payload = {
716
+ "run_id": run_id,
717
+ "proposal_id": proposal_id,
718
+ "evaluation_hash": evaluation_fingerprint,
719
+ "proposal_hash": proposal_hash,
720
+ "weights_hash": weights_hash,
721
+ "total_evaluated": total_evaluated,
722
+ "mismatch_count": len(mismatches),
723
+ "agreement_rate": agreement_rate,
724
+ "status": str(evaluation.get("status", "passed")),
725
+ }
726
+ events.append(
727
+ AuditEvent(
728
+ event_id="",
729
+ event_type="GaiaSurrogateEvaluationCompleted",
730
+ authority=EventAuthority.DERIVED,
731
+ logical_tick=len(events),
732
+ correlation_id=correlation,
733
+ payload=completion_payload,
734
+ )
735
+ )
736
+ return events
737
+
738
+
739
+ def derive_events_for_source(
740
+ *,
741
+ artifact_type: str,
742
+ artifact_payload: Mapping[str, Any],
743
+ source_fingerprint: str | None = None,
744
+ ) -> list[AuditEvent]:
745
+ normalized_type = artifact_type.strip().lower()
746
+ if normalized_type == "compatibility_matrix":
747
+ return derive_compatibility_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
748
+ if normalized_type == "expert_report":
749
+ return derive_expert_report_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
750
+ if normalized_type == "expert_router_validation":
751
+ return _router_validation_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
752
+ if normalized_type == "expert_router_evaluation":
753
+ return _router_evaluation_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
754
+ if normalized_type in {"expert_router_report", "expert_router_batch_report"}:
755
+ return _router_report_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
756
+ if normalized_type in {"expert_router_replay_certification", "expert_router_replay_certification_batch"}:
757
+ return _router_replay_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
758
+ if normalized_type == "gaia_surrogate":
759
+ return derive_gaia_surrogate_audit_events(artifact_payload, source_fingerprint=source_fingerprint)
760
+ raise ValueError(f"Unsupported audit source type: {artifact_type}")