oficina / docs Public

Spec 7.1 is the doc-seq protocol, by Paul's ruling

What shipped was never section 7.1's fallback. That text specified a
sequence number per connection with a high-water ack, and a
per-connection counter cannot survive the connection — so it cannot
tell a reconnecting tab what it missed, which is the case section 7.3's
idle timeout guarantees will happen constantly: every parked tab,
every hibernation cycle, every five-minute TTL. The document's own log
seq answers that by construction.

So 7.1 is rewritten rather than annotated, and the old wording is
recorded as the decision it was rather than quietly replaced. The
WebSocket is now what arrives with presence, which is the thing it is
actually needed for.

Also records the second half of the same ruling in the live-sync
spec's section 9: the connection policy stands as built, including the
hands-free watcher that goes stale at the first server TTL, with a
note on what relaxing 6.1 would take if that ever needs to change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 0de38f849e7eab68acdfa3755530c5554670b292 parent e412077
2 files changed, +60 −10
  • docs/superpowers/specs/2026-08-30-docs-design.md +40 −4
  • docs/superpowers/specs/2026-09-01-live-sync-design.md +20 −6

CI passed — run details

Intent

Human

all that sounds fine. merge and deploy

diff --git a/docs/superpowers/specs/2026-08-30-docs-design.md b/docs/superpowers/specs/2026-08-30-docs-design.md
index 4bbe01e..665797b 100644
--- a/docs/superpowers/specs/2026-08-30-docs-design.md
+++ b/docs/superpowers/specs/2026-08-30-docs-design.md
@@ -385,10 +385,46 @@ JS `yjs` with `y-prosemirror` in the browser; Rust `y-crdt` server-side.
Character-level convergence, so two people in one paragraph both keep their
words.
-Updates travel over a WebSocket. **The SSE fallback needs an upstream path**,
-which revision 1 omitted: SSE is server-to-client only, so under fallback the
-client POSTs batched updates with a monotonic sequence number per connection,
-and the server acknowledges the high-water mark.
+**Updates travel over a Server-Sent Events stream plus the ordinary update
+POST, and the sequence number is the document's own, not a connection's.**
+This is revision 3, and it replaces rather than annotates what revisions 1 and
+2 said here — see the correction below for why the old wording could not
+honestly describe what shipped.
+
+The stream (`GET /d/{sqid}/stream`) carries a seq and no document bytes at
+all. It says "this document has content through seq N"; the client fetches
+whatever is newer than its own cursor through `GET /d/{sqid}/updates?since=N`,
+the same binary endpoint it already replays at mount. Upstream is unchanged:
+the client POSTs batched updates, and the response names the seq that row
+received. `doc_updates.seq` is that number — durable, global to the document,
+and meaningful across connections, tabs and devices.
+
+The full protocol, its cursor rules and its hibernation policy are
+`docs/superpowers/specs/2026-09-01-live-sync-design.md`.
+
+**The correction, recorded because the old text was a decision and not a
+typo.** Revision 1 said updates travel over a WebSocket; revision 2 added that
+"the SSE fallback needs an upstream path… the client POSTs batched updates
+with a monotonic sequence number **per connection**, and the server
+acknowledges the high-water mark." A per-connection counter cannot survive the
+connection. It cannot tell a reconnecting tab what it missed, which makes it
+useless for exactly the case §7.3's idle timeout guarantees will happen
+constantly — every parked tab, every hibernation cycle, every five-minute
+stream TTL. The document's own log seq answers that by construction: a tab
+that reconnects after an hour asks `?since=` its own cursor and receives
+precisely what it missed.
+
+So what shipped is not this section's fallback with the socket still to come.
+It is a different and better protocol, and calling it "the fallback" would
+have left the spec describing a design nobody built. (Paul's ruling,
+2026-09-01, on the live-sync spec's §9.)
+
+**The WebSocket is now the upgrade that arrives with presence, not the
+primary this replaced.** Awareness needs ephemeral, high-frequency, duplex
+messages, which is what a socket is for and what an SSE-plus-POST pair is bad
+at; content sync needs none of that. When presence lands, the socket lands
+with it, and the update-log format above is what it will stream — unchanged,
+because nothing about `doc_updates` is transport-specific (§4.3).
### 7.2 Offline
diff --git a/docs/superpowers/specs/2026-09-01-live-sync-design.md b/docs/superpowers/specs/2026-09-01-live-sync-design.md
index d80821c..42c5e4c 100644
--- a/docs/superpowers/specs/2026-09-01-live-sync-design.md
+++ b/docs/superpowers/specs/2026-09-01-live-sync-design.md
@@ -347,7 +347,7 @@ viewer" is the same person's other tab, and a presence stack that shows you your
is worse than nothing. Presence arrives with sharing, on a socket, as §7 always
intended.
-## 9. Open ruling for Paul
+## 9. Ruling, made 2026-09-01
**§7.1 should be amended rather than annotated, and the wording matters.**
@@ -358,11 +358,25 @@ design ships is not that: `Docs-Seq` is the document's global log seq, durable i
protocol and a better one — a per-connection counter cannot heal a tab that reconnects,
which is most of what §6 needs.
-So "the fallback shipped first" would be a false description of the code. The honest
-options are to amend §7.1 to describe the doc-seq protocol as the transport, with the
-WebSocket as the upgrade that arrives with presence — which is what this design
-assumes — or to rule otherwise. **Recorded as Paul's call; the branch does not merge
-until he makes it.**
+So "the fallback shipped first" would be a false description of the code.
+
+**Paul ruled on 2026-09-01: amend it.** The docs design spec's §7.1 is now revision 3
+and describes the doc-seq protocol as the transport, with the WebSocket as the upgrade
+that arrives with presence. That edit is part of this branch, and it records the
+correction rather than quietly overwriting the old wording — a per-connection counter
+was a decision, and one that could not have survived §7.3's idle timeout.
+
+The same ruling accepted §6's connection policy as it stands, including its one known
+consequence: **a hands-free watcher goes stale at the first server TTL.** Receiving
+events defers the idle close but cannot reopen after the server cuts the stream at five
+minutes, because the client treats every `EventSource` error — an expected TTL close
+included — as "drop it and wait for activity". A wall display nobody touches shows five
+minutes of updates and then freezes. That is the honest reading of §6.3's "any activity
+reconnects": *some* activity is still required. If it ever needs to change,
+`EventSource.readyState` at error time distinguishes a close the browser means to retry
+from a fatal one, which would mean relaxing §6.1's blanket rule — deliberately stronger
+than it needs to be, since `es.close()` never triggers a reconnection and only
+server-side closes were ever at issue.
## 10. Non-goals