fed-sx-m2: Step 8c — delivery-state projection + 14 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s

New next/kernel/delivery_state.erl folds delivery events into a
per-peer worker-shaped snapshot so the outbound queue survives
kernel restart.

Event proplist shapes:
  [{type, enqueued},      {peer, _}, {activity, _}]
  [{type, delivered},     {peer, _}, {cid, _}]
  [{type, failed},        {peer, _}, {cid, _}, {now, _}]
  [{type, dead_lettered}, {peer, _}, {cid, _}]

Projection state shape:
  [{PeerId, [{peer, _}, {pending, _}, {attempts, _},
             {next_retry, _}, {dead_letter, _}]}, ...]

Mirrors delivery_worker:new/1 (minus :dispatch_fn — that's the
live worker's concern) so a fresh gen_server can be hydrated
from the projection on restart.

Public API:
  new/0
  fold/2, fold_fn/0
  peer_state/2, peers/1
  pending/2, attempts/2, next_retry/2, dead_letter/2

The failed branch calls delivery_worker:backoff_for/1 directly,
so the projection and the live worker compute identical retry
slots and dead-letter thresholds. 6th failure -> dead-letter,
matching the worker.

14/14 in next/tests/delivery_state.sh covering:
  - new/0 -> []
  - enqueued appends to pending (FIFO)
  - two peers maintain independent queues
  - delivered clears matching pending entry
  - failed bumps :attempts and sets :next_retry
  - 6th failed -> dead-lettered (activity out of pending)
  - explicit dead_lettered event moves activity to dead_letter
  - peers/1 lists touched peers
  - peer_state {ok, _} | not_found
  - fold_fn/0 is fun/2 for projection:start_link
  - unknown event type passes through
  - delivered after failed clears retry state

delivery_worker.sh 17/17 unchanged, delivery_retry.sh 11/11
unchanged. Conformance preserved at 761/761.

The restart hydration helper (delivery_worker:state_from_proj/2
or similar) lands once 8b-timer can wire the live retry loop
(Blockers #3 — erlang:send_after substrate gap still open).
This commit is contained in:
2026-06-07 02:37:53 +00:00
parent 8bf2b45cf9
commit 8ba3584556
3 changed files with 380 additions and 4 deletions

View File

@@ -566,10 +566,22 @@ a dead-letter list visible via `/admin/dead-letter`.
self-cast or equivalent). Needs the same substrate primitive
that `gen_server` uses for `timeout` returns. Defer behind
substrate gap discovery for now — see Blockers.
- [ ] **8c** — Delivery-state projection so the queue survives
kernel restart. New `next/kernel/delivery_state.erl` fold maps
enqueue / delivered / failed events to the worker's persistent
shape.
- [x] **8c** — Delivery-state projection
(`next/kernel/delivery_state.erl`). Folds delivery events into
per-peer worker-shaped snapshots so the outbound queue survives
kernel restart. Event shapes:
`[{type, enqueued|delivered|failed|dead_lettered}, {peer, _},
{activity, _} | {cid, _}, {now, _}?]`. State shape
`[{PeerId, WorkerProplist}, ...]` mirrors `delivery_worker:new/1`'s
output so a fresh gen_server can be hydrated on restart. Public
API: `new/0`, `fold/2`, `fold_fn/0`, `peer_state/2`, `peers/1`,
per-field accessors (`pending`, `attempts`, `next_retry`,
`dead_letter`). Uses `delivery_worker:backoff_for/1` to decide
dead-letter promotion on the 6th failure, so the projection
and the live worker stay in lockstep. 14/14 in
`delivery_state.sh`. The restart-hydration helper
(`delivery_worker:state_from_proj/2` or similar) lands when
8b-timer wires the live retry loop.
- [x] **8d** — `outbox:publish/2` dispatches each delivery-set
entry to the matching worker. New `dispatch_deliveries/3` +
`enqueue_each/2` in `outbox.erl` walk the computed
@@ -938,6 +950,22 @@ proceed.
Newest first.
- **2026-06-07** — Step 8c: delivery-state projection. New
`next/kernel/delivery_state.erl` folds enqueue / delivered /
failed / dead_lettered events into a per-peer worker-shaped
snapshot. State shape mirrors `delivery_worker:new/1`'s output
so a fresh gen_server can be hydrated from the projection on
kernel restart. The fail branch calls
`delivery_worker:backoff_for/1` directly, so the projection and
the live worker compute identical retry slots / dead-letter
thresholds. `fold_fn/0` plugs into `projection:start_link/3`
just like `actor_state` and `follower_graph`. 14/14 in
`delivery_state.sh`; delivery_worker.sh 17/17 + delivery_retry.sh
11/11 unchanged. Conformance preserved at 761/761. The
hydration helper that loads a worker's pure state from the
projection lands once 8b-timer can wire the live retry loop
(Blockers #3 still open).
- **2026-06-07** — Step 8b-pure: retry-time bookkeeping.
`delivery_worker` state shape gains `:next_retry` proplist
alongside `:attempts`. `record_failure_pure/3(Cid, Now, State)`