diff --git a/packages/core/src/inbound/task-consent.ts b/packages/core/src/inbound/task-consent.ts index ad2ffa0..47cc8f0 100644 --- a/packages/core/src/inbound/task-consent.ts +++ b/packages/core/src/inbound/task-consent.ts @@ -152,7 +152,16 @@ export interface ParsedTaskConsentRequest { } export type TaskConsentRequestRejection = + /** Not addressed to this handler at all — the ONLY reason a caller may + * ignore silently. Everything else claimed to be a consent request and + * failed, which a human is waiting on and must therefore be reported. */ | "not-a-task-consent-request" + /** It IS a consent request, but its payload is unusable. Distinct from the + * above because it used to share it, and callers key on that reason to + * decide whether to stay quiet: a malformed request was dropped in total + * silence — no prompt, no log, and the pending record cleared — which is + * indistinguishable from a message that never arrived. */ + | "malformed-payload" | "untrusted_issuer" | "expired" | "not_eligible"; @@ -246,7 +255,7 @@ export async function parseTaskConsentRequest( !payload.exposure || typeof payload.exposure !== "object" ) { - return reject("not-a-task-consent-request", "payload is missing required members"); + return reject("malformed-payload", "payload is missing required members"); } const now = opts.now ?? new Date(); diff --git a/packages/core/tests/inbound.task-consent.mjs b/packages/core/tests/inbound.task-consent.mjs index 890860e..2ec1501 100644 --- a/packages/core/tests/inbound.task-consent.mjs +++ b/packages/core/tests/inbound.task-consent.mjs @@ -146,12 +146,19 @@ test("…but may approve its own task when policy permits it", async () => { assert.equal(res.ok, true); }); -test("a payload missing required members is refused", async () => { +test("a payload missing required members is refused, and says so distinctly", async () => { // A genuinely-signed request that is nonetheless unusable: without the digest // there is nothing to bind an approval to, so there is nothing to approve. const res = await parseTaskConsentRequest(await inbound({ drop: ["payloadDigest"] }), opts); assert.equal(res.ok, false); - assert.equal(res.reason, "not-a-task-consent-request"); + // NOT "not-a-task-consent-request". That reason means "not addressed to this + // handler", and callers are entitled to ignore it silently — which is exactly + // what happened to a malformed request in the field: dropped with no prompt, + // no log, and its pending record cleared, indistinguishable from a message + // that never arrived. A request that claimed to be a consent ask and failed + // is something a human is waiting on, so it must stay reportable. + assert.equal(res.reason, "malformed-payload"); + assert.match(res.detail ?? "", /missing required members/); }); // ── What the human is shown ──────────────────────────────────────────────────