diff --git a/Makefile b/Makefile index 904fe15..c417b57 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test validate validate-portable-ai validate-model-carry-boundary validate-model-carry-manifest dist release-dry-run clean +.PHONY: build test validate validate-portable-ai validate-model-carry-boundary validate-model-carry-manifest validate-confidential-compute-escalation dist release-dry-run clean BIN := sourceos-ai DIST_DIR := dist @@ -26,7 +26,10 @@ validate-model-carry-boundary: validate-model-carry-manifest: python3 tools/validate_model_carry_manifests.py -validate: build validate-portable-ai validate-model-carry-boundary validate-model-carry-manifest +validate-confidential-compute-escalation: + python3 tools/validate_confidential_compute_escalations.py + +validate: build validate-portable-ai validate-model-carry-boundary validate-model-carry-manifest validate-confidential-compute-escalation python3 tools/validate_carry_refs.py bin/$(BIN) carry validate --refs examples bin/$(BIN) carry list --refs examples diff --git a/contracts/confidential-compute-escalation.schema.json b/contracts/confidential-compute-escalation.schema.json new file mode 100644 index 0000000..dc06344 --- /dev/null +++ b/contracts/confidential-compute-escalation.schema.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/model-carry/confidential-compute-escalation.schema.json", + "title": "SourceOS Confidential Compute Escalation", + "description": "The tiered confidential-compute escalation contract (SourceOS-vs-Apple ModelCarry spec, section 5.2). Model execution escalates to a higher confidential-compute tier (e.g. on-device -> sealed enclave -> attested TEE) based on the data-residency / sensitivity class of the inputs. The escalation decision is governed and receipted: a request whose sensitivity class requires a minimum tier MUST run at or above that tier, and any tier that requires attestation MUST carry an attestation reference. Fail-closed is the core invariant: a request run below its required minimum tier is rejected, never silently downgraded. SHA-256 is the authoritative receipt-hash algorithm (FIPS 180-4).", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "kind", + "escalationId", + "invariants", + "computeTiers", + "sensitivityClassMap", + "decision" + ], + "properties": { + "schemaVersion": { "const": "v0.1" }, + "kind": { "const": "ConfidentialComputeEscalation" }, + "escalationId": { + "type": "string", + "pattern": "^urn:srcos:cc-escalation:", + "description": "SourceOS confidential-compute-escalation URN." + }, + "invariants": { + "type": "object", + "additionalProperties": false, + "required": [ + "belowMinimumTierIsRejected", + "unattestedTierRequiringAttestationIsRejected", + "tierOrderingMustBeMonotonic", + "receiptHashAlgorithm" + ], + "properties": { + "belowMinimumTierIsRejected": { + "const": true, + "description": "Fail-closed core invariant. A request run at a tier below the minimum required by its sensitivity class MUST be rejected, never silently downgraded to a weaker tier." + }, + "unattestedTierRequiringAttestationIsRejected": { + "const": true, + "description": "A decision that selects a tier which requires attestation MUST carry a non-null attestationRef; a missing attestation is a hard stop." + }, + "tierOrderingMustBeMonotonic": { + "const": true, + "description": "computeTiers MUST be ordered by strictly increasing rank. A non-monotonic ordering is non-conformant: the tier lattice must be totally ordered for a minimum-tier comparison to be meaningful." + }, + "receiptHashAlgorithm": { + "const": "sha256", + "description": "Authoritative receipt-hash algorithm. SHA-256 (FIPS 180-4) only." + } + } + }, + "computeTiers": { + "type": "array", + "minItems": 2, + "description": "The ordered confidential-compute tier lattice, weakest first. rank is strictly increasing in array order.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["tierRef", "tierClass", "rank", "requiresAttestation", "attestationKind"], + "properties": { + "tierRef": { "type": "string", "pattern": "^urn:srcos:cc-tier:" }, + "tierClass": { + "type": "string", + "enum": ["on_device", "sealed_enclave", "attested_tee"], + "description": "The confidential-compute posture of this tier." + }, + "rank": { + "type": "integer", + "minimum": 0, + "description": "Position in the tier lattice. Higher rank = stronger confidential-compute guarantee. Strictly increasing across computeTiers in array order." + }, + "requiresAttestation": { + "type": "boolean", + "description": "Whether selecting this tier requires a non-null attestationRef in the decision." + }, + "attestationKind": { + "type": ["string", "null"], + "enum": ["none", "enclave_measurement", "tee_remote_attestation", null], + "description": "The kind of attestation this tier's guarantee is anchored to. null/none for tiers that require no attestation." + } + } + } + }, + "sensitivityClassMap": { + "type": "array", + "minItems": 1, + "description": "Maps each data-sensitivity / residency class to the minimum confidential-compute tier permitted to process it.", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["sensitivityClass", "dataResidencyClass", "minimumTierRef"], + "properties": { + "sensitivityClass": { + "type": "string", + "enum": ["public", "internal", "confidential", "restricted"], + "description": "Sensitivity class of the inputs." + }, + "dataResidencyClass": { + "type": "string", + "enum": ["on_device_only", "sealed_compute_only", "attested_compute_only"], + "description": "Residency posture required for this class, aligned with the estate InferenceReceipt dataResidencyClass vocabulary." + }, + "minimumTierRef": { + "type": "string", + "pattern": "^urn:srcos:cc-tier:", + "description": "tierRef of the weakest tier permitted to process this class. A request in this class run below this tier is rejected." + } + } + } + }, + "decision": { + "type": "object", + "additionalProperties": false, + "required": [ + "requestSensitivityClass", + "chosenTierRef", + "reason", + "attestationRef", + "receiptSha256" + ], + "description": "The governed, receipted escalation decision record.", + "properties": { + "requestSensitivityClass": { + "type": "string", + "enum": ["public", "internal", "confidential", "restricted"], + "description": "Sensitivity class of the request being routed. MUST be present in sensitivityClassMap." + }, + "chosenTierRef": { + "type": "string", + "pattern": "^urn:srcos:cc-tier:", + "description": "tierRef of the tier the request was actually executed on." + }, + "reason": { + "type": "string", + "minLength": 1, + "description": "Human-readable justification for the escalation decision, propagated as first-class provenance." + }, + "attestationRef": { + "type": ["string", "null"], + "description": "Reference to the attestation evidence for the chosen tier. MUST be non-null when the chosen tier requiresAttestation." + }, + "receiptSha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "SHA-256 (FIPS 180-4) of the decision receipt. Content-addressed; never null." + } + } + }, + "notes": { "type": "string" } + } +} diff --git a/docs/confidential-compute-escalation.md b/docs/confidential-compute-escalation.md new file mode 100644 index 0000000..98751f2 --- /dev/null +++ b/docs/confidential-compute-escalation.md @@ -0,0 +1,64 @@ +# Confidential Compute Escalation + +The confidential-compute escalation contract governs how model execution escalates +to a higher confidential-compute tier based on the data-residency / sensitivity +class of the inputs. It is the SourceOS analogue of Apple's on-device to Private +Cloud Compute boundary (SourceOS-vs-Apple ModelCarry spec, section 5.2): where does +an over-scale or high-sensitivity request go, and under what attested-compute +guarantee? + +- Contract: `contracts/confidential-compute-escalation.schema.json` +- Validator: `tools/validate_confidential_compute_escalations.py` (`make validate-confidential-compute-escalation`) +- Example: `examples/confidential-compute-escalation.attested-tee.json` + +## The tier lattice + +`computeTiers` is an ordered, totally ordered lattice of confidential-compute +postures, weakest first: + +| tierClass | Guarantee | Attestation | +|---|---|---| +| `on_device` | Execution stays on the workstation. | none | +| `sealed_enclave` | Execution in a sealed enclave. | `enclave_measurement` | +| `attested_tee` | Execution in a remotely attested TEE. | `tee_remote_attestation` | + +Each request carries a sensitivity class (`public` / `internal` / `confidential` / +`restricted`) and a `dataResidencyClass` aligned with the estate InferenceReceipt +residency vocabulary (`on_device_only` / `sealed_compute_only` / +`attested_compute_only`). `sensitivityClassMap` binds each class to the weakest +tier permitted to process it. The `decision` records the tier actually chosen, the +reason, the attestation reference, and a SHA-256 receipt -- escalation is a +first-class, receipted provenance record, not an implicit routing side effect. + +## Invariants (enforced by the validator) + +| Invariant | Rule | +|---|---| +| Fail-closed | A request whose class requires a minimum tier and is run **below** that tier is **rejected**, never silently downgraded. This is the core invariant. | +| Attestation required | A chosen tier with `requiresAttestation: true` MUST carry a non-null `attestationRef`; a missing attestation is a hard stop. | +| Totally ordered lattice | `computeTiers` ranks are strictly increasing in array order; a non-monotonic ordering is rejected. | +| Declared class | The request's sensitivity class MUST appear in `sensitivityClassMap`; an unknown class is rejected, never defaulted to the weakest tier. | +| Authoritative receipt hash | `invariants.receiptHashAlgorithm` is `sha256` (FIPS 180-4); `decision.receiptSha256` is a SHA-256 digest. | + +## Teeth (negative fixtures) + +| Fixture | Rejected because | +|---|---| +| `confidential-compute-escalation.below-minimum-tier.invalid.json` | A `restricted`-class request (minimum: attested TEE, rank 2) was run on-device (rank 0). Fail-closed. | +| `confidential-compute-escalation.missing-attestation.invalid.json` | The chosen attested TEE tier requires attestation, but the decision has no `attestationRef`. | +| `confidential-compute-escalation.non-monotonic-tiers.invalid.json` | The tier lattice is not strictly monotonic by rank. | +| `confidential-compute-escalation.unknown-class.invalid.json` | The request's class is not declared in `sensitivityClassMap`. | + +## Carry boundary + +This contract is a decision/verification object. It does not perform live TEE +attestation, verify real enclave measurements, provision compute, or authorize +runtime execution. It proves that an escalation decision is well formed and +fail-closed before it is acted on. + +## Not yet in scope (tracked as follow-up) + +- Live TEE attestation verification (real enclave measurement / remote-attestation + quote validation) rather than a structural attestation reference. +- Governed staged pre-load plus atomic swap under a resource governor + (`sourceos-model-carry#21`). diff --git a/examples/confidential-compute-escalation.attested-tee.json b/examples/confidential-compute-escalation.attested-tee.json new file mode 100644 index 0000000..2f2e71d --- /dev/null +++ b/examples/confidential-compute-escalation.attested-tee.json @@ -0,0 +1,64 @@ +{ + "schemaVersion": "v0.1", + "kind": "ConfidentialComputeEscalation", + "escalationId": "urn:srcos:cc-escalation:office-assist-confidential", + "invariants": { + "belowMinimumTierIsRejected": true, + "unattestedTierRequiringAttestationIsRejected": true, + "tierOrderingMustBeMonotonic": true, + "receiptHashAlgorithm": "sha256" + }, + "computeTiers": [ + { + "tierRef": "urn:srcos:cc-tier:on-device", + "tierClass": "on_device", + "rank": 0, + "requiresAttestation": false, + "attestationKind": "none" + }, + { + "tierRef": "urn:srcos:cc-tier:sealed-enclave", + "tierClass": "sealed_enclave", + "rank": 1, + "requiresAttestation": true, + "attestationKind": "enclave_measurement" + }, + { + "tierRef": "urn:srcos:cc-tier:attested-tee", + "tierClass": "attested_tee", + "rank": 2, + "requiresAttestation": true, + "attestationKind": "tee_remote_attestation" + } + ], + "sensitivityClassMap": [ + { + "sensitivityClass": "public", + "dataResidencyClass": "on_device_only", + "minimumTierRef": "urn:srcos:cc-tier:on-device" + }, + { + "sensitivityClass": "internal", + "dataResidencyClass": "on_device_only", + "minimumTierRef": "urn:srcos:cc-tier:on-device" + }, + { + "sensitivityClass": "confidential", + "dataResidencyClass": "sealed_compute_only", + "minimumTierRef": "urn:srcos:cc-tier:sealed-enclave" + }, + { + "sensitivityClass": "restricted", + "dataResidencyClass": "attested_compute_only", + "minimumTierRef": "urn:srcos:cc-tier:attested-tee" + } + ], + "decision": { + "requestSensitivityClass": "restricted", + "chosenTierRef": "urn:srcos:cc-tier:attested-tee", + "reason": "Request carries restricted-class inputs (attested_compute_only residency); task exceeds on-device capability, escalated to the attested TEE tier which satisfies the minimum required tier.", + "attestationRef": "urn:srcos:attestation:tee:2026-08-03T00:00:00Z:a1b2c3", + "receiptSha256": "a05ede635e9b4a5fdc86444c5207d9d0cddd626d93e138311d6aa033bd8065ea" + }, + "notes": "Valid: a restricted-class request run at the attested TEE tier, at or above its required minimum, with an attestation reference for a tier that requires one. Escalation decision, reason, and receipt are first-class provenance." +} diff --git a/examples/confidential-compute-escalation.below-minimum-tier.invalid.json b/examples/confidential-compute-escalation.below-minimum-tier.invalid.json new file mode 100644 index 0000000..6412ef2 --- /dev/null +++ b/examples/confidential-compute-escalation.below-minimum-tier.invalid.json @@ -0,0 +1,54 @@ +{ + "schemaVersion": "v0.1", + "kind": "ConfidentialComputeEscalation", + "escalationId": "urn:srcos:cc-escalation:below-minimum-tier", + "invariants": { + "belowMinimumTierIsRejected": true, + "unattestedTierRequiringAttestationIsRejected": true, + "tierOrderingMustBeMonotonic": true, + "receiptHashAlgorithm": "sha256" + }, + "computeTiers": [ + { + "tierRef": "urn:srcos:cc-tier:on-device", + "tierClass": "on_device", + "rank": 0, + "requiresAttestation": false, + "attestationKind": "none" + }, + { + "tierRef": "urn:srcos:cc-tier:sealed-enclave", + "tierClass": "sealed_enclave", + "rank": 1, + "requiresAttestation": true, + "attestationKind": "enclave_measurement" + }, + { + "tierRef": "urn:srcos:cc-tier:attested-tee", + "tierClass": "attested_tee", + "rank": 2, + "requiresAttestation": true, + "attestationKind": "tee_remote_attestation" + } + ], + "sensitivityClassMap": [ + { + "sensitivityClass": "public", + "dataResidencyClass": "on_device_only", + "minimumTierRef": "urn:srcos:cc-tier:on-device" + }, + { + "sensitivityClass": "restricted", + "dataResidencyClass": "attested_compute_only", + "minimumTierRef": "urn:srcos:cc-tier:attested-tee" + } + ], + "decision": { + "requestSensitivityClass": "restricted", + "chosenTierRef": "urn:srcos:cc-tier:on-device", + "reason": "Routed restricted-class inputs to the on-device tier to save latency.", + "attestationRef": null, + "receiptSha256": "f86705ca895b2eaa669151564dd1506b3c897df0e9f69b566be9f11c80a7d7b6" + }, + "notes": "INVALID (core teeth): a restricted-class request whose required minimum tier is the attested TEE (rank 2) was run at the on-device tier (rank 0). Fail-closed: running below the required minimum tier is REJECTED, never silently downgraded." +} diff --git a/examples/confidential-compute-escalation.missing-attestation.invalid.json b/examples/confidential-compute-escalation.missing-attestation.invalid.json new file mode 100644 index 0000000..2e853e7 --- /dev/null +++ b/examples/confidential-compute-escalation.missing-attestation.invalid.json @@ -0,0 +1,54 @@ +{ + "schemaVersion": "v0.1", + "kind": "ConfidentialComputeEscalation", + "escalationId": "urn:srcos:cc-escalation:missing-attestation", + "invariants": { + "belowMinimumTierIsRejected": true, + "unattestedTierRequiringAttestationIsRejected": true, + "tierOrderingMustBeMonotonic": true, + "receiptHashAlgorithm": "sha256" + }, + "computeTiers": [ + { + "tierRef": "urn:srcos:cc-tier:on-device", + "tierClass": "on_device", + "rank": 0, + "requiresAttestation": false, + "attestationKind": "none" + }, + { + "tierRef": "urn:srcos:cc-tier:sealed-enclave", + "tierClass": "sealed_enclave", + "rank": 1, + "requiresAttestation": true, + "attestationKind": "enclave_measurement" + }, + { + "tierRef": "urn:srcos:cc-tier:attested-tee", + "tierClass": "attested_tee", + "rank": 2, + "requiresAttestation": true, + "attestationKind": "tee_remote_attestation" + } + ], + "sensitivityClassMap": [ + { + "sensitivityClass": "public", + "dataResidencyClass": "on_device_only", + "minimumTierRef": "urn:srcos:cc-tier:on-device" + }, + { + "sensitivityClass": "restricted", + "dataResidencyClass": "attested_compute_only", + "minimumTierRef": "urn:srcos:cc-tier:attested-tee" + } + ], + "decision": { + "requestSensitivityClass": "restricted", + "chosenTierRef": "urn:srcos:cc-tier:attested-tee", + "reason": "Escalated restricted-class inputs to the attested TEE tier.", + "attestationRef": null, + "receiptSha256": "161e8aab373e326d177a3bf572f3e8c7218973890214682926c0c8ae106e2f4c" + }, + "notes": "INVALID: the chosen attested TEE tier requiresAttestation, but the decision carries no attestationRef. A tier whose guarantee is anchored to attestation cannot be selected without an attestation reference -- REJECTED." +} diff --git a/examples/confidential-compute-escalation.non-monotonic-tiers.invalid.json b/examples/confidential-compute-escalation.non-monotonic-tiers.invalid.json new file mode 100644 index 0000000..b0231dd --- /dev/null +++ b/examples/confidential-compute-escalation.non-monotonic-tiers.invalid.json @@ -0,0 +1,54 @@ +{ + "schemaVersion": "v0.1", + "kind": "ConfidentialComputeEscalation", + "escalationId": "urn:srcos:cc-escalation:non-monotonic-tiers", + "invariants": { + "belowMinimumTierIsRejected": true, + "unattestedTierRequiringAttestationIsRejected": true, + "tierOrderingMustBeMonotonic": true, + "receiptHashAlgorithm": "sha256" + }, + "computeTiers": [ + { + "tierRef": "urn:srcos:cc-tier:on-device", + "tierClass": "on_device", + "rank": 0, + "requiresAttestation": false, + "attestationKind": "none" + }, + { + "tierRef": "urn:srcos:cc-tier:sealed-enclave", + "tierClass": "sealed_enclave", + "rank": 2, + "requiresAttestation": true, + "attestationKind": "enclave_measurement" + }, + { + "tierRef": "urn:srcos:cc-tier:attested-tee", + "tierClass": "attested_tee", + "rank": 1, + "requiresAttestation": true, + "attestationKind": "tee_remote_attestation" + } + ], + "sensitivityClassMap": [ + { + "sensitivityClass": "public", + "dataResidencyClass": "on_device_only", + "minimumTierRef": "urn:srcos:cc-tier:on-device" + }, + { + "sensitivityClass": "confidential", + "dataResidencyClass": "sealed_compute_only", + "minimumTierRef": "urn:srcos:cc-tier:sealed-enclave" + } + ], + "decision": { + "requestSensitivityClass": "confidential", + "chosenTierRef": "urn:srcos:cc-tier:sealed-enclave", + "reason": "Escalated confidential-class inputs to the sealed enclave tier.", + "attestationRef": "urn:srcos:attestation:enclave:2026-08-03T00:00:00Z:d4e5f6", + "receiptSha256": "9f3132746c03bcc641389e98f423b28e88aaf43b4d357debfdcc0a654448bf8f" + }, + "notes": "INVALID: the compute-tier lattice is not strictly monotonic (attested TEE has rank 1, below the sealed enclave's rank 2, though it appears after it). A non-monotonic ordering makes the minimum-tier comparison meaningless -- REJECTED." +} diff --git a/examples/confidential-compute-escalation.unknown-class.invalid.json b/examples/confidential-compute-escalation.unknown-class.invalid.json new file mode 100644 index 0000000..458b913 --- /dev/null +++ b/examples/confidential-compute-escalation.unknown-class.invalid.json @@ -0,0 +1,42 @@ +{ + "schemaVersion": "v0.1", + "kind": "ConfidentialComputeEscalation", + "escalationId": "urn:srcos:cc-escalation:unknown-class", + "invariants": { + "belowMinimumTierIsRejected": true, + "unattestedTierRequiringAttestationIsRejected": true, + "tierOrderingMustBeMonotonic": true, + "receiptHashAlgorithm": "sha256" + }, + "computeTiers": [ + { + "tierRef": "urn:srcos:cc-tier:on-device", + "tierClass": "on_device", + "rank": 0, + "requiresAttestation": false, + "attestationKind": "none" + }, + { + "tierRef": "urn:srcos:cc-tier:attested-tee", + "tierClass": "attested_tee", + "rank": 1, + "requiresAttestation": true, + "attestationKind": "tee_remote_attestation" + } + ], + "sensitivityClassMap": [ + { + "sensitivityClass": "public", + "dataResidencyClass": "on_device_only", + "minimumTierRef": "urn:srcos:cc-tier:on-device" + } + ], + "decision": { + "requestSensitivityClass": "restricted", + "chosenTierRef": "urn:srcos:cc-tier:attested-tee", + "reason": "Escalated a request whose sensitivity class is not declared in the map.", + "attestationRef": "urn:srcos:attestation:tee:2026-08-03T00:00:00Z:aa11bb", + "receiptSha256": "a000273d28036365ab9512054dc864cf85b2d7ee63365615c7ec19935371291b" + }, + "notes": "INVALID: the request's sensitivity class (restricted) is not declared in sensitivityClassMap. Fail-closed: an undeclared class has no known minimum tier and is REJECTED, never defaulted to the weakest tier." +} diff --git a/tools/validate_confidential_compute_escalations.py b/tools/validate_confidential_compute_escalations.py new file mode 100644 index 0000000..e56797e --- /dev/null +++ b/tools/validate_confidential_compute_escalations.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Validate SourceOS ConfidentialComputeEscalation examples. + +Enforces the tiered confidential-compute escalation invariants (SourceOS-vs-Apple +ModelCarry spec, section 5.2) that the model-router relies on: + + 1. SHA-256 is the authoritative receipt-hash algorithm (FIPS 180-4). + 2. The compute-tier lattice is totally ordered: ranks are strictly increasing in + array order. A non-monotonic ordering makes a minimum-tier comparison + meaningless and is rejected. + 3. Fail-closed (core invariant): a request whose sensitivity class requires a + minimum tier MUST run at or above that tier. A decision that runs below the + required minimum is rejected, never silently downgraded. + 4. A chosen tier that requiresAttestation MUST carry a non-null attestationRef; + a missing attestation is a hard stop. + 5. The request's sensitivity class MUST be declared in sensitivityClassMap; an + unknown class is rejected rather than defaulted to the weakest tier. + +Boundary: this checker is structural. It does not perform live TEE attestation, +verify real enclave measurements, or authorize runtime execution. Live attestation +and governed staged pre-load are tracked as follow-up issues. +""" + +from __future__ import annotations + +import json +import re +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = ROOT / "contracts" / "confidential-compute-escalation.schema.json" +VALID = ROOT / "examples" / "confidential-compute-escalation.attested-tee.json" +INVALID_BELOW_MIN = ROOT / "examples" / "confidential-compute-escalation.below-minimum-tier.invalid.json" +INVALID_NO_ATTEST = ROOT / "examples" / "confidential-compute-escalation.missing-attestation.invalid.json" +INVALID_NON_MONOTONIC = ROOT / "examples" / "confidential-compute-escalation.non-monotonic-tiers.invalid.json" +INVALID_UNKNOWN_CLASS = ROOT / "examples" / "confidential-compute-escalation.unknown-class.invalid.json" + +SHA256_RE = re.compile(r"^[a-f0-9]{64}$") + + +class ValidationError(Exception): + pass + + +def load_json(path: Path) -> dict[str, Any]: + payload = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(payload, dict): + raise ValidationError(f"{path.name}: expected JSON object") + return payload + + +def require(condition: bool, message: str) -> None: + if not condition: + raise ValidationError(message) + + +def is_sha256(value: Any) -> bool: + return isinstance(value, str) and bool(SHA256_RE.match(value)) + + +def validate_schema(schema: dict[str, Any]) -> None: + require(schema.get("$schema") == "https://json-schema.org/draft/2020-12/schema", "schema draft mismatch") + require(schema.get("type") == "object", "schema must describe object") + require(schema.get("additionalProperties") is False, "schema must be closed") + + +def validate_escalation(name: str, record: dict[str, Any]) -> None: + require(record.get("schemaVersion") == "v0.1", f"{name}: schemaVersion must be v0.1") + require(record.get("kind") == "ConfidentialComputeEscalation", f"{name}: kind must be ConfidentialComputeEscalation") + require(str(record.get("escalationId", "")).startswith("urn:srcos:cc-escalation:"), + f"{name}: escalationId must be a SourceOS cc-escalation URN") + + inv = record.get("invariants", {}) + require(inv.get("belowMinimumTierIsRejected") is True, + f"{name}: belowMinimumTierIsRejected must be true (fail-closed core invariant)") + require(inv.get("unattestedTierRequiringAttestationIsRejected") is True, + f"{name}: unattestedTierRequiringAttestationIsRejected must be true") + require(inv.get("tierOrderingMustBeMonotonic") is True, + f"{name}: tierOrderingMustBeMonotonic must be true") + require(inv.get("receiptHashAlgorithm") == "sha256", + f"{name}: receiptHashAlgorithm must be sha256 (FIPS 180-4 authoritative)") + + # --- Compute-tier lattice: totally ordered, strictly increasing rank --- + tiers = record.get("computeTiers", []) + require(isinstance(tiers, list) and len(tiers) >= 2, f"{name}: computeTiers must be an array of at least 2 tiers") + + by_ref: dict[str, dict[str, Any]] = {} + prev_rank: int | None = None + for tier in tiers: + tier_ref = tier.get("tierRef", "") + require(str(tier_ref).startswith("urn:srcos:cc-tier:"), f"{name}: tierRef must be a SourceOS cc-tier URN") + require(tier_ref not in by_ref, f"{name}: duplicate tierRef {tier_ref}") + rank = tier.get("rank") + require(isinstance(rank, int) and rank >= 0, f"{name}: {tier_ref} rank must be an integer >= 0") + # Strict monotonicity in array order: the tier lattice must be totally ordered. + require(prev_rank is None or rank > prev_rank, + f"{name}: computeTiers not strictly monotonic by rank at {tier_ref} " + f"(rank {rank} does not exceed previous {prev_rank})") + prev_rank = rank + require(isinstance(tier.get("requiresAttestation"), bool), + f"{name}: {tier_ref} requiresAttestation must be a boolean") + by_ref[tier_ref] = tier + + # --- Sensitivity/residency class -> minimum-tier map references real tiers --- + class_map = record.get("sensitivityClassMap", []) + require(isinstance(class_map, list) and class_map, f"{name}: sensitivityClassMap must be a non-empty array") + min_tier_by_class: dict[str, str] = {} + for m in class_map: + cls = m.get("sensitivityClass") + min_ref = m.get("minimumTierRef") + require(min_ref in by_ref, f"{name}: sensitivityClassMap references unknown tier {min_ref}") + require(cls not in min_tier_by_class, f"{name}: duplicate sensitivityClass {cls} in map") + min_tier_by_class[cls] = min_ref + + # --- The governed, receipted decision --- + decision = record.get("decision", {}) + req_class = decision.get("requestSensitivityClass") + chosen_ref = decision.get("chosenTierRef") + + # Unknown class is rejected, not defaulted (fail-closed). + require(req_class in min_tier_by_class, + f"{name}: requestSensitivityClass {req_class} not declared in sensitivityClassMap (fail-closed)") + require(chosen_ref in by_ref, f"{name}: decision chosenTierRef references unknown tier {chosen_ref}") + require(is_sha256(decision.get("receiptSha256")), f"{name}: decision receiptSha256 must be a SHA-256 digest") + + chosen = by_ref[chosen_ref] + required_min = by_ref[min_tier_by_class[req_class]] + + # Core invariant: fail-closed. Chosen tier must be at or above the required minimum. + require(chosen["rank"] >= required_min["rank"], + f"{name}: below-minimum tier: class {req_class} requires tier rank >= {required_min['rank']} " + f"({required_min['tierRef']}) but ran at rank {chosen['rank']} ({chosen_ref}) -- REJECTED (fail-closed)") + + # A chosen tier that requires attestation must carry an attestation reference. + if chosen.get("requiresAttestation"): + att = decision.get("attestationRef") + require(isinstance(att, str) and att.strip() != "", + f"{name}: chosen tier {chosen_ref} requiresAttestation but decision has no attestationRef -- REJECTED") + + +def expect_invalid(path: Path) -> None: + try: + validate_escalation(path.name, load_json(path)) + except ValidationError: + return + raise ValidationError(f"invalid fixture unexpectedly validated: {path.name}") + + +def main() -> int: + try: + validate_schema(load_json(SCHEMA)) + validate_escalation(VALID.name, load_json(VALID)) + expect_invalid(INVALID_BELOW_MIN) + expect_invalid(INVALID_NO_ATTEST) + expect_invalid(INVALID_NON_MONOTONIC) + expect_invalid(INVALID_UNKNOWN_CLASS) + except (OSError, json.JSONDecodeError, ValidationError) as exc: + print(f"ERR: {exc}") + return 1 + print("Confidential compute escalation validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())