fix(sync): scan events keep instance-wide fan-out as the sanctioned BroadcastToAll

Verifying the per-user chokepoint end to end surfaced a UI regression
it would have caused: web/src/header.ts runs the sitewide scan
indicator on scan_progress/scan_complete events in every browser, and
routing those to the triggering identity alone froze the indicator for
everyone else at its page-load state (active-summary stays the
load-time fallback, but live movement died). The source's own comment
declares the indicator sitewide by design, and scan payloads carry no
user data — job ids, file counts, progress percentages only.

Resolution honors both truths: BroadcastToAll is re-added as the ONE
sanctioned instance-wide fan-out, its doc comment making the decision
explicit (no user-derived data; anything carrying books, notes, or
annotations belongs in BroadcastToUser), and the two scan emitters use
it. TestBroadcastToAllReachesEveryone pins the exception alongside
TestBroadcastIsPerUser — audiences are now named decisions the
compiler and the tests both enforce.
This commit is contained in:
John O'Keefe committed 2026-11-02 18:17:37 -05:00
1 parent a431dfa10b
commit dd963993eb
4 files changed
+77 -13

No files matched your search

+8 -7
View File
@@ -75,13 +75,14 @@ nothing to implement. A connection that stops answering is removed.
## Fan-out and backpressure — read this before building a client
- **Every event is delivered only to the acting user's connections.**
Progress, annotation, and conflict events go to the user whose sync
activity produced them; scan events go to the identity that triggered
the job; collection events to the collection's owner. The
ConnectionManager has no untargeted broadcast — the compiler enforces
an audience on every emitter (2026-11-02, after cross-user delivery
was found and fixed).
- **Every event carries an explicit audience.** Progress, annotation,
conflict, and collection events are delivered only to the acting
user's connections — the ConnectionManager has no accidental
fan-out, and the compiler forces every emitter to name its audience
(2026-11-02, after cross-user delivery was found and fixed). The one
sanctioned instance-wide fan-out is **scan events**
(`BroadcastToAll`): they carry job ids and counts, never user data,
and the sitewide scan indicator in every browser runs on them.
- Every connection has a bounded send queue. **If a client falls behind,
messages for that connection are skipped, not queued.** Clients must
therefore treat the socket as a *hint* — on reconnect (or any gap),