Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/core/src/inbound/task-consent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
Expand Down
11 changes: 9 additions & 2 deletions packages/core/tests/inbound.task-consent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ──────────────────────────────────────────────────
Expand Down