Packages

Framework-agnostic Elixir CDC consumer for Postgres logical replication (pgoutput) with zero-loss delivery to a pluggable sink — exactly-once for transactional sinks, at-least-once (duplicate-bounded) for non-transactional sinks.

Current section

Files

Jump to
replicant docs ROADMAP.md
Raw

docs/ROADMAP.md

# Replicant — Feature Tracker

**Updated:** 2026-09-28 · **Latest published:** `v1.3.0` (tagged) · **Candidate:** none · **Branch:** `main`

> **⚠ Commit-SHA note.** Git history was rewritten after most of this file was
> written, so the historical commit SHAs cited in the slice rows below (e.g.
> `489c6b1`, `6387e35`, `a6467af`, `2699e72`) **no longer resolve** to commits in
> this repo. They are retained as a historical record of the lifecycle narrative,
> not as addressable refs. The authoritative current refs are the snapshot below.
> Test counts in historical rows are milestone evidence and are not claims about
> the current suite.

## Current package snapshot

The initial sequencing plan below is complete, and both packages have continued
through later releases:

- **`replicant` 1.3.0 is the latest published release**, on Hex and tagged `v1.3.0` (the
  1.3.0 remediation release: full multidim-array casting, locale-honest money, lossless
  timetz, raising-free `lsn_from_string/1`, signed `atttypmod`, decoder strictness, and the
  complete telemetry/halt/casting reference docs; see CHANGELOG `[1.3.0]` and ADR-0008). Prior:
  1.2.4 (docs/examples release with no library API change). It
  is a docs/examples release with no library API change: the docker-compose reference
  example (`examples/replication_pipeline` — a Postgres→Postgres bridge with an effect-once
  sink, receipts ledger, session-identity binding, and liveness healthcheck) plus its
  `reference-example` CI canary, with the examples' READMEs shipped in the Hex package;
  see CHANGELOG `[1.2.4]`. Version 1.2.3
  keeps append-log acknowledgements bound to the durable delivered checkpoint while preserving
  filtered-WAL idle advancement for state mirrors; see CHANGELOG `[1.2.3]`. Version 1.2.2
  closes the incremental-backfill restart gap before the first chunk commits by adding an
  explicit durable pending state and resuming discovery from the live slot origin. Version
  1.2.1 bounds keyed incremental-snapshot contention after
  three discarded attempts and carries the post-publication package-identity correction.
  Version 1.2.0
  carries the R01–R05 fixes (fail-closed unknown-checkpoint halt, typed telemetry shapes,
  logical-message value-safety, the `handle_slot_origin/2` callback, and proven PostgreSQL
  15–18 support); 1.0.0 shipped at `v1.0.0`.
- **AshReplicant compatibility — OBSERVED, September 21, 2026:** `rg -n`/`sed` reads of
  the owner's `mix.exs`, `mix.lock`, and `CHANGELOG.md` show source version 1.2.0,
  declared Replicant range `>= 1.2.3 and < 2.0.0-0`, and committed Replicant lock 1.2.3.
  This supersedes the earlier 0.4.0/current-lock-1.2.2 snapshot and pending-1.0 wording;
  it does not establish current registry publication or rerun consumer acceptance.
  [AshReplicant's coordination ADR](https://github.com/baselabs/ash_replicant/blob/main/docs/adr/0005-replicant-coordination.md)
  owns the current compatibility floor, fetched-artifact proof, and dependency-first
  release order. Replicant remains independently usable through its public sink contract.

## Sequencing (user directive, 2026-07-05) — ✅ FULLY EXECUTED

Historical record of the build order, now complete. **"One in-build project at a
time":** `ash_replicant` was gated until `replicant` was "completed in full and
published" = build the remaining §3 slices → publish `replicant` to Hex → start
`ash_replicant`. Value-ranked order **(1) checkpoint-store → (2) batching → (3)
proto≥2 streaming** was executed in full; each slice was closeout-reviewed
(`/review-autopilot --fix`) to 100/100. With `replicant` 0.1.0 on Hex the gate
cleared, and `ash_replicant` has since shipped through to its own 0.3.0 release.

## Shipped — v1 streaming core (fail-closed, reviewed, green)

| Slice | What landed | Closeout |
|---|---|---|
| Plan 1 — offline core | decode / assemble / validate / redact; value-free boundary, TOAST sentinel, watermark skip, schema-change classification, identifier allowlist, sink behaviour, telemetry, real-`pgoutput` byte conformance | 100/100 |
| Plan 2 — live streaming + exactly-once | `Connection` owns the slot, ack-after-checkpoint, slot-invalidation fail-closed halt, `AssemblerServer`, per-pipeline supervision, §4 bounded in-flight window, real-PG16 crash-injection (loss=0, effect-dup=0) | 89→100 |
| replicant-snapshot — initial backfill | `snapshot: true`: `EXPORT_SNAPSHOT` → `COPY` at `consistent_point` → hand off at snapshot LSN, gap-free/dup-free; `handle_snapshot/2` + `handle_snapshot_complete/1`; mid-COPY crash halts `:snapshot_incomplete` | 100/100 |

Test evidence at that milestone (pre-history-rewrite figures): unit 184/0,
integration 197/0, dialyzer 0. The later `6fe9d3c` release milestone recorded
unit 350/0 + integration 38/0 and dialyzer 0; see the current snapshot above
for present package refs.

## Delivered — the spec §3 slices (all shipped + closed-out)

Each was a named slice the v1 primitive was **fail-closed without** — absence
refused partial delivery rather than doing it silently. **All are now shipped,
closeout-reviewed to 100/100, and first published in `replicant` 0.1.0.**

| # | Slice | Unlocks | Depends on | Status |
|---|---|---|---|---|
| — | **`ash_replicant`** (standalone lib) | The first-class consumer: an Ash/Postgres sink adapter carrying multitenancy + classification, one layer up from the tenant-blind core. This is what turns the primitive into a usable product. | `replicant` published (gate cleared) | ✅ **BUILT + PUBLISHED — 0.3.0 on Hex** at that milestone (tagged at `c5ef154`, then consuming `{:replicant, "~> 0.1.0"}`). Current package/source state is recorded in the snapshot above. Sibling to `arcadic`→`ash_arcadic`. |
| 1 | `replicant-checkpoint-store` | Non-transactional sinks (files, external APIs): a lib-owned checkpoint table with a mandatory checkpoint-**after**-persist write order (dup, never loss). | Core (shipped) | ✅ Shipped + closeout-ready 2026-07-05 (13/13 tasks, exec-autopilot) · `/review-autopilot --fix` HEAD `2d23832`: graded **89/100 → 100** on user ruling for the sole design-decision; 6 findings fixed, gate battery ALL-PASS 231/0 dialyzer 0. **F3 RESOLVED** (2026-07-05: build §14.18 next — spawns slice 1b). |
| 1b | `replicant-store-fault-retry` (§14.18) | Bounded-retry-then-halt on a checkpoint-store fault: a transient blip self-heals within N; a persistent outage retries N times then **HALTS + alerts the operator** (replaces the current UNPACED connect-retry interim + the immediate mid-stream write halt). Closes checkpoint-store closeout F3. | `replicant-checkpoint-store` (shipped) | ✅ **Shipped 2026-07-05** (exec-autopilot; 7/7 tasks, per-task two-stage opus review; unit **224/0**, integration **25/0**, dialyzer 0, credo/format clean; 0 tier escalations, 3 test-hardening review-fix rounds T1/T5/T6). Closeout `/review-autopilot` pending. Design adversarially reviewed (9 challenges 8-acc/1-refuted); plan machine-gated + independently reviewed (5/5 fixed). |
| 2 | `replicant-batching` | Throughput: batched **checkpointing** for lib mode — defer the lib-owned checkpoint write + slot ack to once per batch of N txns (sink delivery stays per-txn). Amortizes the synchronous serial store round-trip. Per-transaction checkpointing is the correctness baseline it optimizes. | Core + checkpoint-store + store-fault-retry (shipped) | ✅ **Shipped + closed-out 2026-07-06** (exec-autopilot 5 tasks 2 opus/3 sonnet, 0 tier escalations/0 review-fix rounds → `/review-autopilot --fix` **100/100 grader-verified**; post-fix HEAD `7bfa193`, unit **249/0** + integration **28/0**, dialyzer 0, credo **677/0**, format/compile clean). Commits: config `c22f03e`, assembler/server `2ef4d7d`, pipeline `3c4734b`, integration `ce9bf25`, docs `904d153`; closeout fixes `113a2a4`/`65040f3`/`7a9812f`/`c6c1c48`/`85672f1`/`7bfa193`. Both closeout design-decisions user-ratified + implemented (spec §15 amendments): LSN-span base → `max(lib_checkpoint, stream_floor)`; batch discarded on mid-stream reconnect. Cross-vendor Codex: 2 uniques (reconnect-stale-batch, value-free-leak). Opt-in `checkpoint_store: [batch: [max_transactions: 100, max_delay_ms: 1000]]` + auto LSN-span lag-cap (`max_inflight_lag/4`); dup bound widens to one batch (crash + graceful stop + mid-stream reconnect), loss=0 unconditional. Sink-owned batching → `replicant-batch-delivery` ✅ executed 2026-07-06 (row 2b). |
| 2b | `replicant-batch-delivery` | Sink-owned (transactional) batching: a new `handle_batch` sink callback delivering N transactions as ONE atomic unit — amortizes the *sink's own* commit cost (distinct from `replicant-batching`'s library-owned checkpoint-write amortization). Carved out of `replicant-batching` because it needs a mechanically distinct delivery contract + atomic-multi-txn semantics (a fresh-context reviewer confirmed the boundary is mechanical, not effort). | `replicant-batching` (shipped) | ✅ **Executed 2026-07-06** (exec-autopilot, 9 tasks: 5 opus / 3 sonnet / 1 haiku; per-task two-stage opus review, 4 review-fix rounds total, **0 tier escalations**). Post-sweep HEAD `6387e35` (range `7bfa193..6387e35`, 8 code/test/docs commits): **unit 271/0 + integration 31/0**, dialyzer 0, credo 741/0, format/compile clean (gate-log `20260706-071040-bd-full-sweep-6387e350f1`). **Effect-once (dup=0) crash-injection marquee GREEN against live PG16** (mid-batch atomic-checkpoint fault rolls the whole `handle_batch` txn back → resume re-delivers dup=0). Designed via brainstorm-autopilot, user-approved; fresh-context adversarial review folded — 11 challenges / 3 blocking, all reconciled. Effect-once preserved (dup=0, loss=0) — the transactional idempotent sink writes N txns + checkpoint atomically; **stronger** than batching's dup≤batch. Opt-in top-level `batch_delivery: [max_transactions: 100, max_delay_ms: 1000]` (sink-owned only; `+ checkpoint_store` ⇒ `:config_invalid`; missing `handle_batch/1` ⇒ `:batch_unsupported`). 6 lib modules (sink/config/assembler/assembler_server/pipeline/connection); telemetry unchanged. **CLOSED-OUT 2026-07-06 via `/review-autopilot` (8 lenses): 97 → 100 after one in-session fix.** The lone should-fix (`flush_sink_batch`'s result `case` lacked a value-free catch-all → a non-conforming `handle_batch` RETURN raised `CaseClauseError` in the flush path — which runs via `do_flush`, outside `handle_message/2`'s value-free rescue — leaking the returned term into the OTP crash log, Critical Rule 1; caught by the security lens **and** the Codex cross-vendor lens, duplicate/live-repro'd) was fixed in commit `215d58b` (value-free `_unexpected` catch-all + red-capable test RED→GREEN; spec §8 matrix + §14.19 amended). Fresh full battery @ `215d58b`: credo **744/0**, dialyzer 0, unit **303/0**, integration **31/0** (marquee dup=0 green). |
| 3 | `replicant-streaming` | `pgoutput` proto ≥ 2 in-progress-transaction streaming (opt-in `streaming:`): decode Stream Start/Stop/Commit/Abort + (sub)xid-prefixed changes, reassemble each streamed txn **in memory** demultiplexed by top-level xid (interleave + subtransaction-abort filtering), deliver the complete `%Transaction{}` on Stream Commit through the **unchanged** sink contract. Primary benefit is server-side (walsender streams a large txn instead of buffering it whole); consumer memory bounded by the existing §4 halt. | Core (shipped) | ✅ **Executed 2026-07-06** (exec-autopilot, 10 tasks 7 opus/3 sonnet; per-task two-stage opus review, **0 tier escalations**). Post-sweep HEAD `a6467af` (range `2901d4f..a6467af`, 17 code/test commits): **unit 307/0 + integration 34/0** (baseline 272/31 → +35 unit / +3 marquee tests, 0 regressions; the plan's recorded "303" unit baseline was a mis-record — true pre-slice count 272, per the batch-delivery closeout), dialyzer 0, credo 814/0, format/compile clean (gate-logs `20260706-1610…1612`, HEAD-bound). **Streaming effect-once (dup=0) crash-injection marquee GREEN on live PG16** — a large streamed txn with a nested-savepoint rollback (ids 1001..1500) delivers effect-once with the sub-aborted range filtered ABSENT end-to-end; streaming proven fired (`[:replicant, :stream, :committed]` probe, RED when the `work_mem` forcing is removed). **Real spec-§7 defect found + fixed by the marquee + an adversarial investigation:** `StreamCommit` delivered an EMPTY streamed txn (a spilled *unpublished-only* txn streams under pgoutput v2 but is skipped by v1), violating "a streamed txn is indistinguishable from a non-streamed one at the sink" — fixed via `{:skipped}` empty-suppression (`fe01f0c`); the masked-assertion workaround was caught and reverted to the honest `calls_count == 1` live red-gate. Opt-in top-level `streaming: [max_concurrent_txns: N]` (default 64); `Config` `streaming: nil` ≡ disabled (parity with `batch_delivery`). Sink contract + `telemetry.ex` UNCHANGED; effect-once preserved (dedup pre-skip + subxid-abort filter). Designed via brainstorm→plan-autopilot (design fresh-context-reviewed — 9 challenges/3 blocking folded; plan machine-gated 0-errors + independently reviewed 5/5). **Closeout `/review-autopilot --fix` 2026-07-06 → 100/100 (fresh-context grader), post-fix HEAD `b9e0947` (range `11f6afc..b9e0947`).** 8 lenses + adversarial refuter + worktree-isolated fix-diff re-reviewer. **1 blocking cross-vendor UNIQUE catch FIXED:** an EMPTY `StreamCommit` returned `{:skipped, lsn}` which acks the slot immediately — because streaming composes with the batch modes (§7) and a buffered-but-unflushed txn's commit_lsn is BELOW the empty txn's lsn (commit order), the ack advanced `confirmed_flush` PAST un-delivered/un-checkpointed WAL = **loss=0 violation** (sink-owned `batch_delivery`); fixed by folding the empty txn's lsn into the open batch (`suppress_empty_stream_commit/2`, `123287e`; red-capable test mutation-proven; same-family lenses reasoned stream-interleave but missed the batch composition). Cross-vendor CV2 (streamed `Relation` cache scoping) adversarially **REFUTED** (§8-conformant halt + `Enum.zip` truncation + pgoutput Relation re-send). 2 should-fixes fixed: §8 halt reason reconciled to `:too_many_streams` (`123287e`), byte-level xid-tamper decoder test §12.2 added (`75c0947`); replay collapsed to one-pass `map_reduce`. **Post-fix full battery GREEN on live PG16 @ `b9e0947` (clean tree, gate-run --verify): unit+integration 344/0, integration marquee 34/0, dialyzer 0, credo 815/0, compile/format clean** (baseline 341→344, +3 unit tests, 0 regressions). See spec §15 closeout amendments. |
| 3-spill | `replicant-streaming-spill` | Consumer-side disk spill so a single transaction **larger than memory** streams through without halting — the "unbounded single-txn size" capability. Carved from row 3 (its first-disk-I/O + PII-at-rest + OTP-lifetime risk surface earns its own review); row 3 is fail-closed without it (a too-large streamed txn halts, as under v1). | `replicant-streaming` | ✅ **Executed 2026-07-07** (exec-autopilot 12 tasks [9 opus / 3 sonnet] — 5 review-fix rounds, 0 tier escalations; unit **383/0** + integration **38/0** live PG16, dialyzer 0, credo 903/0, format/compile clean; range `9d9821e..473cef9`; gate-log `20260707-042549-spill-full-sweep-473cef984a`). Designed via brainstorm-autopilot Stage-6 fresh-context adversarial review (9 challenges / **4 blocking reworked**). **Delivery contract user-ratified — a >RAM txn delivers as a lazy disk-backed `%Transaction.changes` (`Enumerable.t()`); the parent's event-based "incremental streaming sink contract" non-goal is OBVIATED (not deferred) — lazy whole-`Transaction` changes gives the same memory capability while preserving atomicity.** Two ceilings: resident RAM `max_inflight_lag` (spill trigger) + disk `max_spill_bytes` (`:spill_exhausted` halt); §4 numerator now `received − floor − spilled` (the change the parent deferred here). One `Replicant.Spill` module = the sole `File.*` + at-rest boundary (0700 dir / 0600 files, global startup sweep, value-free `:spill_io_failed`); ephemeral non-fsync'd scratch (write-once-read-once-within-one-lifetime); no in-lib encryption (key-mgmt ownership, NOT Rule 5 — a clean future). **Plan-autopilot: 12 tasks (9 opus / 3 sonnet), machine-gated (plan-verify 0/0) + fresh-context reviewed (7 findings / 7 fixed — 2 blocking: `Error.reason()` enum gap → dialyzer fail; lib+batch spilled-file happy-path leak).** **Exec DONE — oversized-txn spill effect-once (dup=0) marquee GREEN on live PG16 (append-only ledger, `[:stream,:spilled]` probe fired), no stale spill files, both ceilings halt (`:spill_exhausted` disk + §4 RAM). The crash-injection marquee CAUGHT A REAL §8 BUG — a per-txn spilled delivery orphaned its spill file on a sink-fault halt (`deliver_now` deleted only on `{:ok}`) → fixed `dab7f2f`; a live-probe quality review also caught a §4-halt evasion (stale `spilled_bytes` mirror survived reconnect via `handle_connect`, `init/1` runs once) → fixed in `191284f`.** **CLOSED-OUT 2026-07-08 via `/review-autopilot --fix` (fresh-account resume): all 6 findings FIXED (TDD red→green), post-fix HEAD `489c6b1`; full battery GREEN (gate-log `20260708-064419-spill-closeout-fix2`, nonce `c40733be685617fe`): unit 387/0 + integration 38/0 live PG16, dialyzer 0, credo/format/compile clean. Fixes: CV1 `sink_batch_failed` deletes migrated spill files on the FAULT branch — no cleartext orphan (`9e57f11`); empty-suppression closes the spill FD via `Spill.discard` (not `Spill.rm`) — no `:emfile` leak (`97edc74`); CV2 append-before-account reorder so a single row > `max_inflight_lag` spills, `{:spilled_bytes}` cast now sends the NET post-dispatch total (`3ad3cbc`); `decode_frame` validates the frame shape at the at-rest boundary → value-free `:spill_io_failed` (`5561b23`); `:spill_exhausted` telemetry emitted at the disk-ceiling breach (`489c6b1`). **CV3 (§4 ceiling — this spec's own contradiction) resolved via Option A: the CODE is correct (`received−floor−spilled` vs the SUM `max_inflight_lag+max_spill_bytes`); §1/§4/§8 text wrongly said `max_inflight_lag` and was amended. A "Design 2" attempt to CHANGE the code (drop `−spilled`) was CAUGHT by the live disk-ceiling marquee (it made §4 pre-empt the disk ceiling → `:spill_exhausted` unreachable) and reverted — the `−spilled` numerator is LOAD-BEARING (adaptive: routes oversized single txns to the disk ceiling, non-spilling backlogs to §4).** Also de-vacuumed the "disk ceiling" marquee (it always halted via §4 `:sink_too_slow` — the disk-ceiling halt is deferred to `StreamCommit`, which a never-committing oversized txn never reaches — now asserts the real reason). See [[replicant-streaming-spill-slice]], [[replicant-fault-path-resource-cleanup]]. Spec closeout amendment added. |
| — | Multi-sink fan-out per slot | Deliberate non-goal, no slice: one slot = one sink = one checkpoint keeps the watermark tractable. Compose a dispatching sink or run multiple slots. | — | Won't build |

## Backlog — enhancement opportunities (2026-07-09 code-probe audit)

Complete inventory from a code-level capability audit at HEAD `499c8ba` (all
claims probe-verified against `lib/`, not doc-skimmed). Three sources: (A) new
gaps found by the probe, (B) future slices already named with owners in the
shipped specs' §3 non-goal tables, (C) ecosystem/adoption-layer candidates
(new sibling libs, like `ash_replicant`). Value-ranked build order:
**B1 → A1 → A5 → A2+A3 (paired) → remainder.**

### A. Code-probe gaps (not previously in any spec)

> **Current A1 amendment (2026-08-21):** filtered-WAL idle advance applies only to
> `:state_mirror` sinks. An `:append_log` sink acknowledges only its durable delivered
> checkpoint; a quiet append publication uses an ordinary published heartbeat transaction
> to advance that checkpoint and release retained WAL. The A1 row below is the historical
> closeout record from before Replicant exposed sink kinds.

| # | Item | Unlocks | Evidence | Status |
|---|---|---|---|---|
| A1 | **Idle-slot heartbeat / ack-advance** | Prevents indefinite WAL pinning on a quiet publication in a busy cluster — the #1 real-world logical-replication incident class. Ack `wal_end` on keepalive when zero txns are in flight (safe: filtered WAL carries nothing for the publication); today every keepalive acks the checkpointed LSN unconditionally. Touches the exactly-once ack seam → full brainstorm→spec rigor despite small size. | [connection.ex:6-9](../lib/replicant/connection.ex) ("never the received `wal_end`"); README delegates WAL-lag to ops monitoring | ✅ **CLOSED-OUT 2026-07-11 = 100/100** (`/review-autopilot --fix`, 8 lenses + cross-vendor Codex + fresh-context grader; grader-concurred zero-open). Full lifecycle brainstorm→spec→plan→exec→review, 9 commits `abcf22f..34a208d`; **final battery VERIFIED green: 518 tests/0 failures live PG16, dialyzer 0, credo 1266/0, compile --force --warnings-as-errors + format clean, tree clean**; gate-log `20260711-041642-idle-ack-closeout-fix-34a208d0de`. Idle predicate `not in_txn and open_streams empty and checkpoint_lsn ≥ last_commit_lsn` (transaction-boundary; the approved `received_lsn ≤ checkpoint_lsn` was CAUGHT broken at plan-time — 48-byte tail gap). **Closeout fixed 2 should-fix + spec-doc: CV1/CV2 (`9dd1ec5`) — the single `in_txn` boolean under-counted proto-v2 CONCURRENT + savepoint streamed txns → `open_streams` xid-keyed MapSet (StreamAbort closes only on whole-txn abort); CV1 was a CROSS-VENDOR (Codex) UNIQUE catch, missed by all 5 same-family lenses (not constructible loss — §3.2 commit-boundary backstop — but the primary invariant was wrong). A2 (`34a208d`) idle-advance telemetry tagged `kind: :idle`.** Every task RED-before-GREEN; 3 live marquees each tamper-proven non-vacuous; 0 tier escalations. **Standby marquee UNRUN (no substrate) — named coverage gap (spec §3.5).** |
| A2 | **Logical decoding `Message` ('M') decode** | Outbox-pattern + heartbeat-table consumers (`pg_logical_emit_message`). Decoder has no `Message` struct (frames fall to `Unsupported`); `START_REPLICATION` omits `messages 'true'`. Composes with A1. | [messages.ex](../lib/replicant/decoder/messages.ex) (14 types, no `M`); [query_builder.ex:40-47](../lib/replicant/query_builder.ex) | 🏗️ **BUILT 2026-07-12** (exec-autopilot; 10 of 12 tasks executed — Task 11 now CLOSED at closeout, see below; range `b75f202..e7682b3`, 11 commits). **Gate battery VERIFIED green at HEAD `e7682b3` (clean tree): 519 tests/0 failures (66 excluded integration — no live PG), dialyzer 0, credo 1386/0, compile --force --warnings-as-errors + format clean.** Opt-in `messages: true`; a sink missing `handle_message/2` is rejected at START (`:messages_unsupported`, never silently dropping later). **Guarantees (Critical Rule 3 honesty):** transactional messages (`flags=1`) ride `%Transaction{messages: [...]}` and inherit the txn path's `commit_lsn` effect-once dedup; **non-transactional** messages (`flags=0`) route to `handle_message/2` and are **at-least-once — no dedup key, duplicates possible on reconnect** (stated in the `handle_message/2` docstring). Two durability seams prevent silent loss: idle-ack `track_txn` bump (§8.1 — a non-txn message in flight blocks the idle advance, so keepalive can't advance `confirmed_flush` past an undelivered message) and the batch-boundary `{:flush_before_message}` seam (§8.4 — a non-txn message flushes an open sink-owned batch in delivery order). `%Message{}` struct (`transactional?`, `lsn`, `prefix`, `content`, `xid`, `ordinal`) decoded by v1 + streamed clauses; `messages` flag threads to `start_replication`. ⏸ **Task 11 DEFERRED (non-blocking handoff):** the real-byte `'M'` conformance-capture fixtures (corroboration layer in `conformance_test.exs`) were NOT captured — three capture approaches failed (`pg_logical_slot_peek_changes` returns `text` mangling non-printables; `pg_recvlogical` across docker exec = 0-byte files; a postgrex `ReplicationConnection` capture timed out without `:xlogdata` frames). The decoder IS conformance-validated (Task 5's hand-crafted bytes prove the parser; the existing real-byte fixtures corroborate the shared decode path) — the independent `'M'`-specific real-byte second layer was missing. ✅ **Task 11 now CLOSED (2026-07-14 closeout):** real `'M'` bytes (non-txn `flags=0` + txn `flags=1`) captured from live PG16.14 via a `Postgrex.ReplicationConnection` and added to `conformance_test.exs` (tamper-proven) — the ReplicationConnection capture works; the prior "timed out / uncapturable" claim was false on probe. ✅ **CLOSED-OUT 2026-07-14** (forge **T2** closeout, live PG16.14 @:5599; commit `5b3680f`) — fresh-context spec-conformance + **security** + **gate-integrity** lenses + **cross-vendor Codex**. **Caught + fixed a REAL silent-loss bug** (spec §7.1): a proto-v2 **streamed** txn carrying only a transactional message with zero published row-changes was empty-suppressed → the message was DROPPED (v1 delivers it). Fix in `assembler.ex` `deliver_or_skip_stream` (suppress only when no surviving messages), **red-first regression test** in `assembler_test.exs`; **cross-vendor (Codex) corroborated** the same bug independently. **Security lens: Critical Rules 1/2/3 empirically CLEAN** (malformed `'M'` → `:decode_failure` no byte leak; SQL-injection attempts rejected; guarantee-honesty docstring accurate). The integration marquees (`messages_test.exs` 7/7, `multipub_test.exs` 3/3) were driven to non-vacuity for the FIRST time — **3 never-run-marquee test-harness bugs fixed** (row-shape, hand-rolled cp-table missing `updated_at`, hard-coded `commit_lsn=1`). Final battery `mix test` **586/0 live PG16** (2 `:pg17` excluded), dialyzer 0, credo 1388/0, compile `-Werror` + format clean. [ADR-0001](adr/0001-logical-decoding-messages-delivery-guarantees.md). 2 advisory follow-ups ✅ **COMPLETED 2026-07-14 in `7df3b69` (T1, fresh-context reviewed clean):** streamed-message `ordinal` now shares the per-txn counter with changes (interleaves correctly, in-memory + spilled — mirrors v1); `[:message,:received]` telemetry now fires for txn messages (`transactional: true`) across all delivery modes. **Record correction: HEAD `71594ea`'s `(A2+A3 closeout)` subject was docs-only; the closeout review actually ran in `5b3680f`.** |
| A3 | **Multi-publication per pipeline** | `publication_names` accepts a comma-list per pgoutput; config takes exactly one `:publication`. Small config-surface change, real capability. | [config.ex:51](../lib/replicant/config.ex); [query_builder.ex:47](../lib/replicant/query_builder.ex) | 🏗️ **BUILT 2026-07-12 = COMPLETE** (exec-autopilot; 4 of 4 tasks, range `b75f202..9c13b2b`). `publication: String \| [String]` — a single string is the byte-unchanged default path; a list enables multi-publication, each name validated via `Identifier.validate/1` and normalized to a list. `start_replication/3` + the four discovery queries (`publication_tables/1`, `pk_columns/0`, `table_columns/0`) bind `DISTINCT ... pubname = ANY($1)`; `publication_exists/1` interpolates a validated `IN (...)` list (the connect-chain simple-query protocol can't bind `$1`). A NEW connect-chain `:publication_check` step **fails closed if the found-pubnames set ≠ the requested set** (decision #18 — `START_REPLICATION` with a missing pub silently streams the subset). pgoutput dedups overlapping tables across publications (probe-verified). In the same gate battery as A2. ✅ **CLOSED-OUT 2026-07-14** (forge **T2** closeout with A2, live PG16.14 @:5599; commit `5b3680f`) — fresh-context spec-conformance + security + gate-integrity + **cross-vendor Codex**, all CLEAN on the A3 surface. Probe-verified live: DISTINCT discovery dedup (a table in two publications → `pk_raw = {id}`, not `{id,id}`) and the fail-closed `:publication_check` (found set ≠ requested → `halt_publication_missing`, stay-idle, value-free). The multipub **snapshot-discovery marquee** was fixed (the sink lacked `handle_snapshot/2` + `handle_snapshot_complete/1`) and now runs a REAL snapshot proving the DISTINCT union delivers the shared table exactly once. [ADR-0002](adr/0002-multi-publication-per-pipeline.md). Same battery: `mix test` **586/0 live PG16**. |
| A4 | **`origin` filter option** | Bidirectional / blue-green replication setups (pgoutput PG16+ `origin 'none'`). Absent from the options builder. | [query_builder.ex:40-47](../lib/replicant/query_builder.ex) | Open |
| A5 | **PG17+ forward-compat + failover slots** | Invalidation detection is PG16-keyed (`wal_status`/`conflicting`); PG17 adds `invalidation_reason` (+ `failover`/`synced`). PG17 `failover = true` slots → HA resume-on-promoted-standby. **Correction: "CI matrix is PG16-only" was false — there was NO CI; added in this slice.** | [query_builder.ex:123-127](../lib/replicant/query_builder.ex) (comments acknowledge the PG17 divergence) | 📐 **DESIGNED 2026-07-11** (brainstorm-autopilot; probe-grounded on live PG16.14 + disposable PG17.10). Fresh-context reviewed — 7 challenges folded: grammar **reversed to dual/byte-safe** (published default path untouched, `FAILOVER` only on the opt-in parenthesized path), PG16-failover **stay-idle** halt (mirrors `halt_store_permanent`), **synced-unpromoted** fail-closed halt (livelock→halt), `invalidation_reason` as authoritative fail-closed signal (fixed atom map, Rule-1 clean, forward-robust), executed **dual-version DoD** + **GitHub Actions PG16+PG17 matrix**. 🏗️ **BUILT 2026-07-11** (exec-autopilot; 11 tasks [8 opus / 3 sonnet] + hotfix A1, per-task two-stage opus review, 3 review-fix rounds, 0 tier escalations). Gate battery VERIFIED green on BOTH substrates (HEAD `24c5670`, tree clean): unit **484/0**, PG16 integration (path-scoped, isolated) **52/0**, PG17 integration (isolated) **56/0** (pg17 tests EXECUTED, not skipped), compile `--force --warnings-as-errors` + format clean, credo **1291/0**, dialyzer **0**; gate-logs `20260711-09*-a5-*`. **Task 8's PG16 integration run CAUGHT a real regression** — replication-connection results arrive as TEXT (`"160014"`, `"t"/"f"`), so the version gate (`"160014" >= 170000` = true) forced the PG17 4-col query on PG16 → `undefined_column` → reconnect storm on every PG16 pipeline; **hotfix A1 (`76ffc8d`) coerces version/in_recovery/conflicting/synced at the connect boundary** (also fixed a pre-existing latent `conflicting`-text bug). Dual/byte-safe grammar (published default path byte-unchanged), PG16-failover stay-idle halt, synced-unpromoted halt, `invalidation_reason` authoritative signal, GitHub Actions PG16+PG17 matrix (fails on unready substrate). Commits `3a5ff8d..24c5670` + closeout fixes `af6d8fd`, `520906a`. ✅ **CLOSED-OUT 2026-07-11 = 100/100** (`/review-autopilot --fix`, fresh-context grader-concurred, zero open) — 6 read-only lenses + worktree-isolated tamper pass (all 5 safety-property tests RED-capable, zero vacuous) + independent grader. 2 should-fix fixed: test_helper `server_version_num/0` now **fails closed** when `REPLICANT_TEST_URL` is set-but-unreachable (was fail-open → vacuous-green risk) `af6d8fd`; spec amended with the A1 text-coercion mechanism + §2/§5.1 corrections. 1 advisory fixed: orphaned `is_in_recovery/0` re-homed to `incremental.ex` `standby?/1` (de-dup + de-orphan) `520906a`. Final battery green on BOTH substrates (HEAD `520906a`, tree clean): PG16 **538/0**, PG17 **540/0** (pg17 EXECUTED, 0 excluded), credo **1291/0**, dialyzer **0**; gate-log `20260711-102409-a5-closeout-final`. Cross-vendor SKIPPED (Codex rate-limited, 0 probes). The 1st battery's 2 `streaming_spill` timeouts were **load-induced flaky** (untouched subsystem; isolated re-run 4/0, quiet-machine battery 0 failures) → backlog-routed to widen the marquee timeout. |
| A6 | **Replication-command-error fail-closed policy** | A persistent `%Postgrex.Error{}` from a replication command currently `{:disconnect, :query_error}` → `auto_reconnect` livelocks (no halt, no surfaced cause). Bounded-retry-then-halt like §14.18 store-fault-retry, without inspecting the value-free error (Rule 1). | [connection.ex](../lib/replicant/connection.ex) `handle_result(%Postgrex.Error{})` | ✅ **CLOSED-OUT 2026-07-14 (forge T2, live PG16.14 @:5599)** — a chain-progress watchdog counts established-then-dropped connect cycles in `handle_disconnect` (store-retry exempt), enforces `max_command_retries` (default 5, `0`=halt-now) in `handle_connect` before any store read via the shared `CheckpointStore.retry_decision/2`, resets on the first replication frame (`?w`/`?k`), and stay-idle halts emitting value-free `[:connection, :command_error_halt]`. Bounds the WHOLE pre-frame class (create-slot, START_REPLICATION, `:unexpected_result`), not just `handle_result` — server-down + post-frame transients still self-heal. Design adversarial pass caught+fixed a broken first mechanism (counter-through-`{:disconnect}` never persists); plan review + per-task review + live marquee (halt at attempt=budget+1, budgets 2→3 & 5→6). Range `9ca211a`+`981252e` (+ core `23ecf47..b14c760`); local Forge design and plan receipts retained under `.forge/`. |

### B. Spec-named future slices (owners assigned in shipped specs)

| # | Item | Unlocks | Named in | Status |
|---|---|---|---|---|
| B1 | **`replicant-snapshot-incremental`** — resumable / incremental snapshot (DBLog-derived chunking adapted to READ-ONLY sources, interleaved with the stream) | Large-table onboarding: today a crash at 99% of a multi-hour COPY redoes everything (all-or-nothing per attempt, exported snapshot dies with its session). The biggest adoption blocker. | snapshot spec §3 | ✅ **CLOSED-OUT 2026-07-10 (`/review-autopilot --fix`, 8 lenses + cross-vendor Codex + fresh-context grader) = 100/100 (excellent) — 3 fix rounds (69→83→100) + Tasks 12–13 built + all findings closed.** HEAD `3ecee0a`, battery VERIFIED green (unit+integration **497/0** live PG16, dialyzer 0, credo 1242/0, format/compile clean, tree clean; gate-log `20260710-205502-inc-t12t13-final-3ecee0a368`). **ALL THREE BLOCKING data-loss defects the review found are FIXED:** (1) DELETE/TRUNCATE during backfill crashed the AssemblerServer via `pk_tuple(nil, …)` OUTSIDE the value-free rescue (OTP row-value leak = **Rule 1 breach**) + ghost rows → `545fb06` (PK from `old_record`; truncate→taint); (2) **cross-vendor UNIQUE** — PK-changing UPDATE resurrected the old key → `9f54f06` (track BOTH keys); (3) **CV2** — a keyed table's buffered final chunk discarded after the reader marked it done = loss → `b795650` (keyed final-chunk completion barrier). Earlier: `8c8c7db`/`196140c`/`b67ee6c` (keyless data-loss fixes) + `a9a6e1f` (lib+batch PK-retention drop-filter, LIFTED the interim lib-batch×incremental rejection) + `4eca198` (§9 chunk_retried / compressed-token DoS guard / bounded last_buffered_changes). **F7 RESOLVED** (`dd8f920`, user-ratified): resume no longer backfills a mid-run-added table (matches §3/§18 non-goal). **TASKS 12–13 BUILT + all findings closed:** §12 live-marquee matrix filled — PK-less contended (`:snapshot_table_contended`+retry), completion at-least-once (lib-mode resume + durable `complete?`), batch_delivery×incremental effect-once (append-only chunk-PK ledger), `:sink_too_slow` §4 backpressure DURING backfill — all non-vacuous / red-capable on live PG16 (`43eb452`, `3ecee0a`); feature docs added to README + `Replicant` moduledoc + usage-rules (`7c6c064`). Notes closed: post-completion window-clear + real `:completed` duration + `close_table` dead-code removed (`74f2ccc`); cross-run test isolation (drop `cp_`/`prog_`/default-progress row on_exit) + resume-marquee timeout + `PG16.wait_until` doc (`3ecee0a`). RI-USING-INDEX-non-PK delete edge routed B2-adjacent (PK-keyed drop-set correct under default RI=PK/FULL; diagnosis on-disk). Fresh-context grader **concurred 100/100 zero-open**; spec closeout amendment + 8-lens reports + 25-finding table on-disk (gitignored `superpowers/`). |
| A6b | **SQLSTATE-class fast-halt for command errors** | Halt IMMEDIATELY on an unfixable replication-command error class (`insufficient_privilege` / `syntax_error` / `undefined_object`) instead of burning the full `max_command_retries` budget — a halt-latency optimization on the one channel (`handle_result(%Postgrex.Error{})`) that carries a SQLSTATE. Rule-1-conformant via a SQLSTATE→fixed-atom map (the landed `invalidation_reason_atom/1` precedent), so deferred on scope, not prohibition. | A6 design §Non-goals + A5 spec §11 | Open — named by A6 (2026-07-14); the count-based A6 watchdog already bounds every channel correctly |
| B2 | Filtered / column-projected snapshot | Subset snapshots beyond the publication's natural scope | snapshot spec §3 | Open |
| B3 | Parallel multi-table COPY | Snapshot throughput (v1 is serial) | snapshot spec §3 | Open |
| B4 | Proto v3 two-phase streaming (`Stream Prepare`) | 2PC-aware pipelines | streaming spec §3 | Open |
| B5 | Proto v4 parallel streaming | Concurrent apply of interleaved streams | streaming spec §3 | Open |
| B6 | At-rest spill encryption | In-lib blanket encryption of spill files ("a clean, unblocked future addition"); key management is the separable subsystem | spill spec §3 | Open |
| B7 | Spill-file compression | Disk/CPU trade-off under the spill format | spill spec §3 | Open |
| B8 | Checkpoint-read cache | Eliminates the per-Commit live `sink.checkpoint()` read; deferred pending profiling evidence that the read matters | batch-delivery spec §3 | Open — profiling-gated |
| B9 | Snapshot column metadata via context | Full column metadata at snapshot time (values arrive natively typed today, so none is needed to interpret them) | snapshot spec §138 | Open |

### C. Ecosystem / adoption layer (sibling-lib candidates)

| # | Item | Unlocks | Status |
|---|---|---|---|
| C1 | A proposed `metrics/0` helper on `Replicant.Telemetry` (`Telemetry.Metrics` definitions) | Oban/Broadway convention — 30 structure-only events exist; every consumer currently hand-authors metric definitions | Open |
| C2 | Broadway producer adapter | Opens replicant to the dominant Elixir data-pipeline ecosystem | Open |

### Closed boundaries (cited, not silently dropped)

| Item | Ruling |
|---|---|
| Multi-sink fan-out per slot | Won't-build, user-ratified (core spec §3; row above) — compose a dispatching sink or run multiple slots |
| fsync'd / durable spill | Never, by design (spill spec §3) — spill is scratch, not a WAL; crashes re-stream |
| Incremental streaming sink contract | Obviated (spill spec §3) — lazy whole-`Transaction` `changes` delivers the same memory capability with atomicity preserved |

## Why `ash_replicant` was the #1 value item (retrospective)

Value-ranking rationale, retained now that it has shipped as 0.3.0:

- **It is the reason the core exists.** README states it outright: replicant is
  "the reliable CDC consumer sibling to `arcadic`," with multitenancy,
  classification, and Ash resources living "one layer up, in the
  `ash_replicant` sink adapter." Without it the core had no first-class
  consumer — `ash_replicant` 0.3.0 is now that consumer.
- **The other slices optimized a primitive that already worked.** Checkpoint-store,
  batching, and proto-v2 each added value only under a specific pressure
  (non-transactional sinks / throughput / unbounded single-txn size); none
  delivered new end-user capability the way the consumer layer does.
- **Sequencing note.** Despite the #1 value ranking, the user directive built the
  §3 slices *first* (one in-build project at a time) and gated `ash_replicant` on
  the `replicant` publish — which is how it actually shipped.

## 1.0.0 Release hardening — readiness assessment (2026-08-12)

A four-lens assessment (architecture, security, coverage, API-stability) of the
0.3.1 core against a 1.0.0 bar. The core is release-ready — no P0 in library
behavior. The rows below are the gaps to close at/around the 1.0 tag. Status
here is authored narrative (this section pre-dates forge-roadmap schema
migration); the slug is the slice join key.

| ID | What | Acceptance | Depends | Why |
|---|---|---|---|---|
| D1 | **Stale install constraint** — README + getting-started Livebook ship `{:replicant, "~> 0.2"}`, which resolves `< 0.3.0` and locks users out of every 0.3 feature (and 1.0). slug:d1-install-constraint | `README.md` + `notebooks/getting_started.livemd` show `~> 1.0`; grep finds no `~> 0.2` / `~> 0.1.0` install reference | — | Currency — the most-copied snippet |
| D2 | **Coordinate the Replicant/AshReplicant major contract** — Replicant 1.0 must expose authoritative actual-session identity before checkpoint lookup, and AshReplicant must not admit a 0.3 install that lacks it. slug:d2-ash-replicant-coord | Replicant's fetched package exposes `SessionIdentity` and `handle_session_identity/2`, the live callback reports system/database identity from the exact replication connection before checkpoint lookup, AshReplicant requires Replicant `>= 1.0.0 and < 2.0.0-0` and tests exact 1.0.0/its committed lock/latest compatible; Replicant releases first, while AshReplicant publication remains blocked on its own completed release roadmap and explicit authorization | D1 | Release coordination — a 1.0 that orphans its consumer or permits an unsafe downgrade |
| D3 | **Release hygiene** — toolchain/CI skew, a previously red format gate, an unenforced audit alias, and a vulnerable Postgrex floor cannot ship in 1.0. slug:d3-release-hygiene | `.tool-versions` pins Elixir 1.20.3-otp-29 / Erlang 29.0.3; CI matches it; format, audits, and gate red-probes pass; Postgrex is at least 0.22.4; actions and database images use immutable revisions | — | A red gate and an unpatched dependency cannot ship 1.0 |
| D4 | **`:batch` type trapdoor** — `Replicant.Config.t` lists `optional(:batch)` but `fetch_batch/3` rejects a top-level `:batch` with `:config_invalid` (it is derived). slug:d4-batch-type-trapdoor | The public type no longer advertises a key the user cannot set; dialyzer + compile clean | — | Freeze the type honestly |
| D5 | **v1 snapshot casting divergence** — `snapshot: true` runs `SELECT *` and zips raw Postgrex-decoded values (no cast), so a `timestamp` arrives as `NaiveDateTime` from the snapshot and `DateTime` from the stream; the incremental path was fixed (`::text` + `cast_record`) but v1 was never back-ported. slug:d5-snapshot-casting | A red-first convergence test proves a typed column delivers the SAME runtime type from v1 snapshot and the stream; v1 routes values through the shared `Casting.Types.cast_record/2` (or v1 is explicitly deprecated); value-free boundary intact | — | Convergence correctness (Critical Rule 1 boundary preserved) |
| D6 | **`%Transaction.changes` spill hazard** — typed `Enumerable.t()`, but a spilled txn is single-pass / call-lifetime; a sink authored against List breaks on its first oversized spilled txn. slug:d6-changes-spill-type | The type names both forms as either `[Change.t()]` or `Spill.Reader.t()`, and a `@typedoc` states the single-pass contract; dialyzer clean | — | The biggest latent data-contract hazard |
| D7 | **Hex description overclaims exactly-once** — `mix.exs` description ends "…exactly-once delivery" unqualified; the lib has three at-least-once paths. slug:d7-hex-description | The Hex blurb qualifies exactly-once to the sink-owned/transactional path (Critical Rule 3) | — | Honesty on the most-visible surface |
| D8 | **Foundational ADRs + published Critical Rules** — `docs/adr/` has only 0001/0002; the load-bearing posture decisions (value-free boundary, commit-LSN transaction watermark, spill-as-ephemeral-scratch, `:one_for_all`+`:temporary` supervision) are unrecoverable from code alone; the 5 Critical Rules live only in the un-tarballed `AGENTS.md`. slug:d8-foundation-adrs | Four ADRs land under `docs/adr/`; a tarball-shipped invariants doc (in `mix.exs` `extras`) carries the 5 Critical Rules for a Hex consumer | — | A 1.0 is the moment a bare-clone maintainer must recover the WHY |
| D9 | **Three vendored public functions unspecced** — `Casting.Types.cast_record/2` (the central casting contract), `Casting.ArrayParser.parse/1`, `Decoder.OidDatabase.name_for_type_id/1`. slug:d9-vendored-specs | All three carry `@spec`; the frozen public surface is fully specced; dialyzer clean | — | Freeze the contract |
| D10 | **Named-ctrl-conn cascade masked the integration suite** — every integration module started a NAMED Postgrex pool in `setup` and never stopped it; ExUnit's `async: false` one-process model then made test 2+ fail with `{:already_started, _}`, silently masking ~half the suite (the full run was 66/31). Found while verifying D5. slug:d10-named-conn-cascade | `PG16.named_conn/2` centralizes per-test isolation (start unlink + register on_exit stop); all 23 named-pool sites route through it; the full integration suite is 66/0 (was 66/31) | D5 | A masked integration suite is not 1.0-grade correctness evidence |

### Status (updated 2026-08-19; 1.0 closeout 2026-08-13)

**All Replicant-side requirements across the ten rows shipped in 1.0.0** (`v1.0.0`):
D1 (install constraint), D4–D10 closed in the hardening run, and the two 2026-08-13
major-contract rows — D2 (actual-session identity:
`Replicant.SessionIdentity`, `handle_session_identity/2`, the `IDENTIFY_SYSTEM` connect step,
ADR-0007) and D3 (release hygiene: `.tool-versions` pins Elixir 1.20.3-otp-29 / Erlang 29.0.3,
CI matches and uses immutable action/image revisions, `mix audit` is a gate, postgrex floors at
`~> 0.22.4`) — both verified against the fetched package. D2's consumer-side source coordination
is now complete
at AshReplicant `e4072fb95975c9588914d9aefe99ce1fa0098dfb`: minimum and
latest-compatible lanes are defined, the committed lock is 1.2.2, and the broad
requirement admits 1.2.3; the generated sink rejects actual-session identity drift
before checkpoint lookup. AshReplicant publication remains a separate release action.
**1.2.3** (`v1.2.3`) kept
acknowledgements bound to durable delivery. **1.2.2** (`v1.2.2`) closes the pre-first-chunk
incremental-backfill restart gap. **1.2.1** (`v1.2.1`) bounds keyed snapshot contention and
hardens post-publication package identity. **1.2.0** (`v1.2.0`) added the fail-closed
unknown-checkpoint/absent-slot halt, typed telemetry shapes, logical-message value-safety,
the `handle_slot_origin/2` callback, and proven PostgreSQL 15–18 support. **1.1.0**
(`v1.1.0`) remains the preceding post-1.0 hardening patch:
float-array casting accepts all valid PostgreSQL text forms, post-halt snapshot-window calls
return `{:error, :window_reset}`, and snapshot reader connection options use library-wins
merging (see CHANGELOG `[1.1.0]`).

### P2 backlog (post-1.0 hardening)

Not blocking the freeze; tracked here so nothing is lost. ✅ = shipped in the 1.0-hardening run.

- ✅ **Reader "exactly-one" invariant — structural guard** (was comment-defended): `Incremental.register_reader/1` registers under `{:incremental_reader, slot}` in `Replicant.Registry`; a forgotten `retire_reader/1` halts fail-closed (`:duplicate_reader`) instead of double-delivering. `37ab768`.
- ✅ **`assembler.ex` god-module split DONE** — extracted `Assembler.Streaming` (proto-v2 streaming + spill, 398 LOC) and `Assembler.Batch` (batched-checkpoint buffering + flush, 230 LOC); Core is now 1072 LOC (was 1633), `%Assembler{}` struct UNCHANGED. **Not mechanical** — the move carries a cross-cutting Rule-1 scrub cluster (every sink-call site keeps its value-free `rescue`/`catch`), a runtime-resolved Core↔Batch call cycle (Core routes to Batch; Batch calls Core's `reset`/`span_base`/`safe_shape`/`write_checkpoint`), and 8 made-`def` internal seams (`@doc false`); it is the lowest-risk path to review-fidelity — pure functions on an unchanged struct, verified green at every commit by the 545-unit + 613-integration preservation net (no behavior change). Closeout HEAD `f99ce14` (range `481d54d..f99ce14`, 7 commits): forge T2; fresh-context diff-review CLEAN; cross-vendor codex+claude CLEAN (one stale-comment fix landed); crash-injection marquees loss=0 / effect-dup=0 green against live PG16.
- **B8 checkpoint-read cache** — `Assembler.skip?/2` does a live `sink.checkpoint()/0` read per Commit (sink-owned clause in `lib/replicant/assembler.ex`); the throughput floor in sink-owned mode. Profiling-gated.
- ✅ **Duplicated batch flush-trigger `cond`** — extracted to `Assembler.maybe_trip_batch/3` (lib-batch and sink-owned-batch trip identically; no drift). `55316e9`.
- ✅ **Spill cleartext-at-rest threat model** — documented in ADR-0005 (D8) + README "Operator guidance".
- **`Replicant.Config.t()` is a map, not a struct** — ✅ frozen: `@typedoc` states it is a plain map by contract (built only by `validate/1`; adding keys allowed, renaming/re-typing is 2.0). `d0ebed4`.
- **`handle_batch/1` arity** — ✅ frozen: `@doc` states arity-1 is the 1.0 contract (no context arg; the batch high-water is `List.last(transactions).commit_lsn`; a context arg is a 2.0 callback change). `d0ebed4`.
- ✅ **Conformance tamper-evidence** — machine-checked: a parametric byte-flip test (type byte + sampled payload) per message class proves each fixture goes red on mutation. `b04b4d7`.
- ✅ (corrected) **Effect-once / loss=0 / §4 unit proof** — the readiness test-audit over-claimed "zero unit-level proof"; probing the suite shows the invariants ARE unit-covered: watermark-skip (`assembler_test.exs:188-246`), batch write-fault + watermark discipline (`:791-800`), `reset_batch`/reconnect-discard (`assembler_server_test.exs:405-470`), the §4 `:sink_too_slow` halt + first-frame no-false-halt (`connection_test.exs:295-362`), idle-ack ordering (`:200`). Crash-injection is integration-only by nature (the logic is unit-tested; the end-to-end marquees prove it under real reconnect). No gap to close.
- ✅ **`Replicant.SnapshotProgress` ungrouped in HexDocs** + three public fns had `@spec` but no `@doc` — SnapshotProgress grouped under "Data structures"; `@doc` added to `Replicant.Supervisor.start_link/1`, `Telemetry.span/3`, `Telemetry.event/3`. `d0ebed4`.
- ✅ **Telemetry test-noise** — silenced the `:telemetry` attach-time `:info` advisory by setting the test Logger level to `:warning` (one line; replicant emits no Logger calls, so nothing useful is hidden). `a6f39f5`.