Skip to content
Draft
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
27 changes: 27 additions & 0 deletions .changeset/delivery-not-eligible-ack-claim-ownership.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@objectstack/spec': patch
---

`DELIVERY_NOT_ELIGIBLE`'s `INotificationOutbox.ack` gloss now describes the post-#11859 refusal set

The `ERROR_CODE_LEDGER` entry for `DELIVERY_NOT_ELIGIBLE` enumerates `ack`'s
refusal cases per surface. #11859 widened that set on the notification surface —
the claim credential now rides the record `claim()` returns and `ack()` takes it
back — and two statements in the bullet stopped being the whole truth:

- **The refusal set is wider than "not `in_flight`".** `ack` now also refuses a
row that **is** `in_flight` but is no longer held by the claim being
completed: ownership joined the predicate, and the credential is the
(`claimed_by`, `claimed_at`) **pair**, so a claim lost to the `claimTtlMs`
reap plus a re-claim by any node — including the caller's own later claim —
matches nothing and nothing is written. It additionally refuses a record
handed back carrying no claim credential at all.
- **"Also raised by `SqlNotificationOutbox`" was too narrow.**
`MemoryNotificationOutbox` raises the lost-claim refusal too; both backends
are pinned on one table in `outbox-ack-claim-ownership.integration.test.ts`,
and both route through the shared refusal messages in `outbox.ts` so the two
cannot drift into different wordings for one contract violation.

Comment only. No code is registered or removed, no wire value changes, and no
acceptance or refusal behaviour moves — `packages/spec` publishes
`src/**/*.zod.ts`, so the corrected gloss ships to consumers reading the ledger.
21 changes: 14 additions & 7 deletions packages/spec/src/api/error-code-ledger.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,13 +569,20 @@ export const ERROR_CODE_LEDGER = {
// re-claimed the row mid-call — both `SqlHttpOutbox` and
// `MemoryHttpOutbox` report that miss instead of a false success
// (#11009).
// - `INotificationOutbox.ack` (`NotificationAckError`; #11453) refuses a
// row that is not `in_flight` — which covers BOTH an unclaimed
// `pending` row (the ack-as-cancel trap) and an already-terminal one,
// because `ack` records the outcome of a delivery the caller CLAIMED.
// Also raised by `SqlNotificationOutbox` when its compare-and-set
// read-back shows the claim was lost mid-ack (a slow send outrunning
// `claimTtlMs`), so nothing was written.
// - `INotificationOutbox.ack` (`NotificationAckError`; #11453, #11859)
// refuses a row that is not `in_flight` — an unclaimed `pending` row
// (the ack-as-cancel trap) or an already-terminal one, because `ack`
// records the outcome of a delivery the caller CLAIMED — AND, since
// #11859, an `in_flight` row no longer held by the claim being
// completed: `ack` takes back the record `claim()` returned and the
// compare-and-set binds its (`claimed_by`, `claimed_at`) credential,
// so a claim lost to the `claimTtlMs` reap plus a re-claim (by ANY
// node, including the caller's own later claim) matches nothing and
// nothing is written. Also raised for a record handed back carrying
// no claim credential at all. BOTH backends raise every one of these
// refusals — `SqlNotificationOutbox` and `MemoryNotificationOutbox`,
// pinned on one table in
// `outbox-ack-claim-ownership.integration.test.ts`.
// Distinct from DELIVERY_NEVER_SENT: this one says "wrong state for THIS
// operation, try when it settles"; that one says "never, fix the
// configuration instead".
Expand Down
Loading