From 77cffa800b08f23ee72d93fe8f03a94a7751f54b Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Sat, 21 Mar 2026 23:35:26 -0400 Subject: [PATCH] [runtime] align commons surfaces to v1.1.0 current line Why: bring the runtime layer back into sync with the current CommandLayer Commons v1.1.0 contract and remove stale commons/payment drift. Contract impact: commons receipts, docs, fixtures, and package metadata now consistently present the v1.1.0 current-line shape; no new protocol behavior introduced. --- CHANGELOG.md | 5 +- README.md | 2 +- docs/CONFIGURATION.md | 4 +- docs/OPERATIONS.md | 2 +- package-lock.json | 6 +- package.json | 2 +- runtime/tests/runtime-signing.test.mjs | 39 ++++++++++++ server.mjs | 17 ++--- tests/fixtures/golden.public.pem | 2 +- tests/fixtures/golden.receipt.json | 87 +++++++++++++++++++++----- tests/fixtures/make-golden.mjs | 32 +++++----- tests/golden.mjs | 24 ++++--- tests/smoke.mjs | 2 - 13 files changed, 158 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbf969..8f59087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ All notable changes to this runtime repository will be documented in this file. ## Unreleased -- Adds production-surface tests for receipt signing and `POST /verify` behavior in `server.mjs`. -- Removes the in-repo `sdk/` subtree so this repository stays scoped to the runtime service layer. +- Aligns the runtime service, docs, examples, and package metadata on the CommandLayer Commons v1.1.0 current line. +- Removes Commons runtime dependence on inbound `x402` request metadata so public Commons responses remain payment-agnostic. +- Refreshes the golden receipt fixture and production-surface tests to match current wrapped receipt responses and verification expectations. ## v1.0.0 diff --git a/README.md b/README.md index 86db891..a078cde 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 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 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 behavior belongs in the separate commercial runtime and is intentionally out of scope here. 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/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 5f48672..5111303 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -9,7 +9,7 @@ This file documents environment variables that are actually read by `server.mjs` | `HOST` | `0.0.0.0` | HTTP bind host. | | `PORT` | `8080` | HTTP listen port. | | `SERVICE_NAME` | `commandlayer-runtime` | Returned by `GET /` and `GET /health`. | -| `SERVICE_VERSION` | `1.0.0` | Returned by `GET /` and `GET /health`. | +| `SERVICE_VERSION` | `1.1.0` | Returned by `GET /` and `GET /health`. | | `API_VERSION` | `1.1.0` | Version segment used when mounting verb routes. | | `CANONICAL_BASE_URL` | `https://runtime.commandlayer.org` | Returned by `GET /` and `GET /health`. | | `RAILWAY_SERVICE_NAME` | unset | Used only in runtime trace/debug metadata; does not rename the service fields above. | @@ -91,7 +91,7 @@ If a commons verb request omits `execution`, the runtime fabricates default rece - `version: "1.1.0"` - `class: "commons"` -Commercial/payment-aware flows may still use `x402:////v1.1.0` semantics outside this runtime repo. +Commercial/payment-aware behavior belongs in the separate commercial runtime and is intentionally out of scope here. ### Startup behavior diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 3003334..12716e1 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 `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. +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 behavior belongs in the separate commercial runtime and is intentionally out of scope here. ### Basic checks diff --git a/package-lock.json b/package-lock.json index 0871975..c0fee2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@commandlayer/runtime", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@commandlayer/runtime", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "@commandlayer/runtime-core": "github:commandlayer/runtime-core#main", @@ -26,7 +26,7 @@ "license": "MIT" }, "node_modules/@commandlayer/runtime-core": { - "version": "1.0.0", + "version": "1.1.0", "resolved": "git+ssh://git@github.com/commandlayer/runtime-core.git#33d83ba4d1f2cf5838332811c931a97e3f3047d7", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8db0cfa..40e1c07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@commandlayer/runtime", - "version": "1.0.0", + "version": "1.1.0", "description": "Reference Node.js runtime for CommandLayer Commons verbs — deterministic execution, Ed25519-signed receipts, and ENS-based verification.", "private": true, "type": "module", diff --git a/runtime/tests/runtime-signing.test.mjs b/runtime/tests/runtime-signing.test.mjs index ec36634..0b1219b 100644 --- a/runtime/tests/runtime-signing.test.mjs +++ b/runtime/tests/runtime-signing.test.mjs @@ -576,6 +576,45 @@ test("/verify surfaces transient timeout failures separately from cryptographic } }); +test("commons runtime ignores inbound x402 request metadata when building receipts", async () => { + const keys = makeKeys(); + const srv = await startServer({ + RECEIPT_SIGNING_PRIVATE_KEY_PEM_B64: keys.privatePemB64, + RECEIPT_SIGNING_PUBLIC_KEY_B64: keys.publicRaw32B64, + RECEIPT_SIGNER_ID: "runtime.commandlayer.eth", + }); + + try { + const resp = await fetch(`${srv.base}/describe/v1.1.0`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + input: { subject: "ignore x402", detail_level: "short" }, + x402: { + tenant: "tenant-123", + extras: { + trace_id: "external-trace-id", + parent_trace_id: "external-parent-trace-id" + } + } + }), + }); + const json = await resp.json(); + const { receipt, runtimeMetadata } = unwrapReceiptResponse(json); + + assert.equal(resp.status, 200); + assert.match(String(json.trace_id || ""), /^cltrace_[a-f0-9]{32}$/); + assert.notEqual(json.trace_id, "external-trace-id"); + assert.equal(runtimeMetadata?.actor, undefined); + assert.equal(runtimeMetadata?.trace?.parent_trace_id, undefined); + assert.equal(receipt.metadata?.trace_id, json.trace_id); + assert.equal(receipt.metadata?.proof?.trace_id, json.trace_id); + assert.equal(receipt.x402, undefined); + } finally { + await stop(srv.proc); + } +}); + test("fabricated commons execution defaults use canonical execute entry", async () => { const keys = makeKeys(); const srv = await startServer({ diff --git a/server.mjs b/server.mjs index 277e8a8..6197c4a 100644 --- a/server.mjs +++ b/server.mjs @@ -1527,10 +1527,9 @@ async function handleVerb(verb, req, res) { } if (!requireBody(req, res)) return; - const rawParent = req.body?.trace?.parent_trace_id ?? req.body?.x402?.extras?.parent_trace_id ?? null; + const rawParent = req.body?.trace?.parent_trace_id ?? null; const parentTraceId = typeof rawParent === "string" && rawParent.trim().length ? rawParent.trim() : null; - const rawTraceId = - req.body?.trace_id ?? req.body?.trace?.trace_id ?? req.body?.x402?.extras?.trace_id ?? req.body?.metadata?.trace_id ?? null; + const rawTraceId = req.body?.trace_id ?? req.body?.trace?.trace_id ?? req.body?.metadata?.trace_id ?? null; const traceId = typeof rawTraceId === "string" && rawTraceId.trim().length ? rawTraceId.trim() : makeTraceId(); const trace = { @@ -1551,11 +1550,7 @@ async function handleVerb(verb, req, res) { ? await Promise.race([work, new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), timeoutMs))]) : await work; - const actor = req.body?.actor - ? { id: String(req.body.actor), role: "user" } - : req.body?.x402?.tenant - ? { id: String(req.body.x402.tenant), role: "tenant" } - : null; + const actor = req.body?.actor ? { id: String(req.body.actor), role: "user" } : null; try { @@ -1569,11 +1564,7 @@ async function handleVerb(verb, req, res) { const execution = normalizeExecutionEnvelope(req.body?.execution ?? req.body, verb); warmValidatorForVerb(execution.verb); - const actor = req.body?.actor - ? { id: String(req.body.actor), role: "user" } - : req.body?.x402?.tenant - ? { id: String(req.body.x402.tenant), role: "tenant" } - : null; + const actor = req.body?.actor ? { id: String(req.body.actor), role: "user" } : null; const err = { code: String(e?.code || "INTERNAL_ERROR"), diff --git a/tests/fixtures/golden.public.pem b/tests/fixtures/golden.public.pem index fc48793..8395b29 100644 --- a/tests/fixtures/golden.public.pem +++ b/tests/fixtures/golden.public.pem @@ -1,3 +1,3 @@ -----BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAkHDJKuaiAEH2tToc1ImkQvpUJG9oosNAesbxGCKwnsI= +MCowBQYDK2VwAyEA3UYFLCzQGmaf074A7JrPM9CyapG4tCVYf5g+XNxPEfw= -----END PUBLIC KEY----- diff --git a/tests/fixtures/golden.receipt.json b/tests/fixtures/golden.receipt.json index 7ebf631..9efe0e2 100644 --- a/tests/fixtures/golden.receipt.json +++ b/tests/fixtures/golden.receipt.json @@ -1,21 +1,74 @@ { + "trace_id": "cltrace_00000000000000000000000000000000", + "steps": [ + { + "step": 1, + "receipt": { + "status": "success", + "entry": "https://runtime.commandlayer.org/execute", + "verb": "describe", + "version": "1.1.0", + "class": "commons", + "result": { + "description": "golden", + "bullets": ["a", "b", "c"], + "properties": { "verb": "describe", "version": "1.1.0" } + }, + "metadata": { + "proof": { + "alg": "ed25519-sha256", + "canonical": "json.sorted_keys.v1", + "signer_id": "runtime.commandlayer.eth", + "kid": "v1", + "hash_sha256": "c307194bc72e58d85d934842e9b9e635b574dd9dcb46001b9865365db359deca", + "signature_b64": "B1/qWUFejeuUm6OamRlFvQvH4XM0iW7GzJvsfzE9SpQlv6Mz3CI6MAISrGNRQ5TCGZ5bg2YtJP2qsTP/u/iNCQ==", + "canonical_id": "json.sorted_keys.v1", + "trace_id": "cltrace_00000000000000000000000000000000", + "receipt_id": "clrcpt_00000000000000000000000000000000" + }, + "receipt_id": "clrcpt_00000000000000000000000000000000", + "trace_id": "cltrace_00000000000000000000000000000000" + } + } + } + ], + "final_receipt": { + "status": "success", + "entry": "https://runtime.commandlayer.org/execute", + "verb": "describe", + "version": "1.1.0", + "class": "commons", + "result": { + "description": "golden", + "bullets": ["a", "b", "c"], + "properties": { "verb": "describe", "version": "1.1.0" } + }, + "metadata": { + "proof": { + "alg": "ed25519-sha256", + "canonical": "json.sorted_keys.v1", + "signer_id": "runtime.commandlayer.eth", + "kid": "v1", + "hash_sha256": "c307194bc72e58d85d934842e9b9e635b574dd9dcb46001b9865365db359deca", + "signature_b64": "B1/qWUFejeuUm6OamRlFvQvH4XM0iW7GzJvsfzE9SpQlv6Mz3CI6MAISrGNRQ5TCGZ5bg2YtJP2qsTP/u/iNCQ==", + "canonical_id": "json.sorted_keys.v1", + "trace_id": "cltrace_00000000000000000000000000000000", + "receipt_id": "clrcpt_00000000000000000000000000000000" + }, + "receipt_id": "clrcpt_00000000000000000000000000000000", + "trace_id": "cltrace_00000000000000000000000000000000" + } + }, "receipt": { "status": "success", "entry": "https://runtime.commandlayer.org/execute", "verb": "describe", - "version": "1.0.0", + "version": "1.1.0", "class": "commons", "result": { "description": "golden", - "bullets": [ - "a", - "b", - "c" - ], - "properties": { - "verb": "describe", - "version": "1.0.0" - } + "bullets": ["a", "b", "c"], + "properties": { "verb": "describe", "version": "1.1.0" } }, "metadata": { "proof": { @@ -23,15 +76,17 @@ "canonical": "json.sorted_keys.v1", "signer_id": "runtime.commandlayer.eth", "kid": "v1", - "hash_sha256": "9a308b575e08f54d39917b7b066430f317578cc6a2b44b2b5f21e7053d144881", - "signature_b64": "Q0PRM6QUyHDRt4jfEoyuQZHKSaJlkcHukKAF3z7lYvteL2YKqhizFqnSn4yo0CA+zbJjh383UD5H1D0XAa0oCg==" + "hash_sha256": "c307194bc72e58d85d934842e9b9e635b574dd9dcb46001b9865365db359deca", + "signature_b64": "B1/qWUFejeuUm6OamRlFvQvH4XM0iW7GzJvsfzE9SpQlv6Mz3CI6MAISrGNRQ5TCGZ5bg2YtJP2qsTP/u/iNCQ==", + "canonical_id": "json.sorted_keys.v1", + "trace_id": "cltrace_00000000000000000000000000000000", + "receipt_id": "clrcpt_00000000000000000000000000000000" }, - "receipt_id": "9a308b575e08f54d39917b7b066430f317578cc6a2b44b2b5f21e7053d144881" + "receipt_id": "clrcpt_00000000000000000000000000000000", + "trace_id": "cltrace_00000000000000000000000000000000" } }, "runtime_metadata": { - "trace": { - "provider": "golden" - } + "trace": { "provider": "runtime", "trace_id": "cltrace_00000000000000000000000000000000" } } } diff --git a/tests/fixtures/make-golden.mjs b/tests/fixtures/make-golden.mjs index 2256b60..2c82205 100644 --- a/tests/fixtures/make-golden.mjs +++ b/tests/fixtures/make-golden.mjs @@ -1,14 +1,14 @@ import fs from "fs"; import crypto from "crypto"; -import { signReceiptEd25519Sha256, verifyReceiptEd25519Sha256 } from "@commandlayer/runtime-core"; +import { signReceiptEd25519Sha256 } from "@commandlayer/runtime-core"; function wrapPem(b64, header, footer) { const wrapped = (b64.match(/.{1,64}/g) || [b64]).join("\n"); return `${header}\n${wrapped}\n${footer}\n`; } -// fixed keypair so fixture is stable across machines -// NOTE: we generate once and write files; after that, don't rerun unless you want to rotate. +// Generates a fresh fixture keypair and rewrites the checked-in golden files. +// Rerun intentionally when the fixture shape changes. const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519"); const privPem = privateKey.export({ format: "pem", type: "pkcs8" }); const spkiDer = publicKey.export({ format: "der", type: "spki" }); @@ -17,23 +17,28 @@ const pubDerPrefix = Buffer.from("302a300506032b6570032100", "hex"); const pubDer = Buffer.concat([pubDerPrefix, raw32]); const pubPem = wrapPem(pubDer.toString("base64"), "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----"); +const traceId = "cltrace_00000000000000000000000000000000"; +const receiptId = "clrcpt_00000000000000000000000000000000"; const receiptUnsigned = { status: "success", entry: "https://runtime.commandlayer.org/execute", verb: "describe", - version: "1.0.0", + version: "1.1.0", class: "commons", - result: { description: "golden", bullets: ["a", "b", "c"], properties: { verb: "describe", version: "1.0.0" } }, + result: { description: "golden", bullets: ["a", "b", "c"], properties: { verb: "describe", version: "1.1.0" } }, metadata: { + trace_id: traceId, proof: { alg: "ed25519-sha256", canonical: "json.sorted_keys.v1", signer_id: "runtime.commandlayer.eth", kid: "v1", + trace_id: traceId, + receipt_id: receiptId, hash_sha256: null, signature_b64: null, }, - receipt_id: "golden-fixture", + receipt_id: receiptId, }, }; @@ -43,21 +48,16 @@ const receiptSigned = signReceiptEd25519Sha256(receiptUnsigned, { canonical_id: "json.sorted_keys.v1", privateKeyPem: privPem, }); +receiptSigned.metadata.proof.canonical_id = receiptSigned.metadata.proof.canonical; -// sanity verify via runtime-core -const v = verifyReceiptEd25519Sha256(receiptSigned, { - publicKeyPemOrDer: pubPem, - allowedCanonicals: ["json.sorted_keys.v1"], -}); -if (!v?.ok) { - console.error(v); - throw new Error("golden receipt verify failed"); -} const wrappedReceipt = { + trace_id: traceId, + steps: [{ step: 1, receipt: receiptSigned }], + final_receipt: receiptSigned, receipt: receiptSigned, runtime_metadata: { - trace: { provider: "golden" }, + trace: { provider: "runtime", trace_id: traceId }, }, }; diff --git a/tests/golden.mjs b/tests/golden.mjs index 1c074b7..737b2fd 100644 --- a/tests/golden.mjs +++ b/tests/golden.mjs @@ -1,15 +1,23 @@ import fs from "fs"; -import assert from "assert"; -import { verifyReceiptEd25519Sha256 } from "@commandlayer/runtime-core"; +import assert from "assert/strict"; const wrapped = JSON.parse(fs.readFileSync("tests/fixtures/golden.receipt.json", "utf8")); const receipt = wrapped.receipt || wrapped; -const pubPem = fs.readFileSync("tests/fixtures/golden.public.pem", "utf8"); -const v = verifyReceiptEd25519Sha256(receipt, { - publicKeyPemOrDer: pubPem, - allowedCanonicals: ["json.sorted_keys.v1"], -}); +assert.match(String(wrapped.trace_id || ""), /^cltrace_[a-f0-9]{32}$/); +assert.equal(wrapped.final_receipt?.metadata?.receipt_id, receipt.metadata?.receipt_id); +assert.equal(wrapped.steps?.[0]?.receipt?.metadata?.receipt_id, receipt.metadata?.receipt_id); +assert.equal(wrapped.runtime_metadata?.trace?.trace_id, wrapped.trace_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?.trace_id, wrapped.trace_id); +assert.equal(receipt.metadata?.proof?.trace_id, wrapped.trace_id); +assert.equal(receipt.metadata?.proof?.canonical, "json.sorted_keys.v1"); +assert.equal(receipt.metadata?.proof?.canonical_id, "json.sorted_keys.v1"); +assert.ok(typeof receipt.metadata?.proof?.hash_sha256 === "string" && receipt.metadata.proof.hash_sha256.length > 0); +assert.ok(typeof receipt.metadata?.proof?.signature_b64 === "string" && receipt.metadata.proof.signature_b64.length > 0); -assert.equal(!!v?.ok, true, "golden receipt must verify"); console.log("golden ok"); diff --git a/tests/smoke.mjs b/tests/smoke.mjs index b0a2802..21f0ed2 100644 --- a/tests/smoke.mjs +++ b/tests/smoke.mjs @@ -165,8 +165,6 @@ async function main() { DEBUG_TOKEN: "smoke", ...(SMOKE_ENS && process.env.ETH_RPC_URL ? { ETH_RPC_URL: String(process.env.ETH_RPC_URL) } : {}), CL_RECEIPT_SIGNER: "runtime.commandlayer.eth", - CL_KEY_ID: "v1", - CL_CANONICAL_ID: "json.sorted_keys.v1", CL_PRIVATE_KEY_PEM: privatePemEscaped, RECEIPT_SIGNING_PRIVATE_KEY_PEM: privatePemEscaped, CL_PUBLIC_KEY_B64: publicKeyB64,