Source preview guide · 0.1.0
Events, replay and reconnect
Treat event streams as bounded hints and reconcile authoritative REST state.
The event stream tells clients that authoritative resources may have changed. It does not deliver complete findings, own the lifetime of a job or replace the resource’s revision checks. The durable database record remains the place to establish what happened.
Commit the change before announcing it
- Connect with the admitted client
The browser uses its session; the IDE needs event:read for its active generation. No credentials or replay cursor belong in query parameters.
- Receive a bounded hint
project.changed, scan.changed, job.changed and findings.changed point to changed resources. Keepalive comments only indicate transport activity.
- Read the resource
Refresh the intended project, scan, job or finding pages and reconcile their current revisions.
- Reconnect with Last-Event-ID
Retain the last observed event ID and reconnect after backoff. Replay is bounded.
- Rebuild after stream.reset
When retained events cannot fill the gap, reread current state rather than assuming the missing interval was empty.
Database changes stage bounded event intents in an outbox within the transaction. A dispatcher assigns replay sequence IDs to committed intents. PostgreSQL notifications wake the API listener; the listener then reads durable rows. A notification is a wake-up signal, not the complete event history.
The API shares one blocking PostgreSQL LISTEN connection within its process. It establishes the subscription before the initial database read to close the listen/query race. Streams read from the outbox and send server-sent events to their authorized clients. If a client misses a wake-up or has an old view, resource reconciliation is still required.
See outbox dispatch and stream reads.
Open a stream with current authority
GET /v1/events returns text/event-stream. A browser uses its session; an IDE needs event:read for its exact active generation. The stream accepts no URL query parameters. Use the Last-Event-ID header for replay, with a non-negative decimal sequence within the supported range.
The implementation limits streams to 32 total and four per subject in each API process. This is a process-local safety bound, not a distributed quota across independently scaled API instances. Exceeding it returns 429; an unavailable listener returns 503. Creating additional streams is not a recovery strategy for either condition.
IDE authority is checked against the durable capability slot and generation before frames are emitted, including keepalives. Revocation closes the stream without exposing credential details in a special event; reconnecting under revoked authority is refused.
Read protocol and domain events
| Event | Meaning and client response |
|---|---|
stream.ready |
Reports the stream schema and current high-water/earliest event IDs. Reconcile initial resource state. |
stream.reset |
The supplied cursor cannot be replayed safely. Rebuild relevant REST state before reconnecting. |
project.changed, scan.changed, job.changed, findings.changed |
Bounded resource-change hints. Refresh the affected resource or collection, preserving revision checks. |
ide.source.open |
A targeted pending source-opening command for the exact authorized IDE generation. It is not a general broadcast or a replayable finding payload. |
Domain events have sequence IDs. Protocol frames and targeted source commands do not become ordinary domain replay entries. Targeted delivery is withheld if multiple live streams make the capability’s recipient ambiguous; opening more windows must not cause a source command to select an arbitrary recipient.
Event documents are capped at 4 KiB. The source emits a keepalive comment every 15 seconds while waiting. A keepalive neither advances replay state nor proves that a scan completed. See the stream emission and authorization checks.
Reconnect and reset
A first subscription without a cursor begins at the current high-water mark. It does not replay all earlier events, so the client still needs an initial REST view. On reconnect, retain the last observed domain event ID and send it through the header after backoff.
Replay is bounded to 256 events. The outbox has retention bounds of 50,000 dispatched events and 24 hours, so neither bound promises that a requested cursor remains available. The stream returns a reset and closes for these conditions:
| Reset reason | Why replay cannot proceed |
|---|---|
cursor_ahead |
The supplied sequence is beyond the current high-water mark. |
cursor_expired |
Required earlier events are no longer retained. |
backlog_too_large |
The pending replay exceeds the bounded batch. |
On reset, rebuild the selected project/scan, active jobs and relevant finding pages from current REST state, then establish a fresh stream baseline. Reconcile again after connection loss or an authority change. Do not treat a gap as an empty result, keep a stale collection cursor or silently advance a job to completion.
The outbox is a bounded synchronization mechanism, not an indefinite audit log or a guarantee of exactly-once client action. The stream reference remains bound to its published API snapshot; the implementation linked here explains the current reviewed behavior. This documentation site opens no event connections.