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" } });