feat(#81): SSE realtime events endpoint bridging LISTEN/NOTIFY#86
Conversation
Approved brainstorming outcome: raw NOTIFY->SSE bridge on the main API behind a config-gated path, static channel allowlist + standard JWT gate (with an access_token query fallback for EventSource), fire-and-forget delivery, multiplexed frames via the SSE event: field, and a shared per-instance listener fanning out through a duplicate-keys Registry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Nine TDD tasks from the approved spec: config surface, SSE encoding, duplicate-keys registry, telemetry, LISTEN fan-out listener, request handler + routing + BIER* error envelopes, streaming edge-case tests, docs (guide + brewery tutorial chapter), and the full precommit gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
… proxy-status Applies the whole-branch review wave for the SSE realtime events feature: - Reorder Bier.Events.handle/2's validation chain to authenticate before parsing/authorizing channels, so a tokenless request on a JWT-protected instance is always 401 instead of leaking channel existence via a 404/401 split (unauthenticated channel-enumeration oracle). Spec doc and moduledoc updated to match; pinning test added. - Brand Bier's own BIER-prefixed error codes with `Proxy-Status: Bier; error=<code>` instead of PostgREST's, leaving every PGRST* code (and the frozen conformance suite that depends on them) untouched. - Make the realtime events guide honest about three shipped behaviors: tokens are verified once at connect (not re-checked against exp), OPTIONS never reaches the events handler, and slow-client backpressure piles up unbounded in that subscriber's own mailbox. - Adopted minors: percent-decode the events path segment before comparing it (consistent with relation resolution), thread the chunk-write failure reason into the subscribe :stop telemetry event, dedupe the wait_until_listener_connected/2 test helper into Bier.SSETestClient, fix a stale comment on Listener.safe_listen/2, note the events listener's boot-ordering gap in lib/bier.ex, and fix a moduledoc heading's capitalization. - Bump mint 1.9.2 -> 1.9.3 (mix.lock only) to clear a same-day security advisory (EEF-CVE-2026-59249) that was blocking `mix hex.audit`, unrelated to this branch's code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
|
@claude review once. |
|
Claude finished @milmazz's task in 4m 14s —— View job Review: SSE realtime events endpoint (#81)
|
…gistry note PR #86 review follow-up: a NOTIFY payload containing a bare CR previously left a raw CR inside a data: line, which an EventSource parser treats as a line break - silently truncating the payload after it. Split on all three SSE line terminators instead. Also updates CLAUDE.md's supervision note, stale since Bier.Application gained Bier.Events.Registry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4
|
Review follow-up, addressed in f2cd0dc:
|

Closes #81.
What
A config-gated Server-Sent Events endpoint (
GET /<events_path>?channel=...) that bridges PostgreSQLLISTEN/NOTIFYto streaming HTTP clients — bier's first deliberate differentiator vs PostgREST, where this has been requested since 2019 (PostgREST/postgrest#1388, #4746) and never landed.events_channels: []disables everything; the frozen conformance surface is untouched (891 tests green, profile unchanged).Bier.Events.Listener, cloned fromSchemaCacheListener's connection-ownership pattern: trap-exit, 500ms→30s backoff) fans out through a duplicate-keysBier.Events.Registryto per-subscriber Bandit connection processes.event:field carries the channel,data:carries the NOTIFY payload verbatim.access_tokenquery fallback because browserEventSourcecannot set headers.retry:hint, token verified at connect, 8KB NOTIFY cap — all documented.BIER*code namespace (BIER001/BIER002) andProxy-Status: Bier; error=...branding.[:bier, :events, :subscribe, :start|:stop],[:bier, :events, :notification],[:bier, :events, :listener].docs/guides/realtime_events.md) + brewery-tutorial chapter (docs/tutorials/realtime.md, live check-ins board).Design & plan
docs/superpowers/specs/2026-07-16-sse-realtime-events-design.mddocs/superpowers/plans/2026-07-16-sse-realtime-events.mdTesting
mix precommitfully green: format, hex.audit, compile/credo/docs warnings-as-errors, 891 tests (5 excluded).access_token); auth-first pinning test.mint1.9.2→1.9.3 bump (EEF-CVE-2026-59249) required for a greenhex.audit.Follow-ups (deliberately deferred)
exp; per-subscriber mailbox guard;events_authorizerDB-function callback (config shaped for it).SSE.frametrailing-newline pinning, boot-levelevents_children([])case.🤖 Generated with Claude Code
https://claude.ai/code/session_01Y8pKvGBR8U9oBzUzWmzdX4