fed-sx-m2: Step 8a — delivery_worker skeleton + 17 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 55s

next/kernel/delivery_worker.erl is the gen_server-per-peer
delivery queue per design §13.4. Step 8a lands the skeleton:
pure-functional state shape + enqueue / drain / deliver_one
helpers + backoff schedule + gen_server wrapper. No retry
timer wiring yet (Step 8b), no persist projection yet (8c),
no outbox dispatch wiring yet (8d), no httpc BIF yet (8e), no
live HTTP yet (8f).

State shape (pure):
  [{peer, PeerId},
   {pending, [Activity, ...]},          %% FIFO queue
   {attempts, [{Cid, AttemptCount}]},   %% per-cid retry count
   {dead_letter, [Activity, ...]},
   {dispatch_fn, fun/1 | undefined}]

Pure-functional API:
  new/1
  pending/1, peer/1
  enqueue_pure/3       — append to FIFO
  drain_pure/1         — attempt every queued; returns
                         {NewState, DeliveredCids, RetryCids}
  deliver_one_pure/2   — single dispatch via :dispatch_fn

Backoff schedule (§13.4): 30s / 5m / 30m / 6h / 24h then dead_letter
  backoff_for/1   — attempt -> seconds | dead_letter
  schedule_for/1  — attempt -> {retry_in, Sec} | dead_letter

gen_server (registered under peer-id atom):
  start_link/1, start_link/2(PeerId, DispatchFn)
  stop/1
  enqueue/2     — sync call
  flush/1       — drain + reply with {ok, Delivered, Retry}
  pending_srv/1
  set_dispatch_fn/2  — swap dispatch in flight

dispatch_fn is a caller-supplied 1-arity fun so tests can stub the
HTTP POST. Step 8f will plug in a closure over httpc:request/4
without touching the queue logic.

17/17 in next/tests/delivery_worker.sh covering:
  - new/peer/pending base cases
  - enqueue_pure FIFO append
  - drain_pure no-dispatch -> retry, queue intact
  - drain_pure ok dispatch -> queue empties + delivered list
  - drain_pure failing dispatch -> queue intact + retry list
  - deliver_one_pure {ok, Cid} and {error, _, no_dispatch_fn}
  - backoff_for slot values match §13.4
  - backoff_for >=6 returns dead_letter
  - schedule_for wraps the slot or dead_letter
  - gen_server start_link + enqueue + pending_srv
  - gen_server flush with ok dispatch (delivered)
  - gen_server flush with failing dispatch (queue kept)
  - gen_server set_dispatch_fn in-flight swap

Conformance 761/761.
This commit is contained in:
2026-06-07 01:01:17 +00:00
parent c6b4920074
commit bf4e034c4e
3 changed files with 398 additions and 9 deletions

View File

@@ -536,15 +536,38 @@ a dead-letter list visible via `/admin/dead-letter`.
**Deliverables:**
- `delivery_worker.erl`: gen_server per-peer queue with `enqueue/2`
and a private retry loop.
- Backoff schedule: 30s / 5m / 30m / 6h / 24h then dead-letter.
- Delivery state stored as a projection (`delivery-state`) so it
survives kernel restarts.
- `outbox:publish/2` augmented: after `log:append`, dispatch to the
delivery worker for each delivery-set entry.
- HTTP client: extend the existing native httpc primitive to
carry signed envelope bytes + the right Content-Type.
- [x] **8a** — `delivery_worker.erl` skeleton: pure-functional
state shape `[{peer, _}, {pending, [_]}, {attempts, [{Cid, N}]},
{dead_letter, [_]}, {dispatch_fn, _}]` plus
`enqueue_pure/3`, `drain_pure/1`, `deliver_one_pure/2` and the
backoff schedule (`backoff_for/1`, `schedule_for/1`) matching
§13.4 (30s / 5m / 30m / 6h / 24h then dead-letter).
gen_server wrapper with `start_link/1,2`, `enqueue/2`, `flush/1`,
`pending_srv/1`, `set_dispatch_fn/2`. dispatch_fn is a
caller-supplied 1-arity fun so tests can stub the HTTP POST;
Step 8f plugs in the live httpc call without touching the
queue logic. No actual HTTP yet; no retry timer wiring yet.
17/17 in `delivery_worker.sh`.
- [ ] **8b** — Retry / backoff scheduler. Wire `schedule_for/1`
into a private retry loop: `flush/1` returns deliveries that
failed; the worker schedules a self-cast via Erlang `after`
timer for the next retry slot. Tests fake-time via a Cfg
`:now_fn`.
- [ ] **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.
- [ ] **8d** — `outbox:publish/2` dispatches each delivery-set
entry to the matching worker. The worker is created lazily on
first delivery to a peer.
- [ ] **8e** — `httpc:request/4` BIF wrapper in
`lib/erlang/runtime.sx` (the briefing's allowed scope
exception for Step 8). Marshalling: SX dict ↔ Erlang proplist
shape with `{ok, Status, Headers, Body}` / `{error, Reason}`.
- [ ] **8f** — Real HTTP dispatch through the BIF + content-type
wiring. dispatch_fn for live use becomes a closure over the
peer URL that calls `httpc:request/4` with the signed envelope
bytes as the body.
**Tests:**
@@ -867,6 +890,18 @@ proceed.
Newest first.
- **2026-06-07** — Step 8a: delivery_worker skeleton.
`next/kernel/delivery_worker.erl` with pure-functional state +
enqueue / drain / deliver_one + backoff schedule (30s / 5m /
30m / 6h / 24h then dead-letter, per design §13.4). gen_server
wrapper exposes the same APIs under the peer-id atom. dispatch
is a caller-supplied `:dispatch_fn` fun — Step 8b layers the
retry timer, Step 8c persists the queue, Step 8d wires
`outbox:publish/2` to dispatch, Step 8e brings the
`httpc:request/4` BIF (substrate exception per briefing), Step
8f closes with live HTTP. 17/17 in `delivery_worker.sh`.
Conformance 761/761.
- **2026-06-07** — Step 7c (closes Step 7): outbox-side
delivery_set integration. `outbox:publish/2` computes the
audience-resolved delivery set after sign + log and stashes