diff --git a/runtime/tests/runtime-signing.test.mjs b/runtime/tests/runtime-signing.test.mjs index 6e9f0f5..df6c058 100644 --- a/runtime/tests/runtime-signing.test.mjs +++ b/runtime/tests/runtime-signing.test.mjs @@ -814,71 +814,78 @@ test("full chain clean -> summarize -> classify verifies with schema using commo } }); -test("POST /trust-verification/sign/v1.0.0 returns signed proof and verifies via runtime-core", 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 payload = { subject: "hello", scope: "trust-verification" }; - const resp = await fetch(`${srv.base}/trust-verification/sign/v1.0.0`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ payload }), +const TRUST_VERIFICATION_VERBS = ["sign", "attest", "authorize", "approve", "reject", "permit", "grant", "authenticate", "endorse"]; + +for (const verb of TRUST_VERIFICATION_VERBS) { + test(`POST /trust-verification/${verb}/v1.0.0 returns signed proof and verifies via runtime-core`, 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", }); - assert.equal(resp.status, 200); - const json = await resp.json(); - const { receipt } = unwrapReceiptResponse(json); - assert.ok(receipt?.metadata?.proof); - assert.equal(receipt.metadata.proof.signature.alg, "Ed25519"); - assert.equal(receipt.metadata.proof.hash.alg, "SHA-256"); - const verifiedResp = await fetch(`${srv.base}/verify`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(receipt), - }); - assert.equal(verifiedResp.status, 200); - const verified = await verifiedResp.json(); - assert.equal(verified.checks?.signature_valid, true); - assert.equal(verified.checks?.hash_matches, true); - - const tampered = JSON.parse(JSON.stringify(receipt)); - tampered.verb = "tampered-sign"; - const tamperedResp = await fetch(`${srv.base}/verify`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(tampered), - }); - assert.equal(tamperedResp.status, 200); - const tamperedVerify = await tamperedResp.json(); - assert.equal(tamperedVerify.status, "INVALID"); - assert.equal(tamperedVerify.checks?.signature_valid, false); - } finally { - await stop(srv.proc); - } -}); - -test("POST /trust-verification/sign/v1.0.0 rejects missing payload", 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 payload = { subject: "hello", scope: "trust-verification", verb }; + const resp = await fetch(`${srv.base}/trust-verification/${verb}/v1.0.0`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ payload }), + }); + assert.equal(resp.status, 200); + const json = await resp.json(); + const { receipt } = unwrapReceiptResponse(json); + assert.ok(receipt?.metadata?.proof); + assert.equal(receipt.verb, verb); + assert.equal(receipt.class, "trust-verification"); + assert.equal(receipt.version, "1.0.0"); + assert.equal(receipt.metadata.proof.signature.alg, "Ed25519"); + assert.equal(receipt.metadata.proof.hash.alg, "SHA-256"); + + const verifiedResp = await fetch(`${srv.base}/verify`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(receipt), + }); + assert.equal(verifiedResp.status, 200); + const verified = await verifiedResp.json(); + assert.equal(verified.checks?.signature_valid, true); + assert.equal(verified.checks?.hash_matches, true); + + const tampered = JSON.parse(JSON.stringify(receipt)); + tampered.verb = `tampered-${verb}`; + const tamperedResp = await fetch(`${srv.base}/verify`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(tampered), + }); + assert.equal(tamperedResp.status, 200); + const tamperedVerify = await tamperedResp.json(); + assert.equal(tamperedVerify.status, "INVALID"); + assert.equal(tamperedVerify.checks?.signature_valid, false); + } finally { + await stop(srv.proc); + } }); - try { - const resp = await fetch(`${srv.base}/trust-verification/sign/v1.0.0`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({}), + + test(`POST /trust-verification/${verb}/v1.0.0 rejects missing payload`, 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", }); - assert.equal(resp.status, 400); - const json = await resp.json(); - assert.equal(json.error, "missing_payload"); - } finally { - await stop(srv.proc); - } -}); + try { + const resp = await fetch(`${srv.base}/trust-verification/${verb}/v1.0.0`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({}), + }); + assert.equal(resp.status, 400); + const json = await resp.json(); + assert.equal(json.error, "missing_payload"); + } finally { + await stop(srv.proc); + } + }); +} diff --git a/server.mjs b/server.mjs index ef67c8e..b833fee 100644 --- a/server.mjs +++ b/server.mjs @@ -1742,38 +1742,43 @@ app.post("/verify", async (req, res) => { } }); -app.post("/trust-verification/sign/v1.0.0", async (req, res) => { - const payload = extractSignPayload(req.body); - if (payload === null) { - return res.status(400).json({ - ok: false, - error: "missing_payload", - message: "Request body must include a non-null payload field", - ...instancePayload(), - }); - } +const TRUST_VERIFICATION_VERBS_V1 = ["sign", "attest", "authorize", "approve", "reject", "permit", "grant", "authenticate", "endorse"]; - const traceId = makeTraceId(); - const execution = { - entry: `${CANONICAL_BASE}/trust-verification/sign`, - verb: "sign", - version: "1.0.0", - class: "trust-verification", - }; +for (const verb of TRUST_VERIFICATION_VERBS_V1) { + app.post(`/trust-verification/${verb}/v1.0.0`, async (req, res) => { + const payload = extractSignPayload(req.body); + if (payload === null) { + return res.status(400).json({ + ok: false, + error: "missing_payload", + message: "Request body must include a non-null payload field", + ...instancePayload(), + }); + } - try { - const receipt = makeReceipt({ - execution, - result: { payload }, - status: "success", - traceId, - receiptId: makeFlowReceiptId(), - }); - return res.status(200).json(wrapReceiptResponse(receipt, { trace: { trace_id: traceId, provider: process.env.RAILWAY_SERVICE_NAME || "runtime" } })); - } catch (signErr) { - return respondSigningError(res, signErr); - } -}); + const traceId = makeTraceId(); + const execution = { + entry: `${CANONICAL_BASE}/trust-verification/${verb}`, + verb, + version: "1.0.0", + class: "trust-verification", + }; + + try { + const receipt = makeReceipt({ + execution, + result: { payload }, + status: "success", + traceId, + receiptId: makeFlowReceiptId(), + }); + receipt.verb = verb; + return res.status(200).json(wrapReceiptResponse(receipt, { trace: { trace_id: traceId, provider: process.env.RAILWAY_SERVICE_NAME || "runtime" } })); + } catch (signErr) { + return respondSigningError(res, signErr); + } + }); +} // verb routes // -----------------------