From df9f47769da125bcb34957435adf6d0b2bc0078c Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Sat, 21 Mar 2026 14:01:39 -0400 Subject: [PATCH] [runtime] emit commons receipts with canonical execute entry Why: commons receipts must reflect the real runtime execution surface instead of payment-gated x402 semantics. Contract impact: commons receipt fields now use entry/verb/version/class and remove nested x402 blocks; commercial x402 semantics remain out of scope for this repo. --- README.md | 2 +- SECURITY.md | 2 +- docs/CONFIGURATION.md | 8 ++- docs/OPERATIONS.md | 2 +- runtime/tests/runtime-signing.test.mjs | 37 ++++++------ scripts/smoke.mjs | 2 +- server.mjs | 82 +++++++++++++------------- tests/fixtures/golden.public.pem | 2 +- tests/fixtures/golden.receipt.json | 15 +++-- tests/fixtures/make-golden.mjs | 5 +- tests/smoke.mjs | 7 ++- 11 files changed, 89 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index b0009d9..86db891 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Supported query flags: When `schema=1`, schema validation uses the receipt verb to compute a `v1.1.0` receipt schema URL under `SCHEMA_HOST`. -When a verb request omits `x402`, the runtime fabricates defaults from the live route version: `version: "1.1.0"` and `entry: "x402://agent.eth//v1.1.0"`. +When a commons verb request omits `execution`, the runtime fabricates receipt execution defaults from the live route version: `entry: "https://runtime.commandlayer.org/execute"`, `verb: ""`, `version: "1.1.0"`, and `class: "commons"`. Commercial/payment-aware flows may still use `x402://...` semantics outside this repo. When `VERIFY_SCHEMA_CACHED_ONLY=1` (the default), `/verify?schema=1` returns HTTP `202` with `validator_not_warmed_yet` if the validator for that verb has not been compiled yet. `POST /debug/prewarm` can queue validator warmup, and `GET /debug/validators` shows cache state. diff --git a/SECURITY.md b/SECURITY.md index 4ff018b..6d52fd9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -78,7 +78,7 @@ ENS-backed verification currently reads these TXT records directly from the sign The server does not implement `VERIFIER_ENS_NAME` or `ENS_SIGNER_TEXT_KEY`. -When schema verification is requested, the runtime resolves receipt schemas from the `v1.1.0` schema tree under `SCHEMA_HOST`. When a verb request omits `x402`, the runtime fabricates `version: "1.1.0"` and `entry: "x402://agent.eth//v1.1.0"` before signing. +When schema verification is requested, the runtime resolves receipt schemas from the `v1.1.0` schema tree under `SCHEMA_HOST`. When a commons verb request omits `execution`, the runtime fabricates `entry: "https://runtime.commandlayer.org/execute"`, the live `verb`, `version: "1.1.0"`, and `class: "commons"` before signing. ### Controls not implemented by the current server diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 51e6ee9..5f48672 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -84,10 +84,14 @@ Behavior: - the server converts accepted public-key inputs to SPKI PEM internally for verification. -If a verb request omits `x402`, the runtime fabricates default values with the live API version: +If a commons verb request omits `execution`, the runtime fabricates default receipt execution values with the live API version: +- `entry: "https://runtime.commandlayer.org/execute"` +- `verb: ""` - `version: "1.1.0"` -- `entry: "x402://agent.eth//v1.1.0"` +- `class: "commons"` + +Commercial/payment-aware flows may still use `x402:////v1.1.0` semantics outside this runtime repo. ### Startup behavior diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 8c159b7..3003334 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -30,7 +30,7 @@ The current server implements: It does not implement `GET /ready` or `GET /version`. -When callers omit `x402` on a verb request, the runtime fabricates `version: "1.1.0"` and `entry: "x402://agent.eth//v1.1.0"` before signing the receipt. +When callers omit `execution` on a commons verb request, the runtime fabricates `entry: "https://runtime.commandlayer.org/execute"`, the live `verb`, `version: "1.1.0"`, and `class: "commons"` before signing the receipt. Commercial/payment-aware flows may still use `x402://...` semantics outside this repo. ### Basic checks diff --git a/runtime/tests/runtime-signing.test.mjs b/runtime/tests/runtime-signing.test.mjs index 75dd202..7ffe52e 100644 --- a/runtime/tests/runtime-signing.test.mjs +++ b/runtime/tests/runtime-signing.test.mjs @@ -56,7 +56,7 @@ function unwrapReceiptResponse(payload) { async function createDescribeReceipt(base) { const body = { - x402: { verb: "describe", version: "1.1.0", entry: "x402://describeagent.eth/describe/v1.1.0" }, + execution: { verb: "describe", version: "1.1.0", entry: "https://runtime.commandlayer.org/execute", class: "commons" }, input: { subject: "t", detail_level: "short" }, trace: { provider: "test" }, }; @@ -81,18 +81,13 @@ async function startSchemaHost() { const receiptSchema = { $id: `http://127.0.0.1:${port}/schemas/v1.1.0/commons/describe/receipts/describe.receipt.schema.json`, type: "object", - required: ["status", "x402", "result", "metadata"], + required: ["status", "entry", "verb", "version", "result", "metadata"], properties: { status: { const: "success" }, - x402: { - type: "object", - required: ["verb", "version", "entry"], - properties: { - verb: { const: "describe" }, - version: { type: "string" }, - entry: { type: "string" }, - }, - }, + entry: { const: "https://runtime.commandlayer.org/execute" }, + verb: { const: "describe" }, + version: { type: "string" }, + class: { const: "commons" }, result: { type: "object", required: ["description", "bullets", "properties"], @@ -178,6 +173,11 @@ test("makeReceipt production path emits signed receipts with runtime kid and can assert.equal(receipt.metadata?.proof?.trace_id, response?.trace_id); assert.match(receipt.metadata?.receipt_id || "", /^clrcpt_[a-f0-9]{32}$/); assert.equal(receipt.metadata?.proof?.receipt_id, receipt.metadata?.receipt_id); + assert.equal(receipt.entry, "https://runtime.commandlayer.org/execute"); + assert.equal(receipt.verb, "describe"); + assert.equal(receipt.version, "1.1.0"); + assert.equal(receipt.class, "commons"); + assert.equal(receipt.x402, undefined); assert.equal(receipt.metadata?.proof?.alg, "ed25519-sha256"); assert.equal(receipt.metadata?.proof?.signer_id, "runtime.commandlayer.eth"); assert.equal(receipt.metadata?.proof?.kid, keys.kid); @@ -409,7 +409,7 @@ test("/verify surfaces transient timeout failures separately from cryptographic } }); -test("fabricated x402 defaults use v1.1.0", async () => { +test("fabricated commons execution defaults use canonical execute entry", async () => { const keys = makeKeys(); const srv = await startServer({ API_VERSION: "1.1.0", @@ -427,18 +427,20 @@ test("fabricated x402 defaults use v1.1.0", async () => { assert.equal(receiptResp.status, 200); const response = await receiptResp.json(); const { receipt } = unwrapReceiptResponse(response); - assert.deepEqual(receipt.x402, { + assert.deepEqual({ entry: receipt.entry, verb: receipt.verb, version: receipt.version, class: receipt.class }, { + entry: "https://runtime.commandlayer.org/execute", verb: "describe", version: "1.1.0", - entry: "x402://describeagent.eth/describe/v1.1.0", + class: "commons", }); + assert.equal(receipt.x402, undefined); } finally { await stop(srv.proc); } }); -test("full chain clean -> summarize -> classify verifies with schema using partial x402 defaults", { timeout: 20000 }, async () => { +test("full chain clean -> summarize -> classify verifies with schema using commons execution defaults", { timeout: 20000 }, async () => { const keys = makeKeys(); const srv = await startServer({ RECEIPT_SIGNING_PRIVATE_KEY_PEM_B64: keys.privatePemB64, @@ -459,7 +461,7 @@ test("full chain clean -> summarize -> classify verifies with schema using parti method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ - x402: { verb, version: "1.1.0" }, + execution: { verb, version: "1.1.0", class: "commons" }, input: { content }, ...(traceId ? { trace: { trace_id: traceId } } : {}), }), @@ -548,7 +550,8 @@ test("full chain clean -> summarize -> classify verifies with schema using parti assert.equal(classify?.steps?.[0]?.receipt?.metadata?.receipt_id, finalReceipt.metadata?.receipt_id); assert.equal(classify?.final_receipt?.metadata?.receipt_id, finalReceipt.metadata?.receipt_id); - assert.equal(finalReceipt.x402.entry, "x402://classifyagent.eth/classify/v1.1.0"); + assert.equal(finalReceipt.entry, "https://runtime.commandlayer.org/execute"); + assert.equal(finalReceipt.class, "commons"); let verifyAttempt = null; diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs index 069fd79..be18a6c 100644 --- a/scripts/smoke.mjs +++ b/scripts/smoke.mjs @@ -2,7 +2,7 @@ import process from "node:process"; const base = process.env.SMOKE_BASE_URL || `http://127.0.0.1:${process.env.PORT || 8080}`; const input = { - x402: { entry: "x402://describeagent.eth/describe/v1.1.0", verb: "describe", version: "1.1.0" }, + execution: { entry: "https://runtime.commandlayer.org/execute", verb: "describe", version: "1.1.0", class: "commons" }, input: { subject: "CommandLayer", detail_level: "short" }, }; diff --git a/server.mjs b/server.mjs index f06d7bb..41714a5 100644 --- a/server.mjs +++ b/server.mjs @@ -712,17 +712,16 @@ const BUILTIN_SHARED_SCHEMAS = { required: ["id", "role"], additionalProperties: true, }, - "/schemas/v1.1.0/_shared/x402.schema.json": { - $id: `${SCHEMA_HOST}/schemas/v1.1.0/_shared/x402.schema.json`, + "/schemas/v1.1.0/_shared/execution.schema.json": { + $id: `${SCHEMA_HOST}/schemas/v1.1.0/_shared/execution.schema.json`, type: "object", properties: { + entry: { type: "string" }, verb: { type: "string" }, version: { type: "string" }, - entry: { type: "string" }, - tenant: {}, - extras: { type: "object" }, + class: { type: "string" }, }, - required: ["verb", "version", "entry"], + required: ["entry", "verb", "version"], additionalProperties: true, }, "/schemas/v1.1.0/_shared/receipt.base.schema.json": { @@ -730,7 +729,10 @@ const BUILTIN_SHARED_SCHEMAS = { type: "object", properties: { status: { enum: ["success", "error"] }, - x402: { $ref: `${SCHEMA_HOST}/schemas/v1.1.0/_shared/x402.schema.json` }, + entry: { type: "string" }, + verb: { type: "string" }, + version: { type: "string" }, + class: { type: "string" }, metadata: { type: "object", properties: { @@ -765,7 +767,7 @@ const BUILTIN_SHARED_SCHEMAS = { additionalProperties: true, }, }, - required: ["status", "x402", "metadata"], + required: ["status", "entry", "verb", "version", "metadata"], additionalProperties: true, }, }; @@ -779,21 +781,15 @@ function builtinReceiptSchemaForVerb(verb) { allOf: [{ $ref: `${SCHEMA_HOST}/schemas/v1.1.0/_shared/receipt.base.schema.json` }], properties: { status: { enum: ["success", "error"] }, - x402: { - allOf: [{ $ref: `${SCHEMA_HOST}/schemas/v1.1.0/_shared/x402.schema.json` }], - properties: { - verb: { const: normalizedVerb }, - version: { const: API_VERSION }, - entry: { const: `x402://${normalizedVerb}agent.eth/${normalizedVerb}/v${API_VERSION}` }, - }, - required: ["verb", "version", "entry"], - additionalProperties: true, - }, + entry: { const: `${CANONICAL_BASE}/execute` }, + verb: { const: normalizedVerb }, + version: { const: API_VERSION }, + class: { const: "commons" }, }, if: { properties: { status: { const: "success" } }, required: ["status"] }, then: { required: ["result"] }, else: { required: ["error"] }, - required: ["status", "x402", "metadata"], + required: ["status", "entry", "verb", "version", "metadata"], additionalProperties: true, }; } @@ -925,7 +921,7 @@ async function getValidatorForVerb(verb, options = {}) { try { const shared = [ `${SCHEMA_HOST}/schemas/v1.1.0/_shared/receipt.base.schema.json`, - `${SCHEMA_HOST}/schemas/v1.1.0/_shared/x402.schema.json`, + `${SCHEMA_HOST}/schemas/v1.1.0/_shared/execution.schema.json`, `${SCHEMA_HOST}/schemas/v1.1.0/_shared/identity.schema.json`, ]; await Promise.all(shared.map((u) => fetchJsonWithTimeout(u, SCHEMA_FETCH_TIMEOUT_MS, options).catch(() => null))); @@ -1015,10 +1011,13 @@ function makeFlowReceiptId() { return `clrcpt_${crypto.randomUUID().replace(/-/g, "")}`; } -function makeReceipt({ x402, result, status = "success", error = null, traceId, receiptId }) { +function makeReceipt({ execution, result, status = "success", error = null, traceId, receiptId }) { let receipt = { status, - x402, + entry: execution.entry, + verb: execution.verb, + version: execution.version, + ...(execution.class ? { class: execution.class } : {}), ...(error ? { error } : {}), ...(status === "success" ? { result } : {}), metadata: { @@ -1091,18 +1090,19 @@ function wrapReceiptResponse(receipt, { trace = null, actor = null, delegation_r }; } -function normalizeX402Envelope(rawX402, verb) { +function normalizeExecutionEnvelope(rawExecution, verb) { const fallbackVerb = String(verb || "").trim(); - const x402 = rawX402 && typeof rawX402 === "object" ? { ...rawX402 } : {}; - const normalizedVerb = String(x402.verb || fallbackVerb).trim() || fallbackVerb; - const version = String(x402.version || API_VERSION).trim() || API_VERSION; - const entry = String(x402.entry || `x402://${normalizedVerb}agent.eth/${normalizedVerb}/v${version}`).trim(); + const execution = rawExecution && typeof rawExecution === "object" ? { ...rawExecution } : {}; + const normalizedVerb = String(execution.verb || fallbackVerb).trim() || fallbackVerb; + const version = String(execution.version || API_VERSION).trim() || API_VERSION; + const entry = String(execution.entry || `${CANONICAL_BASE}/execute`).trim(); + const executionClass = String(execution.class || "commons").trim() || "commons"; return { - ...x402, + entry, verb: normalizedVerb, version, - entry, + ...(executionClass ? { class: executionClass } : {}), }; } @@ -1540,8 +1540,8 @@ async function handleVerb(verb, req, res) { }; try { - const x402 = normalizeX402Envelope(req.body?.x402, verb); - warmValidatorForVerb(x402.verb); + const execution = normalizeExecutionEnvelope(req.body?.execution, verb); + warmValidatorForVerb(execution.verb); const callerTimeout = Number(req.body?.limits?.timeout_ms || req.body?.limits?.max_latency_ms || 0); const timeoutMs = Math.min(SERVER_MAX_HANDLER_MS, callerTimeout && callerTimeout > 0 ? callerTimeout : SERVER_MAX_HANDLER_MS); @@ -1559,15 +1559,15 @@ async function handleVerb(verb, req, res) { try { - const receipt = makeReceipt({ x402, result, status: "success", traceId, receiptId: makeFlowReceiptId() }); + const receipt = makeReceipt({ execution, result, status: "success", traceId, receiptId: makeFlowReceiptId() }); return res.json(wrapReceiptResponse(receipt, { trace, actor })); } catch (signErr) { return respondSigningError(res, signErr); } } catch (e) { - const x402 = normalizeX402Envelope(req.body?.x402, verb); - warmValidatorForVerb(x402.verb); + const execution = normalizeExecutionEnvelope(req.body?.execution, verb); + warmValidatorForVerb(execution.verb); const actor = req.body?.actor ? { id: String(req.body.actor), role: "user" } @@ -1585,7 +1585,7 @@ async function handleVerb(verb, req, res) { try { - const receipt = makeReceipt({ x402, status: "error", error: err, traceId, receiptId: makeFlowReceiptId() }); + const receipt = makeReceipt({ execution, status: "error", error: err, traceId, receiptId: makeFlowReceiptId() }); return res.status(500).json(wrapReceiptResponse(receipt, { trace, actor })); } catch (signErr) { return respondSigningError(res, signErr); @@ -1890,7 +1890,7 @@ app.post("/verify", async (req, res) => { ens_match: wantEns ? true : null, }, values: { - verb: receipt?.x402?.verb ?? null, + verb: receipt?.verb ?? null, signer_id: proof?.signer_id ?? null, pubkey_source: pubSrc, ens: ensExpect, @@ -1912,10 +1912,10 @@ app.post("/verify", async (req, res) => { if (wantSchema) { schemaOk = false; - const verb = String(receipt?.x402?.verb || "").trim(); + const verb = String(receipt?.verb || "").trim(); if (!verb) { - schemaErrors = [{ message: "missing receipt.x402.verb" }]; + schemaErrors = [{ message: "missing receipt.verb" }]; } else if (VERIFY_SCHEMA_CACHED_ONLY && !hasValidatorCached(verb)) { warmValidatorForVerb(verb); @@ -1928,7 +1928,7 @@ app.post("/verify", async (req, res) => { ens_match: wantEns ? true : null, }, values: { - verb: receipt?.x402?.verb ?? null, + verb: receipt?.verb ?? null, signer_id: proof?.signer_id ?? null, pubkey_source: pubSrc, claimed_hash: proof?.hash_sha256 ?? null, @@ -1975,7 +1975,7 @@ app.post("/verify", async (req, res) => { ens_match: wantEns ? true : null, }, values: { - verb: receipt?.x402?.verb ?? null, + verb: receipt?.verb ?? null, signer_id: proof?.signer_id ?? null, kid: proof?.kid ?? null, canonical_id: proofCanonical || null, @@ -2003,7 +2003,7 @@ app.post("/verify", async (req, res) => { want_schema: wantSchema, refresh, strict_kid: strictKid, - verb: receipt?.x402?.verb ?? null, + verb: receipt?.verb ?? null, signer_id: proof?.signer_id ?? null, }); } diff --git a/tests/fixtures/golden.public.pem b/tests/fixtures/golden.public.pem index ccecc90..fc48793 100644 --- a/tests/fixtures/golden.public.pem +++ b/tests/fixtures/golden.public.pem @@ -1,3 +1,3 @@ -----BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAIL4DH52qyRXv6DYEA253pjohH/l6Slr1cmtP/uJYGnQ= +MCowBQYDK2VwAyEAkHDJKuaiAEH2tToc1ImkQvpUJG9oosNAesbxGCKwnsI= -----END PUBLIC KEY----- diff --git a/tests/fixtures/golden.receipt.json b/tests/fixtures/golden.receipt.json index 1a48ab8..7ebf631 100644 --- a/tests/fixtures/golden.receipt.json +++ b/tests/fixtures/golden.receipt.json @@ -1,11 +1,10 @@ { "receipt": { "status": "success", - "x402": { - "verb": "describe", - "version": "1.0.0", - "entry": "x402://describeagent.eth/describe/v1.0.0" - }, + "entry": "https://runtime.commandlayer.org/execute", + "verb": "describe", + "version": "1.0.0", + "class": "commons", "result": { "description": "golden", "bullets": [ @@ -24,10 +23,10 @@ "canonical": "json.sorted_keys.v1", "signer_id": "runtime.commandlayer.eth", "kid": "v1", - "hash_sha256": "8deae11dc363a4d43164a5e6778cc09ca92079b739185f261c656bb4852b7d96", - "signature_b64": "l8rytZcttuB/McrYrN8/oHZH9ifVnP0teDa8fgWGGwtUq5h9i2wZdU1qW9J0+rseHwzgX1eFIA1AtPzjVkW5BQ==" + "hash_sha256": "9a308b575e08f54d39917b7b066430f317578cc6a2b44b2b5f21e7053d144881", + "signature_b64": "Q0PRM6QUyHDRt4jfEoyuQZHKSaJlkcHukKAF3z7lYvteL2YKqhizFqnSn4yo0CA+zbJjh383UD5H1D0XAa0oCg==" }, - "receipt_id": "8deae11dc363a4d43164a5e6778cc09ca92079b739185f261c656bb4852b7d96" + "receipt_id": "9a308b575e08f54d39917b7b066430f317578cc6a2b44b2b5f21e7053d144881" } }, "runtime_metadata": { diff --git a/tests/fixtures/make-golden.mjs b/tests/fixtures/make-golden.mjs index 748ab17..2256b60 100644 --- a/tests/fixtures/make-golden.mjs +++ b/tests/fixtures/make-golden.mjs @@ -19,7 +19,10 @@ const pubPem = wrapPem(pubDer.toString("base64"), "-----BEGIN PUBLIC KEY-----", const receiptUnsigned = { status: "success", - x402: { verb: "describe", version: "1.0.0", entry: "x402://describeagent.eth/describe/v1.0.0" }, + entry: "https://runtime.commandlayer.org/execute", + verb: "describe", + version: "1.0.0", + class: "commons", result: { description: "golden", bullets: ["a", "b", "c"], properties: { verb: "describe", version: "1.0.0" } }, metadata: { proof: { diff --git a/tests/smoke.mjs b/tests/smoke.mjs index f4fb416..b0a2802 100644 --- a/tests/smoke.mjs +++ b/tests/smoke.mjs @@ -192,7 +192,7 @@ async function main() { } const describeBody = { - x402: { verb: "describe", version: "1.1.0", entry: "x402://describeagent.eth/describe/v1.1.0" }, + execution: { verb: "describe", version: "1.1.0", entry: "https://runtime.commandlayer.org/execute", class: "commons" }, input: { subject: "smoke-test", detail_level: "short" }, trace: { provider: "smoke" }, }; @@ -201,6 +201,11 @@ async function main() { assert.equal(describe.status, 200, `describe failed: ${JSON.stringify(describe.json)}`); const { receipt, runtimeMetadata } = extractReceiptEnvelope(describe.json); assert.equal(receipt.status, "success", `describe receipt status must be success`); + assert.equal(receipt.entry, "https://runtime.commandlayer.org/execute", `commons receipt entry must use the canonical runtime execute URL`); + assert.equal(receipt.verb, "describe", `commons receipt verb must be describe`); + assert.equal(receipt.version, "1.1.0", `commons receipt version must be v1.1.0`); + assert.equal(receipt.class, "commons", `commons receipt class must be commons`); + assert.equal(receipt.x402, undefined, `commons receipts must not emit a nested x402 block`); assert.equal(runtimeMetadata?.trace?.provider, "runtime", `runtime_metadata.trace.provider must be runtime`); assert.equal(describe.json?.trace_id, runtimeMetadata?.trace?.trace_id, `top-level trace_id must match runtime metadata`); assert.ok(/^cltrace_[a-f0-9]{32}$/.test(String(describe.json?.trace_id || "")), `trace_id must use cltrace_ format`);