Take an Ack as the read position only after the catch-up #151

Merged
cruelacid merged 1 commit from worktree-nec-87-ack-floor into main 2026-09-24 16:31:38 +01:00
Owner

Task: NEC-87

A guest joining a shared folder never received a file the owner created at the same moment (CI runs 531 and 660). Run 660's TRACE_BROADCASTS trace shows the cause on the folder listing:

14:18:
23.847 owner pushes the new file's entry; broadcast to nobody, because the guest hasn't subscribed yet
23.867 guest pushes to the listing before its Subscribe has gone out; the server registers the socket and acks at seq 4
23.869 guest subscribes from 4, so the catch-up is empty and the owner's seqs are never sent

The Subscribe waits for the IndexedDB checkpoint and the push doesn't. The Ack handler took the acked seq as the read position, which is only valid for a socket that was already receiving the document. The position is persisted, so the loss was permanent and silent.

Change: an Ack moves lastSeq only once the connection's catch-up has ended (sub.synced). Before that, the SyncStatus sets it. Safe in either order, because the server registers the socket on the push and acks after the broadcast.

  • WIRE-034 reworded; provider-framing.test.ts covers it, including the exact CI ordering and a reconnect. Mutation-checked: dropping the synced guard fails the two new tests, and never advancing fails the two existing ones.
  • The decrypt-gap.test.ts Ack test now syncs first, so it still fails only on the gap guard (mutation-checked).
  • Server: comment only. It overstated what ack ordering guarantees.
  • docs/sync-limitations.md entry is now marked fixed, with the residual: plugin 0.1.5 keeps the bug, so the e2e compatibility pass can still hit it until a fixed release replaces 0.1.5. The e2e README says how to tell that apart from a new failure.

Local: pnpm test, typecheck, lint, build-mirror --check, spec-delta --check, conformance --check, and test:e2e:multi (69 passed; the local run has no 0.1.5 pass). The built mirror bundle contains the guard.

Changelog

Fixed a race condition where a note another member created just as you joined a shared folder could silently never reach your vault.


🤖 Generated with Claude Code

https://claude.ai/code/session_01Q6gkmHZ3pYvsZCCpiA9qe9

Task: NEC-87 A guest joining a shared folder never received a file the owner created at the same moment (CI runs 531 and 660). Run 660's `TRACE_BROADCASTS` trace shows the cause on the folder listing: | 14:18: | | |---|---| | 23.847 | owner pushes the new file's entry; broadcast to nobody, because the guest hasn't subscribed yet | | 23.867 | **guest pushes** to the listing before its Subscribe has gone out; the server registers the socket and acks at seq 4 | | 23.869 | **guest subscribes from 4**, so the catch-up is empty and the owner's seqs are never sent | The Subscribe waits for the IndexedDB checkpoint and the push doesn't. The Ack handler took the acked seq as the read position, which is only valid for a socket that was already receiving the document. The position is persisted, so the loss was permanent and silent. **Change:** an Ack moves `lastSeq` only once the connection's catch-up has ended (`sub.synced`). Before that, the SyncStatus sets it. Safe in either order, because the server registers the socket on the push and acks after the broadcast. - WIRE-034 reworded; `provider-framing.test.ts` covers it, including the exact CI ordering and a reconnect. Mutation-checked: dropping the `synced` guard fails the two new tests, and never advancing fails the two existing ones. - The `decrypt-gap.test.ts` Ack test now syncs first, so it still fails only on the gap guard (mutation-checked). - Server: comment only. It overstated what ack ordering guarantees. - `docs/sync-limitations.md` entry is now marked fixed, with the residual: **plugin 0.1.5 keeps the bug**, so the e2e compatibility pass can still hit it until a fixed release replaces 0.1.5. The e2e README says how to tell that apart from a new failure. Local: `pnpm test`, typecheck, lint, `build-mirror --check`, `spec-delta --check`, conformance `--check`, and `test:e2e:multi` (69 passed; the local run has no 0.1.5 pass). The built mirror bundle contains the guard. ## Changelog Fixed a race condition where a note another member created just as you joined a shared folder could silently never reach your vault. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Q6gkmHZ3pYvsZCCpiA9qe9
Take an Ack as the read position only after the catch-up
Some checks failed
CI / e2e (pull_request) Successful in 5m0s
CI / build (pull_request) Successful in 6m26s
CI / promote (pull_request) Has been skipped
CI / build (push) Failing after 1m13s
CI / e2e (push) Successful in 5m59s
CI / promote (push) Has been skipped
Release note / release-note (pull_request) Successful in 11s
b9366a9eaf
NEC-87. A guest joining a folder never received a file the owner created
at the same moment. Run 660's broadcast trace shows why: the guest pushed
to the folder listing before its Subscribe went out (the Subscribe waits
for the stored checkpoint, the push does not), the server registered the
socket on that push and acked it at seq 4, and the client took 4 as its
read position. The Subscribe then asked from 4, and the owner's earlier
sequences, appended before the socket was registered, were never sent
and never asked for again. The position is persisted, so the loss was
permanent and silent.

An Ack now moves the read position only once the connection's catch-up
has ended. Before that, the SyncStatus sets it. That is safe in either
order: the socket is registered when its push arrives and the Ack follows
the broadcast, so every lower peer sequence is in the catch-up or was
broadcast first.

WIRE-034 says so, provider-framing.test.ts defends it including the CI
ordering (mutation-checked both ways), and the decrypt-gap Ack test now
syncs first so it still fails only on the gap guard. The server is
unchanged apart from a comment that overstated the guarantee. Plugin
0.1.5 keeps the bug, so the e2e compatibility pass can still hit it until
a fixed release replaces it; sync-limitations.md and the e2e README say
how to tell that apart from a new failure.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q6gkmHZ3pYvsZCCpiA9qe9
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Nectenda/nectenda!151
No description provided.