diff --git a/runtime/tests/runtime-signing.test.mjs b/runtime/tests/runtime-signing.test.mjs index 86b11e2..ec36634 100644 --- a/runtime/tests/runtime-signing.test.mjs +++ b/runtime/tests/runtime-signing.test.mjs @@ -252,6 +252,37 @@ test("POST /execute dispatches execution.verb to clean and keeps canonical commo } }); +test("POST /execute normalizes nested execution into the handler body and preserves execution metadata", 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}/execute`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + execution: { verb: "describe", version: "1.1.0", class: "commons" }, + input: { subject: "Normalize me" }, + }), + }); + const json = await resp.json(); + const { receipt } = unwrapReceiptResponse(json); + + assert.equal(resp.status, 200); + assert.equal(receipt.verb, "describe"); + assert.equal(receipt.version, "1.1.0"); + assert.equal(receipt.class, "commons"); + assert.equal(typeof receipt.result.description, "string"); + assert.ok(receipt.result.description.length > 0); + } finally { + await stop(srv.proc); + } +}); + test("POST /execute falls back to top-level verb for summarize", async () => { const keys = makeKeys(); const srv = await startServer({ diff --git a/server.mjs b/server.mjs index 3e12633..277e8a8 100644 --- a/server.mjs +++ b/server.mjs @@ -1540,7 +1540,7 @@ async function handleVerb(verb, req, res) { }; try { - const execution = normalizeExecutionEnvelope(req.body?.execution, verb); + const execution = normalizeExecutionEnvelope(req.body?.execution ?? req.body, verb); warmValidatorForVerb(execution.verb); const callerTimeout = Number(req.body?.limits?.timeout_ms || req.body?.limits?.max_latency_ms || 0); @@ -1566,7 +1566,7 @@ async function handleVerb(verb, req, res) { } } catch (e) { - const execution = normalizeExecutionEnvelope(req.body?.execution, verb); + const execution = normalizeExecutionEnvelope(req.body?.execution ?? req.body, verb); warmValidatorForVerb(execution.verb); const actor = req.body?.actor @@ -2025,10 +2025,20 @@ app.post("/verify", async (req, res) => { // verb routes // ----------------------- app.post("/execute", (req, res) => { - const resolvedVerb = String(req.body?.execution?.verb || req.body?.verb || "").trim(); + const body = req.body && typeof req.body === "object" ? req.body : {}; + const execution = body.execution && typeof body.execution === "object" ? body.execution : body; + const resolvedVerb = String(execution.verb || body.verb || "").trim(); + if (!resolvedVerb) { return res.status(400).json({ ok: false, error: "missing_verb", message: "execution.verb or verb is required", ...instancePayload() }); } + + req.body = { + ...body, + ...execution, + verb: resolvedVerb, + }; + return handleVerb(resolvedVerb, req, res); });