diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..edb03f9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "@wasmagent/protocol", + "version": "0.1.5", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@wasmagent/protocol", + "version": "0.1.5", + "license": "Apache-2.0" + } + } +} diff --git a/schemas/aep/checkpoint-evidence.schema.json b/schemas/aep/checkpoint-evidence.schema.json new file mode 100644 index 0000000..30fcab3 --- /dev/null +++ b/schemas/aep/checkpoint-evidence.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://wasmagent.dev/schemas/aep/checkpoint-evidence.schema.json", + "title": "CheckpointEvidence", + "description": "Signed checkpoint and fork record — captures agent state at a checkpoint boundary, enabling provenance across forks and replay. Canonical source for wasmagent-js and wasmagent-train-replay.", + "type": "object", + "required": ["schema_version", "checkpoint_id", "parent_run_id", "state_digest", "created_at_ms"], + "properties": { + "schema_version": { "type": "string", "enum": ["checkpoint-evidence/v0.1"] }, + "checkpoint_id": { + "type": "string", + "description": "Unique identifier for this checkpoint." + }, + "parent_run_id": { + "type": "string", + "description": "The run_id of the originating AEP record this checkpoint was taken from." + }, + "fork_of": { + "type": ["string", "null"], + "description": "If this checkpoint is a fork, the checkpoint_id of the ancestor checkpoint; null for root checkpoints." + }, + "state_digest": { + "type": "string", + "description": "Cryptographic digest of the captured agent state at checkpoint time." + }, + "created_at_ms": { + "type": "number", + "description": "Epoch-millisecond timestamp when the checkpoint was created." + }, + "run_id": { + "type": "string", + "description": "The run_id assigned to the new run spawned from this checkpoint (if any)." + }, + "signature": { + "type": "object", + "description": "Optional signature over the checkpoint payload for tamper evidence.", + "required": ["alg", "key_id", "sig"], + "properties": { + "alg": { "type": "string" }, + "key_id": { "type": "string" }, + "sig": { "type": "string" }, + "bundle": { "type": "object" }, + "transparency_log_ref": { "type": "string" } + } + } + } +} diff --git a/schemas/index.json b/schemas/index.json index 800c5a4..38fa42c 100644 --- a/schemas/index.json +++ b/schemas/index.json @@ -70,6 +70,17 @@ "consumers": ["wasmagent-js", "trace-pipeline"], "summary": "One record per compliance run." }, + { + "id": "checkpoint-evidence", + "title": "CheckpointEvidence", + "path": "schemas/aep/checkpoint-evidence.schema.json", + "canonical_id": "https://wasmagent.dev/schemas/aep/checkpoint-evidence.schema.json", + "version": "checkpoint-evidence/v0.1", + "stability": "evolving", + "owners": ["wasmagent-protocol"], + "consumers": ["wasmagent-js", "wasmagent-train-replay"], + "summary": "Signed checkpoint and fork record — agent state at a checkpoint boundary for provenance across forks and replay." + }, { "id": "rollout-wire", "title": "Rollout Wire Format", diff --git a/tests/fixtures/invalid/checkpoint-evidence/example.json b/tests/fixtures/invalid/checkpoint-evidence/example.json new file mode 100644 index 0000000..028d505 --- /dev/null +++ b/tests/fixtures/invalid/checkpoint-evidence/example.json @@ -0,0 +1,7 @@ +{ + "schema_version": "checkpoint-evidence/v9.9", + "checkpoint_id": "cp-abc123", + "parent_run_id": "run-xyz789", + "state_digest": "sha256:a1b2c3d4", + "created_at_ms": 1737600000000 +} diff --git a/tests/fixtures/valid/checkpoint-evidence/example.json b/tests/fixtures/valid/checkpoint-evidence/example.json new file mode 100644 index 0000000..11d482e --- /dev/null +++ b/tests/fixtures/valid/checkpoint-evidence/example.json @@ -0,0 +1,13 @@ +{ + "schema_version": "checkpoint-evidence/v0.1", + "checkpoint_id": "cp-abc123", + "parent_run_id": "run-xyz789", + "fork_of": null, + "state_digest": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", + "created_at_ms": 1737600000000, + "signature": { + "alg": "ed25519", + "key_id": "k1", + "sig": "base64sig==" + } +} diff --git a/tests/test_conformance.py b/tests/test_conformance.py new file mode 100644 index 0000000..736ac10 --- /dev/null +++ b/tests/test_conformance.py @@ -0,0 +1,28 @@ +"""Pytest wrapper around the conformance harness. + +The canonical conformance logic lives in conformance.py (runnable as a +standalone script). This thin adapter re-exports its exit-code check +as a pytest-collectable test so that ``pytest tests/ -x -q`` discovers +and runs it. +""" +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + + +def test_conformance() -> None: + """Run tests/conformance.py and fail if it returns non-zero.""" + script = Path(__file__).resolve().parent / "conformance.py" + result = subprocess.run( + [sys.executable, str(script)], + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(result.stdout, file=sys.stdout) + print(result.stderr, file=sys.stderr) + assert result.returncode == 0, ( + f"conformance.py exited {result.returncode}" + )