diff --git a/Makefile b/Makefile index 1dfd020..f5a50c3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: validate validate-resource-contract validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-ingestion-pipeline-examples validate-data-acquisition-examples validate-glossary-alignment-examples validate-data-class-examples validate-table-keys validate-dag-loop validate-vocab-currency-loop vocab-currency-dogfood validate-glossary-promotion validate-agreement validate-stopword-analysis stopword-analysis-live validate-kgram-differential validate-learned-dictionary train-dataclass-classifiers validate-assay-fleet-examples validate-image-trust-examples validate-consent-plane-examples validate-schema-references validate-fingerprint-stack validate-multiverseal-twin-examples validate-multiverseal-twin-reference-impl +.PHONY: validate validate-resource-contract validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-ingestion-pipeline-examples validate-data-acquisition-examples validate-glossary-alignment-examples validate-data-class-examples validate-table-keys validate-dag-loop validate-vocab-currency-loop vocab-currency-dogfood validate-glossary-promotion validate-agreement validate-stopword-analysis stopword-analysis-live validate-kgram-differential validate-learned-dictionary train-dataclass-classifiers validate-assay-fleet-examples validate-image-trust-examples validate-consent-plane-examples validate-schema-references validate-fingerprint-stack validate-multiverseal-twin-examples validate-multiverseal-twin-reference-impl validate-rld-examples -validate: validate-data-class-examples validate-glossary-alignment-examples validate-data-acquisition-examples validate-ingestion-pipeline-examples validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-resource-contract validate-table-keys validate-dag-loop validate-vocab-currency-loop validate-glossary-promotion validate-agreement validate-stopword-analysis validate-kgram-differential validate-learned-dictionary validate-trained-classifiers validate-multiverseal-twin-examples validate-multiverseal-twin-reference-impl validate-assay-fleet-examples validate-image-trust-examples validate-consent-plane-examples validate-schema-references validate-fingerprint-stack +validate: validate-data-class-examples validate-glossary-alignment-examples validate-data-acquisition-examples validate-ingestion-pipeline-examples validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-lawful-dispatch-receipt validate-architectural-building-block validate-agent-passport-examples validate-seam-definition-examples validate-agent-system-vocabulary validate-genesis-inception-examples validate-measurement validate-world-model-examples validate-eval-item-examples validate-resource-contract validate-table-keys validate-dag-loop validate-vocab-currency-loop validate-glossary-promotion validate-agreement validate-stopword-analysis validate-kgram-differential validate-learned-dictionary validate-trained-classifiers validate-multiverseal-twin-examples validate-multiverseal-twin-reference-impl validate-assay-fleet-examples validate-image-trust-examples validate-consent-plane-examples validate-schema-references validate-fingerprint-stack validate-rld-examples @echo "OK: validate" validate-assay-fleet-examples: @@ -258,3 +258,7 @@ validate-multiverseal-twin-examples: validate-multiverseal-twin-reference-impl: python3 -m pip install --user numpy >/dev/null cd reference/multiverseal-twin && python3 test_mvtwin.py + +validate-rld-examples: + python3 -m pip install --user jsonschema referencing >/dev/null + python3 tools/validate_rld_examples.py diff --git a/docs/adr/0018-rld-loader-diagnostics-contracts.md b/docs/adr/0018-rld-loader-diagnostics-contracts.md new file mode 100644 index 0000000..779befc --- /dev/null +++ b/docs/adr/0018-rld-loader-diagnostics-contracts.md @@ -0,0 +1,43 @@ +# ADR-0018: RLD loader & diagnostics contracts (LoaderFault / ResilientDiagnosticRecord) + +Status: Accepted +Date: 2026-08-03 +Part of: Epoch E13 (Reference-Gated Sovereign Stack), workstream WS-A + +## Context + +The Resilient Loader & Diagnostics (RLD) design ships as a Rust workspace + a Lean +launch-completeness proof. What the fabric lacked was the *contracts* — so the loader's +faults and crash records could be carried, deduped, and projected like every other typed +value in the estate. This lands them, making RLD the L1 fail-closed substrate the rest of +Epoch E13 (identity twin, capture receipts) stands on. + +## Decisions + +1. **Every fatal stop is a typed value first (I5 / R2).** `LoaderFault` is an algebraic sum + with **stable codes** (`LDR-DEP-MISSING`, `LDR-ABI-MISMATCH`, …). A bare string abort is a + spec violation. Non-fatal `FEATURE`/`LAZY` misses are recorded too, at `degraded`/`handled` + — the "what quietly turned off" trail the reference format could not express. + +2. **Severity is a table, not a heuristic.** The failing edge's binding class fixes severity: + `REQUIRED→fatal`, `FEATURE→degraded`, `WEAK→info`, `LAZY→handled|fatal`. Machine-checked. + +3. **Root-cause-first, diffable, coalescing.** `ResilientDiagnosticRecord` puts the typed + fault as field #1; `reproKey = H(code ‖ import.name ‖ chain ‖ image_hash)` excludes + timestamps/PIDs so N occurrences of a defect collapse to one bucket. + +4. **Privacy by projection (I8), not redaction.** The telemetry tier is an *allowlist* + projection of the local record; path-like fields are represented symbolically (store names, + content hashes), so there is no user path to leak — enforced by the validator. This is the + same discipline as the reasoning-evidence receipts and the twin's reference-gated projection + (ADR-0014): **what leaves is a projection, never a scrub.** + +## Consequences + +- New conformant contracts `LoaderFault` + `ResilientDiagnosticRecord`, canonical examples + (the reference `CoreSimulator` abort re-encoded, plus its correct `FEATURE`-degraded form), + and an invariant validator wired into `make validate`. +- RDR `$ref`s `LoaderFault` by `$id`; validators resolve it via a `referencing` registry. +- The Rust workspace + Lean proof remain the reference implementation; these contracts are the + wire/record form the fabric carries. +- Ties WS-A into the E13 through-line: bind-at-capture, fail-closed, projection-not-redaction. diff --git a/examples/loader_fault.json b/examples/loader_fault.json new file mode 100644 index 0000000..c17822e --- /dev/null +++ b/examples/loader_fault.json @@ -0,0 +1,17 @@ +{ + "type": "LoaderFault", + "kind": "InterfaceMismatch", + "code": "LDR-ABI-MISMATCH", + "severity": "fatal", + "import": { + "name": "libfoo", + "versionReq": ">=1.0", + "binding": "REQUIRED", + "requestedBy": "app", + "chain": [ + "app" + ] + }, + "want": "b3:abi-1", + "got": "b3:abi-2" +} diff --git a/examples/resilient_diagnostic_record.json b/examples/resilient_diagnostic_record.json new file mode 100644 index 0000000..cbaa424 --- /dev/null +++ b/examples/resilient_diagnostic_record.json @@ -0,0 +1,59 @@ +{ + "id": "urn:srcos:rdr:claude-ios-sim:2026-08-03", + "type": "ResilientDiagnosticRecord", + "specVersion": "2.0.0", + "schema": "rdr/1", + "process": { + "id": "pid:claude-ios-sim", + "imageHash": "b3:img-abc", + "entry": "_main", + "profile": "prod" + }, + "fault": { + "type": "LoaderFault", + "kind": "MissingProvider", + "code": "LDR-DEP-MISSING", + "severity": "fatal", + "import": { + "name": "com.apple.CoreSimulator", + "versionReq": ">=0", + "binding": "REQUIRED", + "requestedBy": "com.facebook.FBSimulatorControl", + "chain": [ + "com.anthropic.claude.ios-sim", + "com.facebook.FBSimulatorControl" + ] + }, + "searched": [ + { + "store": "system", + "result": "absent" + }, + { + "store": "cryptex", + "result": "absent" + } + ] + }, + "phase": "seal-verify", + "closure": { + "coreSize": 3, + "sealedManifestHash": "b3:scm-xyz" + }, + "chain": [ + "com.anthropic.claude.ios-sim", + "com.facebook.FBSimulatorControl" + ], + "env": { + "osBuild": "25A1", + "model": "iPhone", + "abiLevel": 210, + "bootId": "boot-1" + }, + "redaction": { + "tier": "telemetry", + "saltId": "salt-1" + }, + "reproKey": "b3:repro-abc", + "capturedAt": "2026-08-03T00:00:00Z" +} diff --git a/examples/resilient_diagnostic_record_degraded.json b/examples/resilient_diagnostic_record_degraded.json new file mode 100644 index 0000000..a956f8d --- /dev/null +++ b/examples/resilient_diagnostic_record_degraded.json @@ -0,0 +1,56 @@ +{ + "id": "urn:srcos:rdr:claude-desktop:2026-08-03", + "type": "ResilientDiagnosticRecord", + "specVersion": "2.0.0", + "schema": "rdr/1", + "process": { + "id": "pid:claude-desktop", + "imageHash": "b3:img-def", + "entry": "_main", + "profile": "prod" + }, + "fault": { + "type": "LoaderFault", + "kind": "MissingProvider", + "code": "LDR-DEP-MISSING", + "severity": "degraded", + "import": { + "name": "com.apple.CoreSimulator", + "versionReq": ">=0", + "binding": "FEATURE", + "capability": "simulator-control", + "requestedBy": "com.facebook.FBSimulatorControl", + "chain": [ + "com.anthropic.claude.desktop", + "com.facebook.FBSimulatorControl" + ] + }, + "searched": [ + { + "store": "system", + "result": "absent" + } + ] + }, + "phase": "running-lazy", + "closure": { + "coreSize": 3, + "sealedManifestHash": "b3:scm-desktop" + }, + "chain": [ + "com.anthropic.claude.desktop", + "com.facebook.FBSimulatorControl" + ], + "env": { + "osBuild": "25A1", + "model": "Mac", + "abiLevel": 210, + "bootId": "boot-2" + }, + "redaction": { + "tier": "local", + "saltId": "salt-2" + }, + "reproKey": "b3:repro-def", + "capturedAt": "2026-08-03T00:00:00Z" +} diff --git a/registry/contract-registry.json b/registry/contract-registry.json index 777863a..7f46175 100644 --- a/registry/contract-registry.json +++ b/registry/contract-registry.json @@ -811,6 +811,11 @@ "path": "schemas/LivenessProfile.json", "sha256": "sha256:2ba5665d3322c3861f70c7b8e25b0c13b306c65103a53cabf190f137d3a47578" }, + "LoaderFault": { + "$id": "https://schemas.srcos.ai/v2/LoaderFault.json", + "path": "schemas/LoaderFault.json", + "sha256": "sha256:f1ca80e3aeec7402ad46d6682ff35d96cd7227ae745b9e96441c6b91fd9a2684" + }, "LocalArtifactRef": { "$id": "https://schemas.srcos.ai/v2/LocalArtifactRef.json", "path": "schemas/LocalArtifactRef.json", @@ -1251,6 +1256,11 @@ "path": "schemas/ReserveScenarioReport.json", "sha256": "sha256:e4a878579aae3d3795d8d8a4fb2eb24e1159877ed6306a5fdbf6fadf3f6b4545" }, + "ResilientDiagnosticRecord": { + "$id": "https://schemas.srcos.ai/v2/ResilientDiagnosticRecord.json", + "path": "schemas/ResilientDiagnosticRecord.json", + "sha256": "sha256:4de57a2346de5316b7b501b27b339729742f8252105e446846d707751791ec2b" + }, "ResourceContract": { "$id": "https://schemas.srcos.ai/v2/ResourceContract.json", "path": "schemas/ResourceContract.json", @@ -1837,5 +1847,5 @@ "sha256": "sha256:eadc32085987ffda647b090e3eba3726edd102a66d4736e239457bb74867004b" } }, - "count": 367 + "count": 369 } diff --git a/schemas/LoaderFault.json b/schemas/LoaderFault.json new file mode 100644 index 0000000..85efbea --- /dev/null +++ b/schemas/LoaderFault.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/LoaderFault.json", + "title": "LoaderFault", + "description": "A typed, stable-coded fault the resilient loader (rld) constructs BEFORE any degradation or halt (invariant I5 / rule R2: no bare abort). The class of the failing import edge deterministically fixes severity (REQUIRED->fatal, FEATURE->degraded, LAZY->handled-or-fatal, WEAK->info) — the classifier is a table, not a heuristic. Degradations (non-fatal FEATURE/LAZY misses) are recorded here too, giving a full 'what quietly turned off' trail. Embedded in a ResilientDiagnosticRecord.", + "type": "object", + "additionalProperties": false, + "required": ["type", "kind", "code", "severity"], + "properties": { + "type": { "const": "LoaderFault", "description": "Type discriminator; always 'LoaderFault'." }, + "kind": { + "type": "string", + "enum": ["MissingProvider", "VersionUnsatisfied", "InterfaceMismatch", "SignatureInvalid", "CorruptImage", "SandboxDenied", "ResourceExhausted"], + "description": "The fault variant." + }, + "code": { + "type": "string", + "enum": ["LDR-DEP-MISSING", "LDR-DEP-VERSION", "LDR-ABI-MISMATCH", "LDR-SIG-INVALID", "LDR-IMG-CORRUPT", "LDR-SBX-DENY", "LDR-RES-EXHAUST"], + "description": "Stable machine code; the root-cause line is a pure lookup on this (no free-text parsing)." + }, + "severity": { + "type": "string", + "enum": ["fatal", "degraded", "handled", "info"], + "description": "Determined by the failing edge's binding class, not by heuristics." + }, + "import": { + "type": "object", + "additionalProperties": false, + "description": "The import reference that failed (present for dependency/ABI faults).", + "properties": { + "name": { "type": "string", "description": "Required interface name." }, + "versionReq": { "type": "string", "description": "Requested version range." }, + "binding": { "type": "string", "enum": ["REQUIRED", "FEATURE", "LAZY", "WEAK"], "description": "Binding class of the failing edge (drives severity). The reference incident's defect: this was REQUIRED where it should have been FEATURE." }, + "capability": { "type": "string", "description": "Feature capability token kappa, when binding=FEATURE." }, + "requestedBy": { "type": "string", "description": "The module that requested the import." }, + "chain": { "type": "array", "items": { "type": "string" }, "description": "Causal requester chain root -> ... -> requester." } + } + }, + "searched": { "type": "array", "items": { "type": "object", "additionalProperties": true }, "description": "Symbolic store queries attempted (named stores, not filesystem paths) — PII-free by construction." }, + "want": { "type": "string", "description": "Expected interface/ABI digest (InterfaceMismatch)." }, + "got": { "type": "string", "description": "Actual digest found (InterfaceMismatch)." }, + "hash": { "type": "string", "description": "Content hash of the offending image (SignatureInvalid/CorruptImage)." }, + "reason": { "type": "string", "description": "Signature-failure reason (SignatureInvalid)." }, + "candidate": { "type": "string", "description": "Symbolic store candidate rejected by policy (SandboxDenied)." }, + "rule": { "type": "string", "description": "Policy rule id that denied the load (SandboxDenied)." }, + "resourceKind": { "type": "string", "enum": ["VMEM", "FD", "MAPSLOTS"], "description": "Exhausted resource (ResourceExhausted)." }, + "limit": { "type": "integer", "description": "Resource limit hit (ResourceExhausted)." } + } +} diff --git a/schemas/ResilientDiagnosticRecord.json b/schemas/ResilientDiagnosticRecord.json new file mode 100644 index 0000000..216aa7b --- /dev/null +++ b/schemas/ResilientDiagnosticRecord.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/ResilientDiagnosticRecord.json", + "title": "ResilientDiagnosticRecord", + "description": "Root-cause-first crash/diagnostics record emitted by the resilient loader (RDR). The typed LoaderFault and its stable code are field #1, not buried in prose. Deterministic and diffable: identical faults produce byte-identical cores (modulo timestamps), so N occurrences of a defect collapse to one repro_key bucket. Privacy by projection (I8): the telemetry tier is an allowlist projection of the local record, never a scrub — path-like fields are represented symbolically (store names, hashes), so there is no user path to leak. This is the same fabric discipline as the reasoning-evidence receipts and the twin's reference-gated projection.", + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "specVersion", "schema", "process", "fault", "phase", "reproKey", "redaction", "capturedAt"], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:rdr:", "description": "Stable URN for this record." }, + "type": { "const": "ResilientDiagnosticRecord", "description": "Type discriminator; always 'ResilientDiagnosticRecord'." }, + "specVersion": { "type": "string", "description": "Semantic version of the contract." }, + "schema": { "const": "rdr/1", "description": "Versioned RDR schema tag." }, + "process": { + "type": "object", + "additionalProperties": false, + "required": ["id", "imageHash", "profile"], + "description": "The process that faulted. imageHash is content-addressed (no path).", + "properties": { + "id": { "type": "string" }, + "imageHash": { "type": "string" }, + "entry": { "type": "string" }, + "profile": { "type": "string", "enum": ["prod", "dev"] } + } + }, + "fault": { "$ref": "https://schemas.srcos.ai/v2/LoaderFault.json", "description": "The whole typed fault value — root cause as field #1." }, + "phase": { "type": "string", "enum": ["mapping", "seal-verify", "bind-core", "running-lazy"], "description": "Launch state machine phase at fault time." }, + "closure": { + "type": "object", + "additionalProperties": false, + "description": "Sealed-closure context.", + "properties": { + "coreSize": { "type": "integer", "description": "Size of the REQUIRED core closure." }, + "sealedManifestHash": { "type": "string", "description": "Hash of the Sealed Closure Manifest (SCM)." } + } + }, + "chain": { "type": "array", "items": { "type": "string" }, "description": "Causal requester chain root -> ... -> missing." }, + "env": { + "type": "object", + "additionalProperties": false, + "description": "Environment (allowlisted, symbolic — no paths).", + "properties": { + "osBuild": { "type": "string" }, + "model": { "type": "string" }, + "abiLevel": { "type": "integer" }, + "bootId": { "type": "string" } + } + }, + "redaction": { + "type": "object", + "additionalProperties": false, + "required": ["tier"], + "description": "Which projection this record is. telemetry = allowlist projection (fault code, interface name, versions, repro_key, os build, model) — PII-free by construction.", + "properties": { + "tier": { "type": "string", "enum": ["local", "telemetry"] }, + "saltId": { "type": "string" } + } + }, + "reproKey": { "type": "string", "description": "= H(fault.code || import.name || chain || image_hash). EXCLUDES timestamps/addresses/PIDs so identical defects coalesce to one bucket." }, + "capturedAt": { "type": "string", "format": "date-time", "description": "RFC3339 fault time (excluded from reproKey)." } + } +} diff --git a/tools/validate_rld_examples.py b/tools/validate_rld_examples.py new file mode 100644 index 0000000..d400c01 --- /dev/null +++ b/tools/validate_rld_examples.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Validate RLD LoaderFault / ResilientDiagnosticRecord examples + their invariants. + +Beyond JSON Schema (with a $ref registry so RDR can embed LoaderFault): + * URN prefix on the RDR; + * kind <-> code consistency (the stable-code table); + * severity is exactly what the binding class dictates (REQUIRED->fatal, FEATURE->degraded, + WEAK->info, LAZY->handled|fatal) — the classifier is a table, not a heuristic (I5/severity map); + * privacy by projection (I8): a telemetry-tier record contains NO filesystem-path-like value + (no '/Library/…', '/Users/…', or leading '/…') — paths are symbolic (store names, hashes); + * reproKey excludes the timestamp (does not equal capturedAt), so identical defects coalesce. +""" +from __future__ import annotations +import json, sys +from pathlib import Path +from jsonschema import Draft202012Validator, FormatChecker +from referencing import Registry, Resource + +FORMAT_CHECKER = FormatChecker() + +ROOT = Path(__file__).resolve().parents[1] +LF = json.loads((ROOT / "schemas/LoaderFault.json").read_text()) +RDR = json.loads((ROOT / "schemas/ResilientDiagnosticRecord.json").read_text()) +REG = Registry().with_resource(LF["$id"], Resource.from_contents(LF)) + +KIND_CODE = { + "MissingProvider": "LDR-DEP-MISSING", "VersionUnsatisfied": "LDR-DEP-VERSION", + "InterfaceMismatch": "LDR-ABI-MISMATCH", "SignatureInvalid": "LDR-SIG-INVALID", + "CorruptImage": "LDR-IMG-CORRUPT", "SandboxDenied": "LDR-SBX-DENY", + "ResourceExhausted": "LDR-RES-EXHAUST", +} +BINDING_SEV = {"REQUIRED": {"fatal"}, "FEATURE": {"degraded"}, "WEAK": {"info"}, "LAZY": {"handled", "fatal"}} + +PAIRS = [ + ("schemas/LoaderFault.json", "examples/loader_fault.json", LF, False), + ("schemas/ResilientDiagnosticRecord.json", "examples/resilient_diagnostic_record.json", RDR, True), + ("schemas/ResilientDiagnosticRecord.json", "examples/resilient_diagnostic_record_degraded.json", RDR, True), +] + + +def path_like(v) -> bool: + if isinstance(v, str): + return v.startswith("/") or "/Library/" in v or "/Users/" in v or v.startswith("file://") + if isinstance(v, dict): + return any(path_like(x) for x in v.values()) + if isinstance(v, list): + return any(path_like(x) for x in v) + return False + + +def check_fault(fault, ex_rel, errors): + k, c, sev = fault.get("kind"), fault.get("code"), fault.get("severity") + if KIND_CODE.get(k) != c: + errors.append(f"{ex_rel}: kind '{k}' must pair with code '{KIND_CODE.get(k)}', got '{c}'") + imp = fault.get("import") or {} + b = imp.get("binding") + if b and sev not in BINDING_SEV.get(b, set()): + errors.append(f"{ex_rel}: binding {b} requires severity in {sorted(BINDING_SEV[b])}, got '{sev}'") + + +def main() -> int: + errors: list[str] = [] + for schema_rel, ex_rel, schema, is_rdr in PAIRS: + doc = json.loads((ROOT / ex_rel).read_text()) + try: + Draft202012Validator(schema, registry=REG, format_checker=FORMAT_CHECKER).validate(doc) + except Exception as e: + errors.append(f"{ex_rel}: schema invalid: {str(e)[:140]}") + continue + if is_rdr: + if not str(doc.get("id", "")).startswith("urn:srcos:rdr:"): + errors.append(f"{ex_rel}: id must start with 'urn:srcos:rdr:'") + check_fault(doc.get("fault", {}), ex_rel, errors) + if doc.get("redaction", {}).get("tier") == "telemetry" and path_like(doc): + errors.append(f"{ex_rel}: telemetry-tier record contains a filesystem-path-like value (I8 projection violation)") + if doc.get("reproKey") and doc.get("reproKey") == doc.get("capturedAt"): + errors.append(f"{ex_rel}: reproKey must exclude the timestamp") + else: + check_fault(doc, ex_rel, errors) + if not any(e.startswith(ex_rel) for e in errors): + print(f"OK {ex_rel}") + if errors: + print("\nVALIDATION FAILED:") + for e in errors: + print(f" - {e}") + return 1 + print("\nAll RLD examples valid (schema + invariants).") + return 0 + + +if __name__ == "__main__": + sys.exit(main())