fed-sx-m2: Step 9a — pure-functional backfill slicing + 20 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s

New next/kernel/backfill.erl owns the §13.3 backfill mode
slicing. Given an outbox log + a mode, returns the activity
list to send to a new follower as backfill.

Public API:
  slice/2(Mode, LogState)               default Wrap=false
  slice/3(Mode, LogState, Wrap)         Wrap=true wraps entries
  wrap_backfill/1                       add {backfilled, true}
  parse_mode/1                          lift Follow :backfill field

Modes:
  none                       new follower: forward-only content
  full                       entire outbox
  {last_n, N}                last N activities (FIFO)
  {last_t, T, NowFn}         entries with :published in
                             (NowFn()-T .. NowFn()]
  {since_cid, Cid}           entries after the one with :id = Cid
                             (consumes the matched entry; returns
                             every entry after it)

wrap_backfill/1 marks each entry {backfilled, true}. Per §13.3
wrapped bodies preserve :id so the receiver's replay defence
still catches duplicates from the live stream.

parse_mode/1 accepts:
  nil / none / full / {last_n, _} / {last_t, _, _} /
  {since_cid, _} — pass through or normalize
  Proplist with :mode + :limit -> {last_n, N}
  Proplist with :mode + :duration -> {last_t, T, fun() -> 0 end}
  Proplist with :mode = full -> full
  Anything else -> none (open-world default)

Substrate gotchas re-confirmed and worked around:
  - lists:nthtail/2 not registered — rolled drop_n/2
  - Pattern-alias 'Pat = Var' not supported by this port's
    parser — parse_mode/1 clauses use explicit deconstruction

20/20 in next/tests/backfill.sh covering all five modes plus
edge cases (N=0, N>length, T=0 -> empty window, since_cid
hit/miss/unknown), wrap_backfill semantics, parse_mode for
atoms / tuple shapes / proplists / unknown / nil.

Step 9b (outbox listing ?since=Cid&limit=N pagination) and
Step 9c (Follow-Accept-backfill wiring) layer on top.
Conformance preserved at 761/761.
This commit is contained in:
2026-06-07 05:39:46 +00:00
parent b2b61a0112
commit 9621599606
3 changed files with 352 additions and 5 deletions

View File

@@ -630,11 +630,37 @@ Per §13.3: A wants B's history when A first follows B. Four modes:
**Deliverables:**
- Follow activity may carry `:backfill {:mode :last-N :limit 100}`.
- On Accept, B's outbox is GET-paged with appropriate filters.
- `GET /actors/<id>/outbox?since=Cid&limit=N` returns a paged response.
- Backfill bodies wrap the original activities in `:backfilled true`
so projections can decide whether to re-fold or skip.
- [x] **9a** — Pure-functional backfill slicing in
`next/kernel/backfill.erl`:
- `slice/2,3(Mode, LogState[, Wrap])` returns the entry list
for a given mode. Wrap=true marks each entry
`{backfilled, true}` so receiving projections can decide
whether to re-fold or skip (per §13.3, wrapped bodies
preserve `:id` so replay defence still catches duplicates).
- Modes: `none`, `full`, `{last_n, N}`, `{last_t, T, NowFn}`,
`{since_cid, Cid}`. NowFn is a 0-arity fun so tests can
fake-time it.
- `parse_mode/1` lifts the Follow activity's `:backfill`
value (atom or proplist) into the internal mode tuple;
unknown shapes degrade to `none` (open-world default).
Substrate gotchas re-confirmed:
`lists:nthtail/2` not in this port (rolled `drop_n/2`);
pattern-alias `Pat = Var` not supported (rewrote
`parse_mode/1` clauses with explicit deconstruction).
20/20 in `backfill.sh` covering all 5 modes (with edge
cases: N=0, N>length, T=0, since_cid hit/miss/unknown),
wrap_backfill, parse_mode atoms / tuples / proplists /
unknown.
- [ ] **9b** — `GET /actors/<id>/outbox?since=Cid&limit=N`
pagination route. Extends the Step 4d outbox listing with
the `?since=` query param (calls `backfill:since_cid_entries/2`).
Acceptance test extends `http_multi_actor.sh`.
- [ ] **9c** — Follow → Accept → backfill-delivery wiring.
The receiving kernel reads the Follow's `:backfill` field
via `parse_mode/1`, slices its outbox, and dispatches each
entry to the new follower's delivery_worker queue (Step 8d).
Gates on Blockers #2 (httpc) for the actual peer fetch path
but the in-process drain works today.
**Tests:**
@@ -1010,6 +1036,21 @@ proceed.
Newest first.
- **2026-06-07** — Step 9a: pure-functional backfill slicing.
`next/kernel/backfill.erl` with `slice/2,3(Mode, LogState
[, Wrap])` returning the appropriate activity list. Modes
`none / full / {last_n, N} / {last_t, T, NowFn} /
{since_cid, Cid}` cover the §13.3 grammar; `wrap_backfill/1`
marks each entry `{backfilled, true}` (id preserved so the
receiver's replay defence still works). `parse_mode/1` lifts
the Follow activity's `:backfill` value (atom or proplist)
into the internal mode tuple; unknown shapes -> none. 20/20
in `backfill.sh`. Substrate gotchas re-confirmed:
`lists:nthtail/2` not registered (rolled `drop_n/2`); pattern-
alias `Pat = Var` not supported in this port (rewrote
`parse_mode/1` clauses with explicit deconstruction).
Conformance preserved at 761/761.
- **2026-06-07** — Step 11b: projection folds for the new verbs.
Two new modules in `next/kernel/`:
`announce_state.erl` (per-Cid announcer-set fold, set