diff --git a/schemas/trace-claim.json b/schemas/trace-claim.json index 5f69e69..73f8805 100644 --- a/schemas/trace-claim.json +++ b/schemas/trace-claim.json @@ -6,7 +6,7 @@ "type": "object", "required": [ "eat_profile", "iat", "subject", "model", "runtime", "policy", - "data_class", "build_provenance", "appraisal", "transparency", "cnf" + "data_class", "build_provenance", "appraisal", "cnf" ], "properties": { "eat_profile": {"type": "string", "const": "tag:agentrust-io.com,2026:trace-v0.2"}, @@ -58,6 +58,21 @@ }, "additionalProperties": false }, + "origin": { + "type": "object", + "description": "Where imported evidence came from. A non-self origin cannot carry a hardware runtime platform.", + "required": ["kind", "producer"], + "properties": { + "kind": { + "type": "string", + "enum": ["self", "third-party-control-plane", "log-import"] + }, + "producer": {"type": "string", "minLength": 1}, + "source_event_id": {"type": "string", "minLength": 1}, + "ingested_at": {"type": "integer", "minimum": 1700000000} + }, + "additionalProperties": false + }, "build_provenance": { "type": "object", "required": ["slsa_level", "digest"], @@ -100,5 +115,27 @@ "additionalProperties": false } }, - "additionalProperties": false + "additionalProperties": false, + "allOf": [ + { + "if": { + "required": ["origin"], + "properties": { + "origin": { + "required": ["kind"], + "properties": { + "kind": {"enum": ["third-party-control-plane", "log-import"]} + } + } + } + }, + "then": { + "properties": { + "runtime": { + "properties": {"platform": {"const": "software-only"}} + } + } + } + } + ] } diff --git a/tests/conftest.py b/tests/conftest.py index 83c1bc5..c2ed74c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,11 @@ def load_vector(filename): return json.loads((VECTORS_DIR / filename).read_text()) +@pytest.fixture(name="load_vector") +def load_vector_fixture(): + return load_vector + + def load_schema(): return json.loads((SCHEMAS_DIR / "trace-claim.json").read_text()) @@ -26,7 +31,9 @@ def _canonical_json(d: dict) -> bytes: return json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode() -def _build_signed_cmcp_record(*, platform: str = "tpm2", nonce: str | None = None) -> tuple[dict, Ed25519PrivateKey]: +def _build_signed_cmcp_record( + *, platform: str = "tpm2", nonce: str | None = None +) -> tuple[dict, Ed25519PrivateKey]: """Return (record, private_key) for a fully-signed cmcp-runtime claim. The signature covers the canonical JSON of the envelope with the 'signature' diff --git a/tests/test_openshell_import.py b/tests/test_openshell_import.py new file mode 100644 index 0000000..688ae3b --- /dev/null +++ b/tests/test_openshell_import.py @@ -0,0 +1,43 @@ +"""Conformance coverage for records imported from an OpenShell control plane.""" + +from __future__ import annotations + +import copy +import time + +import jsonschema +import pytest + +from trace_tests.result import Status +from trace_tests.runner import run + + +def _failures(results): + return [ + finding + for findings in results.values() + for finding in findings + if finding.status is Status.FAIL + ] + + +def test_openshell_import_is_level0_conformant(schema, load_vector) -> None: + record = load_vector("valid_openshell_import.json") + record["iat"] = int(time.time()) + + jsonschema.validate(record, schema) + assert _failures(run(record, "trace", 0)) == [] + + +def test_imported_openshell_evidence_cannot_claim_hardware(schema, load_vector) -> None: + record = copy.deepcopy(load_vector("valid_openshell_import.json")) + record["runtime"]["platform"] = "intel-tdx" + + with pytest.raises(jsonschema.exceptions.ValidationError): + jsonschema.validate(record, schema) + + +def test_openshell_import_does_not_require_transparency_at_level0(schema, load_vector) -> None: + record = load_vector("valid_openshell_import.json") + assert "transparency" not in record + jsonschema.validate(record, schema) diff --git a/tests/vectors/valid_openshell_import.json b/tests/vectors/valid_openshell_import.json new file mode 100644 index 0000000..ab6ecfc --- /dev/null +++ b/tests/vectors/valid_openshell_import.json @@ -0,0 +1,43 @@ +{ + "eat_profile": "tag:agentrust-io.com,2026:trace-v0.2", + "iat": 1775014139, + "subject": "spiffe://example.org/agent/codex", + "model": { + "provider": "openai", + "model_id": "gpt-5" + }, + "runtime": { + "platform": "software-only", + "measurement": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "policy": { + "bundle_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "enforcement_mode": "enforce", + "version": "openshell-acs-v1" + }, + "data_class": "internal", + "tool_transcript": { + "hash": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "call_count": 1 + }, + "origin": { + "kind": "third-party-control-plane", + "producer": "nvidia-openshell/0.3.0", + "source_event_id": "sbx-123" + }, + "build_provenance": { + "slsa_level": 0, + "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + }, + "appraisal": { + "status": "none", + "verifier": "nvidia-openshell/0.3.0" + }, + "cnf": { + "jwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + } +}