From b0407cda71add6e0507e8b7fb620ce36fbd43461 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Thu, 17 Sep 2026 07:28:41 -0700 Subject: [PATCH] oskiewar consent: pass the desk's capability through instead of a hardcoded null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit capability: null has been in this response since slice 1, with a comment saying it stays that way until there is a worker whose reach a grant can bound. The desk now mints one on an allow, so the null is no longer the honest answer — it is just stale. What arrives is narrow by construction: the approved source categories and outputs, a hash of the exact scope it descends from, and ten minutes of life. No distribution, no marketing, no merchandise, no training — those govern a finished bundle rather than what a worker may touch, and a generation worker is the wrong place to enforce them. An edit still carries nothing. The counter is an offer the player has not taken, and authority on the strength of terms nobody accepted is the failure this wall exists to prevent. Still no worker consumes it. It is passed through rather than withheld because the next stage binds to it, and because a player told "allowed, and recorded" should be able to see the shape of what they permitted. Tests: two cases pin it — a minted capability arrives unaltered, and an edit carries none. 18 -> 20. Co-Authored-By: Claude Opus 5 (1M context) --- system/netlify/functions/oskiewar-consent.mjs | 18 +++++++++++---- .../tests/oskiewar-consent-gateway.test.mjs | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/system/netlify/functions/oskiewar-consent.mjs b/system/netlify/functions/oskiewar-consent.mjs index 71d41029ff..a43b68625e 100644 --- a/system/netlify/functions/oskiewar-consent.mjs +++ b/system/netlify/functions/oskiewar-consent.mjs @@ -224,10 +224,20 @@ export async function handler(event) { receipt, scope: request.frozen_fields.purpose_scope, counter: answer?.counter?.frozen_fields?.purpose_scope ?? null, - // Slice 1 proves the wall. The generation capability is the next thing - // to land behind it, and it stays null until there is a worker whose - // reach this grant can actually bound. - capability: null, + // The desk mints this on an allow and only on an allow — an edit is an + // offer the player has not taken, and authority on the strength of terms + // nobody accepted is the failure this wall exists to prevent. + // + // It is narrow on purpose: the approved source categories and outputs, + // and nothing about distribution, marketing, merchandise or training. + // Those govern a finished bundle, not what a worker may touch. It + // expires in minutes, because a capability is for one job run. + // + // No worker consumes it yet. It is passed through rather than withheld + // because the next stage binds to it, and because a player who has just + // been told "allowed, and recorded" should be able to see the shape of + // what they permitted. + capability: answer?.capability ?? null, }), }; } diff --git a/system/tests/oskiewar-consent-gateway.test.mjs b/system/tests/oskiewar-consent-gateway.test.mjs index f3e0ee0445..eedf2cd0c5 100644 --- a/system/tests/oskiewar-consent-gateway.test.mjs +++ b/system/tests/oskiewar-consent-gateway.test.mjs @@ -43,6 +43,7 @@ const ask = (body = minimal, headers = { authorization: "Bearer t" }) => handler({ httpMethod: "POST", headers, body: JSON.stringify(body) }); const json = (response) => JSON.parse(response.body); +const PURPOSE_FOR_TEST = "oskiewar_fighter_generation"; // The stub desk. `reply` is swapped per test; `seen` keeps the last request // body and `seenHeaders` the last request headers, so a test can assert on @@ -148,6 +149,27 @@ test("the deployer token is presented to the desk, and it is a bearer token", as assert.equal(seenHeaders?.authorization, "Bearer test-deployer-token"); }); +test("a capability the desk mints is passed through, unaltered", async () => { + configure(); + reply = () => ({ status: 200, body: { outcome: "allow", + capability: { jws: "eyJ.fake.sig", expires_at: 1758000600, + sources: ["appearance"], outputs: ["portrait"] } } }); + const body = json(await ask()); + assert.equal(body.outcome, "allow"); + assert.equal(body.capability.jws, "eyJ.fake.sig"); + assert.deepEqual(body.capability.sources, ["appearance"]); + assert.deepEqual(body.capability.outputs, ["portrait"]); +}); + +test("an edit carries no capability, because the player has not taken the offer", async () => { + configure(); + reply = () => ({ status: 200, body: { outcome: "edit", + counter: { frozen_fields: { purpose_scope: { purpose: PURPOSE_FOR_TEST } } } } }); + const body = json(await ask()); + assert.equal(body.outcome, "edit"); + assert.equal(body.capability, null, "an offer is not an authority"); +}); + test("a desk that refuses the ask yields no capability", async () => { configure(); reply = () => ({ status: 500, body: { error: "nope" } }); -- 2.51.2