Skip to content

Room timeline backfills from the spec-scoped event history - #44

Merged
udaychandra merged 2 commits into
mainfrom
agent/sail-spec-events
Aug 18, 2026
Merged

Room timeline backfills from the spec-scoped event history#44
udaychandra merged 2 commits into
mainfrom
agent/sail-spec-events

Conversation

@udaychandra

Copy link
Copy Markdown
Contributor

Spec: sail-spec-events (sail PR: standardapplied/sail#196).

Why

The room timeline backfilled by fetching the last 100 global events and filtering client-side to the spec, so on an active control plane a spec's lifecycle rows silently vanished from its room even though they sit durably in sail's audit store.

What

  • Gateway.specEvents(id, {since?, limit?})GET /v1/events?spec= (sail ≥ 0.26): one spec's durable history, oldest first, RECORD-class only; since is the exclusive event-id cursor.
  • Room load is scopedSpecRoom backfills through specEvents instead of filtering the global window. Version skew: a server that predates the route (405) falls back to the old filtered /recent read, so rooms never regress against an older sail.
  • Reconnect gap-fill is scoped — the room watches the stream state already relayed through onConnectionStatus and, on a reconnect, requests ?spec=&since=<max seen event id>; the existing monotonic-id dedupe makes convergence duplicate-free. The board's EventStream keeps /recent — the firehose is its actual question.
  • Demo gateway implements the same semantics for the browser preview.

Note: history served this way is node-local by contract (no event replica in sail); the fleet-consistent room content remains messages, reviews, and runs.

Verification

bun run typecheck + bun test green (494 pass). New tests: lifecycle rows survive a busy global stream (and /recent is not called), the version-skew fallback filters the global window, reconnect gap-fill issues {since} and renders no duplicate rows, and the Tauri gateway builds the scoped URLs.

The room fetched the last 100 global events and filtered client-side,
so lifecycle rows silently vanished on a busy control plane even though
they sit durably in sail's audit store. The timeline now loads through
GET /v1/events?spec= (sail >= 0.26) and gap-fills scoped from the last
seen event id when the SSE stream reconnects, converging without
duplicates on the existing monotonic-id dedupe. A server that predates
the route falls back to the filtered global window, and the board's
EventStream keeps /recent — the firehose is its actual question.
Two review mediums on the spec-scoped gap-fill:

- It made a single specEvents request, so a disconnect longer than one page
  (PAGE_SIZE) silently dropped every event past the first 100. Now it pages with
  an advancing since cursor until the server returns a short page.

- It only merged events into the timeline, bypassing the durable-state refreshes
  the live handler runs — so a gap-filled message/review/run event left the
  message list, review status, and run status stale until the next live event.
  Gap-fill now splits message events (drive the message list, like the live
  handler) from lifecycle events (merge + reconcile optimistic decisions), and
  after draining runs refreshMessages / refreshReviewsAndRuns exactly when the
  batch warranted them.

Extracts the decision-reconciliation both paths share into one pure helper so a
confirmation landing during a disconnect clears the optimistic overlay like a
live one. Proven red-first: paging stopped at 100, and durable state never
refreshed.
@udaychandra
udaychandra merged commit 2638260 into main Aug 18, 2026
2 checks passed
@udaychandra
udaychandra deleted the agent/sail-spec-events branch August 18, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant