From 0c50f41a4c5a8d7f4accad58c5614e8587deb04d Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Fri, 22 May 2026 13:06:15 -0400 Subject: [PATCH] Add trust receipt trace metadata and extend proof schema --- .../_shared/proof.schema.json | 83 ++++++++++++++----- .../_shared/trace.schema.json | 36 ++++++++ .../approve/approve.receipt.schema.json | 3 + .../attest/attest.receipt.schema.json | 3 + .../authenticate.receipt.schema.json | 3 + .../authorize/authorize.receipt.schema.json | 3 + .../endorse/endorse.receipt.schema.json | 3 + .../grant/grant.receipt.schema.json | 3 + .../permit/permit.receipt.schema.json | 3 + .../reject/reject.receipt.schema.json | 3 + .../sign/sign.receipt.schema.json | 3 + .../verify/verify.receipt.schema.json | 3 + .../validate-trust-verification-examples.mjs | 44 ++++++++++ 13 files changed, 174 insertions(+), 19 deletions(-) create mode 100644 schemas/trust-verification/_shared/trace.schema.json diff --git a/schemas/trust-verification/_shared/proof.schema.json b/schemas/trust-verification/_shared/proof.schema.json index 3e188a8..5c1cba4 100644 --- a/schemas/trust-verification/_shared/proof.schema.json +++ b/schemas/trust-verification/_shared/proof.schema.json @@ -12,7 +12,10 @@ ], "properties": { "canonicalization": { - "const": "json.sorted_keys.v1" + "enum": [ + "json.sorted_keys.v1", + "erc8211.merkle.v1" + ] }, "hash": { "type": "object", @@ -32,26 +35,68 @@ } }, "signature": { - "type": "object", - "additionalProperties": false, - "required": [ - "alg", - "value", - "kid" - ], - "properties": { - "alg": { - "const": "Ed25519" - }, - "value": { - "type": "string", - "minLength": 16 + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "alg", + "value", + "kid" + ], + "properties": { + "alg": { + "const": "Ed25519" + }, + "value": { + "type": "string", + "minLength": 16 + }, + "kid": { + "type": "string", + "minLength": 1 + } + } }, - "kid": { - "type": "string", - "minLength": 1 + { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "alg", + "value", + "kid", + "role" + ], + "properties": { + "alg": { + "const": "Ed25519" + }, + "value": { + "type": "string", + "minLength": 16 + }, + "kid": { + "type": "string", + "minLength": 1 + }, + "role": { + "type": "string", + "enum": [ + "user", + "solver", + "relayer", + "agent", + "runtime", + "verifier" + ] + } + } + } } - } + ] } } } diff --git a/schemas/trust-verification/_shared/trace.schema.json b/schemas/trust-verification/_shared/trace.schema.json new file mode 100644 index 0000000..f06314f --- /dev/null +++ b/schemas/trust-verification/_shared/trace.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.commandlayer.org/schemas/v1.0.0/trust/_shared/trace.schema.json", + "title": "CLAS Trace", + "description": "Shared trace envelope for correlating requests and receipts across agents, hops, workflows, solver fills, and spans.", + "type": "object", + "additionalProperties": false, + "required": [ + "trace_id" + ], + "properties": { + "trace_id": { + "type": "string", + "maxLength": 128 + }, + "span_id": { + "type": "string", + "maxLength": 128 + }, + "parent_span_id": { + "type": "string", + "maxLength": 128 + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string", + "maxLength": 512 + } + } + } +} diff --git a/schemas/trust-verification/approve/approve.receipt.schema.json b/schemas/trust-verification/approve/approve.receipt.schema.json index ac744dd..a2f5029 100644 --- a/schemas/trust-verification/approve/approve.receipt.schema.json +++ b/schemas/trust-verification/approve/approve.receipt.schema.json @@ -73,6 +73,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/attest/attest.receipt.schema.json b/schemas/trust-verification/attest/attest.receipt.schema.json index 4b00234..687b2f0 100644 --- a/schemas/trust-verification/attest/attest.receipt.schema.json +++ b/schemas/trust-verification/attest/attest.receipt.schema.json @@ -73,6 +73,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/authenticate/authenticate.receipt.schema.json b/schemas/trust-verification/authenticate/authenticate.receipt.schema.json index 0bbf560..6e5407a 100644 --- a/schemas/trust-verification/authenticate/authenticate.receipt.schema.json +++ b/schemas/trust-verification/authenticate/authenticate.receipt.schema.json @@ -81,6 +81,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/authorize/authorize.receipt.schema.json b/schemas/trust-verification/authorize/authorize.receipt.schema.json index 160f62c..25c47ff 100644 --- a/schemas/trust-verification/authorize/authorize.receipt.schema.json +++ b/schemas/trust-verification/authorize/authorize.receipt.schema.json @@ -73,6 +73,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/endorse/endorse.receipt.schema.json b/schemas/trust-verification/endorse/endorse.receipt.schema.json index ea5bb1c..5bcc2d8 100644 --- a/schemas/trust-verification/endorse/endorse.receipt.schema.json +++ b/schemas/trust-verification/endorse/endorse.receipt.schema.json @@ -72,6 +72,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/grant/grant.receipt.schema.json b/schemas/trust-verification/grant/grant.receipt.schema.json index a599f54..609cc9b 100644 --- a/schemas/trust-verification/grant/grant.receipt.schema.json +++ b/schemas/trust-verification/grant/grant.receipt.schema.json @@ -73,6 +73,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/permit/permit.receipt.schema.json b/schemas/trust-verification/permit/permit.receipt.schema.json index 44a5511..4b01d67 100644 --- a/schemas/trust-verification/permit/permit.receipt.schema.json +++ b/schemas/trust-verification/permit/permit.receipt.schema.json @@ -73,6 +73,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/reject/reject.receipt.schema.json b/schemas/trust-verification/reject/reject.receipt.schema.json index e040905..010a0cc 100644 --- a/schemas/trust-verification/reject/reject.receipt.schema.json +++ b/schemas/trust-verification/reject/reject.receipt.schema.json @@ -68,6 +68,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/sign/sign.receipt.schema.json b/schemas/trust-verification/sign/sign.receipt.schema.json index 2639090..22301de 100644 --- a/schemas/trust-verification/sign/sign.receipt.schema.json +++ b/schemas/trust-verification/sign/sign.receipt.schema.json @@ -83,6 +83,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/schemas/trust-verification/verify/verify.receipt.schema.json b/schemas/trust-verification/verify/verify.receipt.schema.json index 93fb741..081cd95 100644 --- a/schemas/trust-verification/verify/verify.receipt.schema.json +++ b/schemas/trust-verification/verify/verify.receipt.schema.json @@ -88,6 +88,9 @@ "properties": { "proof": { "$ref": "../_shared/proof.schema.json" + }, + "trace": { + "$ref": "../_shared/trace.schema.json" } } } diff --git a/scripts/validate-trust-verification-examples.mjs b/scripts/validate-trust-verification-examples.mjs index 1d3f213..ab5fd77 100644 --- a/scripts/validate-trust-verification-examples.mjs +++ b/scripts/validate-trust-verification-examples.mjs @@ -67,15 +67,20 @@ for (const verb of entries) { const requestSchema = JSON.parse(fs.readFileSync(requestSchemaPath, 'utf8')); const receiptSchema = JSON.parse(fs.readFileSync(receiptSchemaPath, 'utf8')); const proofSchemaPath = path.join(baseDir, '_shared', 'proof.schema.json'); + const traceSchemaPath = path.join(baseDir, '_shared', 'trace.schema.json'); if (!fs.existsSync(proofSchemaPath)) fail(`[${verb}] Missing shared schema: ${proofSchemaPath}`); + if (!fs.existsSync(traceSchemaPath)) fail(`[${verb}] Missing shared schema: ${traceSchemaPath}`); requestSchema.$id ??= pathToFileURL(requestSchemaPath).href; receiptSchema.$id ??= pathToFileURL(receiptSchemaPath).href; const proofSchema = JSON.parse(fs.readFileSync(proofSchemaPath, 'utf8')); + const traceSchema = JSON.parse(fs.readFileSync(traceSchemaPath, 'utf8')); proofSchema.$id ??= pathToFileURL(proofSchemaPath).href; + traceSchema.$id ??= pathToFileURL(traceSchemaPath).href; + ajv.addSchema(traceSchema); ajv.addSchema(proofSchema); ajv.addSchema(requestSchema); ajv.addSchema(receiptSchema); @@ -83,9 +88,48 @@ for (const verb of entries) { const validateRequest = ajv.getSchema(requestSchema.$id) || ajv.compile(requestSchema); const validateReceipt = ajv.getSchema(receiptSchema.$id) || ajv.compile(receiptSchema); + const validateProof = ajv.getSchema(proofSchema.$id) || ajv.compile(proofSchema); + const metadataSchema = receiptSchema.properties?.metadata; + if (!metadataSchema) fail(`[${verb}] Receipt schema missing metadata property`); + const metadataSchemaResolved = JSON.parse(JSON.stringify(metadataSchema)); + if (metadataSchemaResolved.properties?.proof?.$ref) metadataSchemaResolved.properties.proof.$ref = proofSchema.$id; + if (metadataSchemaResolved.properties?.trace?.$ref) metadataSchemaResolved.properties.trace.$ref = traceSchema.$id; + const validateMetadata = ajv.compile(metadataSchemaResolved); + + const proofSingle = { + canonicalization: 'json.sorted_keys.v1', + hash: { alg: 'SHA-256', value: 'a'.repeat(64) }, + signature: { alg: 'Ed25519', value: 'signaturevalue1234', kid: 'key-1' }, + }; + const proofMerkle = { + canonicalization: 'erc8211.merkle.v1', + hash: { alg: 'SHA-256', value: 'b'.repeat(64) }, + signature: [ + { alg: 'Ed25519', value: 'signaturevalue1234', kid: 'key-1', role: 'solver' }, + ], + }; + + const schemaChecks = [ + { name: 'proof single-signature json.sorted_keys.v1', ok: validateProof(proofSingle) }, + { name: 'proof erc8211.merkle.v1 with signature role array', ok: validateProof(proofMerkle) }, + { name: 'metadata proof only', ok: validateMetadata({ proof: proofSingle }) }, + { name: 'metadata proof + trace', ok: validateMetadata({ proof: proofSingle, trace: { trace_id: 'trace-1', span_id: 'span-1' } }) }, + { name: 'metadata unknown extra field rejected', ok: !validateMetadata({ proof: proofSingle, extra: true }) }, + ]; + + let verbFailed = false; process.stdout.write(`\n[${verb}]\n`); + for (const check of schemaChecks) { + const status = check.ok ? 'PASS' : 'FAIL'; + process.stdout.write(` ${status} schema-check ${check.name}\n`); + if (!check.ok) { + verbFailed = true; + hasExpectationFailures = true; + } + } + for (const { file, schemaType, shouldPass } of EXPECTATIONS) { const targetPath = path.join(examplesDir, file); if (!fs.existsSync(targetPath)) fail(`[${verb}] Missing example file: ${targetPath}`);