docs: Plan 7 — SSE edge sync - #14
Conversation
Add plan doc for long-lived SSE stream, heartbeats, 410 Gone on pruned cursor, non-durable cursor events, per-tenant filtering, and snapshot compression. Mark Plan 6 done in handover. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 58 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe changes add a Plan 7 document specifying long-lived SSE synchronization behavior, cursor handling, filtering, lifecycle controls, testing, and deferred work. The handover updates project status, sequencing, and references the new plan. ChangesSSE Edge Sync Plan
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
PR Summary by QodoDocs: Plan 7 SSE edge sync plan + handover status update
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
18 rules 1.
|
Tighten cursor event frequency to every poll cycle; scope compression to snapshot-only with Z_SYNC_FLUSH; add connection limits, backpressure, graceful shutdown, and initial-batch cap; require snapshot-consistent high-water reads; harden sync_cursors with periodic writes and best-effort disconnect; explicit CI trust-engine requirement. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md`:
- Around line 49-52: Make the 410/cursor contract consistent throughout the
plan: define cursor 0 as an explicit bootstrap state that does not trigger 410
for new clients, while preserving 410 only for genuinely pruned cursors below
the retained minimum. Since event pruning is out of scope, defer or remove
pruning-dependent 410 tests and update verification so a future cursor such as
999999999 is not treated as pruned; apply the same rules to the referenced
sections.
- Line 57: Update all sync_cursors upsert paths in the Plan 7 flow, including
initial, periodic, and disconnect writes, to preserve the greatest existing and
incoming last_cursor value. Apply the monotonic GREATEST(existing.last_cursor,
excluded.last_cursor) behavior consistently so out-of-order writes cannot
regress the stored cursor.
- Line 47: Update the SSE stream description and the corresponding sections
around the polling implementation to describe events as delivered through
five-second Postgres polling rather than pushed at commit time. Alternatively,
if commit-time delivery is required, add and document a notification mechanism
that replaces the removed Redis path; keep the behavior consistent throughout
the plan.
- Around line 3-5: Restore the workspace to a clean checkout of the main branch
before implementing this plan: switch from the detached HEAD to main, discard
the two commits ahead of origin/main, and ensure the working tree is clean and
aligned with origin/main.
- Around line 58-63: Add per-connection backpressure to the SSE connection
handling described under “Connection limits and backpressure”: maintain a
bounded outbound queue for each accepted client, pause dequeuing when the
response write stream signals backpressure, and resume on “drain”. Route event
data, heartbeats, and shutdown frames through this queue, and close clients that
remain blocked beyond the configured slow-client policy.
In `@docs/superpowers/plans/HANDOVER.md`:
- Around line 4-6: Update the Plan 6 status and PR B wording in HANDOVER.md to
reflect completion and merged PRs `#12` and `#13` rather than an in-progress branch.
Amend the “What’s after Plan 6” list to add Plan 7 / step 11 before Go edge
hardening / step 12, keeping the handover sequence consistent with the header.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 00925ec5-cb19-4b89-9cf3-4ccdcccc231f
📒 Files selected for processing (2)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.mddocs/superpowers/plans/HANDOVER.md
Bootstrap cursor 0, poll-delivered wording, monotonic sync_cursors, per-connection SSE write queues, defer pruned-410 tests, and align HANDOVER Plan 6/7 status. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md`:
- Around line 69-70: Update the SSE bounded outbound queue specification to
reserve capacity for the shutdown control frame or give control frames explicit
priority over queued data. Define the behavior when the queue is full so
shutdown is always enqueued and delivered, rather than rejected or dropped,
while preserving the existing backpressure and slow-client timeout semantics.
- Around line 92-93: Define the empty-table high-water behavior in the SSE sync
plan by requiring the atomic event/high-water query to treat an empty
sync_events table as high-water value 0, using COALESCE(MAX(sync_version), 0) or
an equivalent explicit rule before cursor comparison or serialization. Document
and cover empty-table bootstrap behavior in the polling flow.
- Around line 84-87: Update the SSE bootstrap cursor parsing described in the
plan to strictly accept only non-negative integer values, rejecting malformed,
negative, fractional, and oversized IDs before any cursor or backlog logic runs.
Parse large values without floating-point precision loss, preserve missing
values as cursor 0, and add coverage for malformed, negative, and oversized
Last-Event-ID inputs.
- Around line 68-69: The initial SSE backlog lookup must be bounded before
events are materialized. Update the initial-batch query/probe to apply the
tenant predicate and LIMIT it to SYNC_SSE_MAX_INITIAL_BATCH + 1, using the extra
row only to detect the 410 Gone condition; avoid loading the unbounded
sync_events backlog into memory.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: b74c8b1b-259a-4dca-8550-2986103d908a
📒 Files selected for processing (2)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.mddocs/superpowers/plans/HANDOVER.md
Bounded LIMIT+1 backlog probe, shutdown control-frame priority, strict Last-Event-ID parsing, and COALESCE high-water for empty sync_events. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md (4)
74-74: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winReturn high-water even when no tenant rows match.
If the poll query returns one row per tenant-visible event, a poll containing only other-tenant events produces no result row and therefore no high-water value or cursor event. Define a result shape that always returns the scalar high-water alongside an empty event set, and test this tenant-filtered-gap case.
Also applies to: 92-93
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md` at line 74, Update the poll query result shape so it always returns the unfiltered MAX(sync_version) high-water value even when no tenant-visible events match, while returning an empty event set for that case. Ensure cursor-event generation consumes this scalar high-water without requiring an event row, and add coverage for a poll containing only other-tenant events.
69-70: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winStart the slow-client deadline at the first backpressure signal.
A
res.write()call can returnfalsewhile fewer than 256 frames are queued; the queue may never become “full,” so the connection can remain blocked indefinitely. StartSYNC_SSE_SLOW_CLIENT_MSwhen backpressure begins and clear it only afterdrain.Also applies to: 147-147
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md` around lines 69 - 70, Update the per-connection SSE outbound queue flow so the slow-client deadline starts on the first res.write() backpressure signal, regardless of queue occupancy. Track this deadline when res.write() returns false, pause dequeuing until drain, and clear the timer only after drain resumes writable state; retain the existing full-queue timeout behavior and shutdown flushing.
74-74: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftBound poll fetches and couple cursor advancement to the drained range.
A burst of events between polls can exceed the 256-frame outbound queue, while the plan currently fetches all new rows before enqueueing them. Use bounded pages with backpressure-aware draining; do not emit a high-water cursor until every durable event through that high-water has been queued in order, or the client can skip unseen events.
Also applies to: 92-93
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md` at line 74, Update the poll-cycle design to fetch events in bounded pages rather than loading all new rows before enqueueing, and drain each page with backpressure awareness against the 256-frame outbound queue. Couple cursor advancement to the drained range: emit a high-water cursor only after every durable event through that high-water has been queued in order, while preserving snapshot consistency between tenant-filtered events and the high-water query.
93-101: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse a single heartbeat scheduler.
Task 1 schedules
: pingframes, while Task 2 independently requires another per-connection heartbeat interval. Implementing both emits duplicate heartbeats and creates redundant timers; make one task the sole heartbeat owner.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md` around lines 93 - 101, Consolidate heartbeat scheduling so each SSE connection has exactly one interval responsible for enqueuing : ping\n\n frames at SYNC_SSE_HEARTBEAT_INTERVAL_MS. Remove the duplicate heartbeat responsibility between the poll loop in Task 1 and the Task 2 heartbeat timer, while preserving heartbeat delivery during periods without new events and timer cleanup on disconnect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md`:
- Line 74: Update the poll query result shape so it always returns the
unfiltered MAX(sync_version) high-water value even when no tenant-visible events
match, while returning an empty event set for that case. Ensure cursor-event
generation consumes this scalar high-water without requiring an event row, and
add coverage for a poll containing only other-tenant events.
- Around line 69-70: Update the per-connection SSE outbound queue flow so the
slow-client deadline starts on the first res.write() backpressure signal,
regardless of queue occupancy. Track this deadline when res.write() returns
false, pause dequeuing until drain, and clear the timer only after drain resumes
writable state; retain the existing full-queue timeout behavior and shutdown
flushing.
- Line 74: Update the poll-cycle design to fetch events in bounded pages rather
than loading all new rows before enqueueing, and drain each page with
backpressure awareness against the 256-frame outbound queue. Couple cursor
advancement to the drained range: emit a high-water cursor only after every
durable event through that high-water has been queued in order, while preserving
snapshot consistency between tenant-filtered events and the high-water query.
- Around line 93-101: Consolidate heartbeat scheduling so each SSE connection
has exactly one interval responsible for enqueuing : ping\n\n frames at
SYNC_SSE_HEARTBEAT_INTERVAL_MS. Remove the duplicate heartbeat responsibility
between the poll loop in Task 1 and the Task 2 heartbeat timer, while preserving
heartbeat delivery during periods without new events and timer cleanup on
disconnect.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6df012d9-9c70-41ef-925f-82765fc321d1
📒 Files selected for processing (1)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md
Bounded LIMIT+1 backlog probe, shutdown control-frame priority, Decision 15 Last-Event-ID parsing, COALESCE high-water for empty sync_events, and matching task/test bullets. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md`:
- Line 62: Resolve the contradiction between the cursor emission rule and Task
5’s empty-table expectation by explicitly choosing one contract: emit an initial
SSE-only cursor at high-water 0 for empty streams, or update the affected task,
tests, and documentation to permit no cursor until high-water advances. Apply
the same decision consistently to the referenced cursor behavior sections.
- Line 79: Update the Last-Event-ID cursor initialization and persistence flow
to prevent a client-supplied value above the current high-water from being
stored in sync_cursors.last_cursor. Before the upsert or GREATEST-based update,
clamp the initial cursor to the observed current high-water, or persist only
cursors actually observed or emitted; preserve validation and normal cursor
advancement behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 438e3e35-6135-4937-9c1b-a6e8485266a5
📒 Files selected for processing (1)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md
Bootstrap cursor on first hw read (including id 0), persist sync_cursors from last_emitted_hw only, and align Task 5/6 with Decision 4 and 11. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
Reserve 410 for retention; use 429 for backlog cap; event:shutdown control lane; burst latency; durable-only cursor persist; queue 1024; empty-table heartbeat tests; snapshot refresh race in known risks. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md`:
- Around line 111-112: Update the SSE stream flow around last_emitted_hw and
disconnect persistence to track the highest cursor whose frame has actually been
written to res, rather than merely enqueued; use that last-written value for
sync_cursors upserts and periodic persistence, while retaining last_emitted_hw
for queue/enqueue decisions.
- Line 82: Update the plan’s initial-backlog handling to reconcile the
10,000-row batch limit with the 1,024-frame per-connection data queue: specify
incremental production that pauses when the queue is full and resumes after
drain, or reduce the backlog batch cap to fit the queue. Ensure clients with
valid backlogs up to the documented limit are not closed as slow solely because
the initial rows exceed queue capacity.
- Line 88: The sync route’s query schema must preserve last_event_id as a raw
string instead of declaring it numeric. Update the schema and handler around the
last_event_id parsing flow so strict non-negative integer validation, including
oversized safe-integer handling, occurs before backlog probing, 410/429
decisions, or database queries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 27d96e6a-7a7d-4ca6-a016-b431e79627b1
📒 Files selected for processing (1)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md
Incremental backlog enqueue vs queue depth, raw-string last_event_id until Decision 15 parse, and sync_cursors from last_written_hw only. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md`:
- Around line 85-86: Update the SSE data-queue design to bound queued bytes in
addition to the existing frame-count limit, including a configurable per-frame
or queue-byte maximum for potentially large JSONB payloads. Define and document
how oversized events are handled, such as rejecting or closing the connection,
and ensure incremental enqueueing enforces both limits while preserving the
existing backpressure and slow-client behavior.
- Line 92: The Last-Event-ID rules must distinguish omission from a supplied
blank value: update the plan and handler validation so an absent header and
query parameter bootstrap with 0, while a present value that is empty or
whitespace after trimming returns 400 Bad Request. Preserve header precedence,
raw-string parsing, and validation before backlog or database logic, and add
tests covering omitted and blank header/query cases.
- Around line 109-113: The bounded backlog probe and first event read must use
one consistent snapshot so commits cannot increase the initial backlog after
validation. Update the SSE initialization flow around the bounded backlog probe
and first read to perform both within a single snapshot/transaction, or recheck
the cap immediately before sending SSE headers; preserve the 429 backlog-cap
response and avoid opening the stream when the limit is exceeded.
- Line 81: Specify that edge_node_id is obtained from the server-authenticated
edge principal or session, never from client-provided input. Use this
authenticated identity for sync_cursors upserts and lag metrics so persistence
and reporting remain bound to the correct edge.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 28f1f5be-aa5b-4d85-8ec8-728a0de54e39
📒 Files selected for processing (1)
docs/superpowers/plans/2026-07-29-plan-7-sse-edge-sync.md
Byte+frame queue caps, omit vs blank Last-Event-ID, snapshot-consistent backlog probe, and authenticated edge_node_id for sync_cursors. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
Co-authored-by: Cursor <cursoragent@cursor.com>
Findings addressed in subsequent commits (ffff54f/adc324f). Threads resolved; CodeRabbit rate-limited on re-review of head.
Summary
Test plan
@coderabbitai reviewfor doc qualitySummary by CodeRabbit
text/event-streamsync endpoint with poll-based delivery (default 5s),: pingheartbeats, andretryhandling.Last-Event-IDvalidation (invalid values return400) plus standardized resume rules for410 Gone,429backlog caps, and cursor0bootstrap behavior.503), slow-client behavior, graceful shutdown signaling, cursor update cadence, and best-effort cursor persistence.