---
name: a-barrier-cannot-ride-a-carrier-its-sender-outlives
description: "A completion barrier whose sender exits immediately after cannot ride pull-based replication — the peer dials a gone endpoint for its whole window; match the carrier to the publisher's lifetime."
metadata: 
  node_type: memory
  type: feedback
  originSessionId: 1ee6de80-4680-4fb2-bd69-5b132747670f
  modified: 2026-09-08T10:53:54.810Z
---

Retiring a transport (XFER, releases#246) took away three data the two-host ladder used it
for as a SIDE-CHANNEL: B's two session ids, and A's completion barrier. I replaced all three
with the ladder's own replicated notif rows — the same mechanism two other rungs already
depended on, so it looked like the safe, dependency-free choice. **The two sids were fine.
The barrier was not, and the difference is a property I did not think to ask about: whether
the SENDER is still alive to serve the pull.**

MEASURED (one-box rig, 2026-09-08, `.spt/twohost-ladder/w3-ladder-1/`): role A passed all 38
rungs, wrote the barrier row into ITS OWN store, and exited. Role B then sat out its entire
window dialling A — `PUMP_PEER_FAIL … stage=quic-connect: submit-dial exceeded the 10s bound
(peer unresponsive)`, on both the cache and roster legs. **role A exit 0, role B exit 100.**
The row never replicated because replication is a PULL and there was nobody left to pull from.
B's sids worked precisely because B stays up for the rest of the ladder after publishing them.

**Why:** the retired file push was SYNCHRONOUS — it did not return until the peer had the
bytes. Swapping in an asynchronous carrier silently dropped the one property the barrier
exists for. A barrier is a promise that the receiver HAS something; a fire-and-forget write
into your own store is a promise that you WROTE something.

**How to apply:** before replacing any cross-host carrier, ask *how long does the sender live
after this call?* If the answer is "it exits next", the carrier must be synchronous and must
return the receiver's own answer — here `request_wan` with `sent.took_custody()` asserted, the
rig's already-proven seam. Reusing a mechanism that works elsewhere in the same file is NOT
evidence it works here; the two uses differed in sender lifetime, which no amount of reading
the file would have shown. Related: [[never-send-a-claim-composed-before-its-check-ran]].

**The gater was right and it paid on the first run.** doyle refused to accept the risk I
flagged and added a rig leg for the rewrite (`.spt/rig/twohost-ladder-local.sh`, one-box pair,
ports A 7460 / B 7470 / C 7462). A rewritten cross-host barrier that no leg RUNS is
unwitnessed by construction — a source read cannot witness an int cell. Flagging a risk and
then shipping under it is not flagging it. Keep both raw dirs: the red run and the green run
at otherwise-identical trees are the falsifier for the fix.
