Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<verb>agent.eth/<verb>/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: "<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.

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<verb>agent.eth/<verb>/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

Expand Down
8 changes: 6 additions & 2 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<verb>"`
- `version: "1.1.0"`
- `entry: "x402://<verb>agent.eth/<verb>/v1.1.0"`
- `class: "commons"`

Commercial/payment-aware flows may still use `x402://<agent>/<verb>/v1.1.0` semantics outside this runtime repo.

### Startup behavior

Expand Down
2 changes: 1 addition & 1 deletion docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<verb>agent.eth/<verb>/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

Expand Down
37 changes: 20 additions & 17 deletions runtime/tests/runtime-signing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
};
Expand All @@ -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"],
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand All @@ -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,
Expand All @@ -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 } } : {}),
}),
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion scripts/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
};

Expand Down
82 changes: 41 additions & 41 deletions server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -712,25 +712,27 @@ 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": {
$id: `${SCHEMA_HOST}/schemas/v1.1.0/_shared/receipt.base.schema.json`,
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: {
Expand Down Expand Up @@ -765,7 +767,7 @@ const BUILTIN_SHARED_SCHEMAS = {
additionalProperties: true,
},
},
required: ["status", "x402", "metadata"],
required: ["status", "entry", "verb", "version", "metadata"],
additionalProperties: true,
},
};
Expand All @@ -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,
};
}
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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 } : {}),
};
}

Expand Down Expand Up @@ -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);
Expand All @@ -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" }
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});
}
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/golden.public.pem
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAIL4DH52qyRXv6DYEA253pjohH/l6Slr1cmtP/uJYGnQ=
MCowBQYDK2VwAyEAkHDJKuaiAEH2tToc1ImkQvpUJG9oosNAesbxGCKwnsI=
-----END PUBLIC KEY-----
15 changes: 7 additions & 8 deletions tests/fixtures/golden.receipt.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand All @@ -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": {
Expand Down
Loading
Loading