Two meanings of replay
The last lesson replayed the log to rebuild the mirror. This one is about the other replay: a client sending the same request again because it never heard the answer. A phone on a weak link sends a moment, the engine records it, and the reply dies somewhere on the way back. From the phone's side nothing happened, so it tries again. If the engine treated that second request as new, the corpus would hold the same photo twice, Pippa would read it twice, and Dad would see two moments for one sign.
Spark's answer is that every create carries identity the client chose before it ever touched the network, and the engine answers a known identity with the record it already has.
Ids are born at capture
When Dad takes a photo, the phone mints everything the send will need, right there, before any request: a ULID for the moment, a ULID for the small copy Pippa reads, a ULID for the full-size original, a request id for the moment's create and another for the practical turn. Then it puts the whole capture in an outbox on the device. The comment on the capture method puts it plainly: the moment is safe the moment this returns without throwing; its id is final already.
The web client does the same for what it creates, minting the moment's ULID in the browser with the family kit's TypeScript copy. The engine mints an id itself only when a caller sends none, such as a quick-recall session it assembles on its own.
Why insist on this, when the request id alone already catches a repeated create? It does, and a test below proves it: the moment is created with no id, the engine mints one, and the same request id sent again still returns that first moment. The phone mints the moment id anyway because a capture made offline needs an identity before any reply exists. Its copy, its original and its turn wait in the outbox under that id. Media is stricter still. An upload carries no request id at all, only its phone-minted media id, so a server-minted media id really would turn a lost reply into a second file.
The send, step by step
The third code block is the contract of the phone's send. It goes in the order that shows Pippa's answer soonest: the moment, the small copy, the turn, and only then the heavy original. Each step is marked done in the outbox when it succeeds and skipped on a retry. Each is also idempotent on the engine side, so a step that succeeded while its reply was lost is harmless to repeat:
| Step | What makes a repeat harmless |
|---|---|
| Create the moment | The phone-minted moment id, and the outbox entry's id as the request id |
| Upload the copy | The phone-minted media id plus a hash of the bytes |
| Start the practical turn | The request id minted at capture |
| Upload the original | Its own media id plus a hash of the bytes |
A permanent 4xx answer parks the entry where Dad can see it, and anything else waits for the next pass. The Outbox comment in this lesson's code says every 4xx parks, but the kit's drain decides, and it treats 401, 403, 408, 425 and 429 as passing trouble that waits like a dropped link (track 9 tells why).
Two keys on the engine
The first code block is the top of create_moment. It checks two independent keys before writing anything. First the request id: if an earlier op carried it, return that moment with created: false. Then the moment id: if a moment with that id exists, deleted or not, return it the same way. Only a request that passes both becomes a moment.create op. Two tests hold each key separately: one creates twice with the same phone-minted id, the other twice with the same request id, and both assert one moment and exactly one line in the log.
The request key lives in the mirror, which the second code block fills. Every op that carries a client_request_id leaves a row in requests mapping that id to what it produced. It is INSERT OR IGNORE, so the first producer wins forever. And because the row is written by the fold, it is derived state like everything else: rebuild the mirror and the idempotency memory comes back with it, straight from the log.
Turns use the same table. Ask for a practical reading with a request id the engine has seen and it returns the turn it already started, running or finished, and launches nothing. A test sends the same practical request twice and asserts the second answer is the first turn and that Pippa was asked exactly once.
Media answers by content. A replayed upload under a known media id must carry the same bytes; the store compares SHA-256 hashes and returns the existing record. The same id with different bytes is a client bug, and the engine refuses it with a 409 instead of guessing which one to keep.
Ids that derive from ids
Some requests fan out. Quick recall creates a moment and then starts a turn on it, so the turn's request id is the recall's id plus :turn. The route caps an incoming recall id at 70 characters so that the suffix still fits the family's 80-character ceiling. A high-quality job starts one turn per brain seat, each <job id>:<index>, with the job id capped at 64 for the same reason. A retried fan-out lands on the same derived ids and duplicates nothing.
Where the doc and the code differ
The architecture doc says every engine write carries a client request id. The code is narrower, and it is the code you should learn from. Creates and turn starts carry one. Keeping a phrase does not need one, because its id is built from the text (keep: plus the normalized phrase) and keeping it twice finds the active mark and writes nothing. Deleting a moment twice finds nothing live the second time and writes nothing. A repeated edit records the same values again, which changes nothing a reader sees. Each write is idempotent; they reach it by different routes.