Commit Graph
7 Commits
Author SHA1 Message Date
angelandClaude Sonnet 5 4577c80e0c amazon: reconnect the LISTEN connection on failure instead of dying
A dropped connection, a Postgres restart, or any other error on the
dedicated LISTEN connection previously propagated all the way out of
ProcessEvents, and main.go's top-level shutdown logic treats that
error channel firing the same as a fatal server error - taking down
the entire application over a hiccup on one background connection that
has nothing to do with serving HTTP traffic. This matters more with
eleven other platforms already sharing the same trigger+notify shape
in the migrations with no Go processor yet.

Adds (*Mocks).reconnectOrStop: on a real failure (not an ordinary
shutdown), logs a warning and re-establishes LISTEN after a backoff
that starts at 1s, caps at 30s, doubles on repeated immediate
failures, and resets once a reconnect actually succeeds.
ProcessEvents' select no longer returns on a LISTEN error - it loops
back in with fresh channels instead.

Verified against a genuinely killed connection (pg_terminate_backend,
targeting the backend via pg_stat_activity matched on its LISTEN
query text), not a simulated one - both in a manual check and in the
new TestProcessEvents_ReconnectsAfterListenConnectionDrops test.

Closes out all four insights from the domains/amazon design review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEDaCB7C2NEBgyvqEtZuxY
2026-08-20 00:35:22 -06:00
angelandClaude Sonnet 5 662b0ac3c9 amazon: make the poll fallback interval configurable and observable
ProcessEvents' safety-net poll was a bare time.Minute literal inline in
its select statement - no way to verify the fallback path works without
waiting 60+ seconds in a test, no way to tune the cadence without a code
change, and no way to tell afterward whether a given loop iteration was
triggered by a real NOTIFY or by the poll timer.

Adds Mocks.pollInterval (default time.Minute) and a WithPollInterval(d)
builder, mirroring WithNotifyRetryAfter's existing pattern - deploy-time
configurable via construction, not a live/API-adjustable knob, and not
wired through .env, matching how notifyRetryAfter already works.

Adds a Debug log line on each of the two meaningful wake-up branches so
which path fired is now observable.

New test proves the poll branch actually works without waiting or
touching the shared DB trigger (which would be unsafe against the real
dev DB): it reuses the retry mechanism from the previous commit so a
second dispatch can only come from the poll timer, since nothing else
ever notifies again for the rest of the test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEDaCB7C2NEBgyvqEtZuxY
2026-08-20 00:35:22 -06:00
angelandClaude Sonnet 5 984a4465f8 amazon: replace fire-and-forget listener notifications with ack-based retry
Dispatching an event to the listener previously happened from an
un-awaited goroutine, with only a log line on failure - once
processed_at was set, a dropped or failed notification was permanently
and silently lost, with no way to tell it had happened.

Replaces the processed/processed_successfully booleans with a
notified_at/processed_at pair (migration 000031): the dispatcher sets
notified_at and hands the event to MockEventListener.Notify, which now
also receives an ack callback the listener calls whenever it's truly
done, synchronously or arbitrarily later. Anything still "notified" but
unacked past notifyRetryAfter (a tunable field, not a stored
per-row timestamp) gets notified again on the dispatcher's normal
poll/reactive loop - no new retry mechanism needed. ack is idempotent,
since a late ack from an earlier attempt and one from a retry can both
eventually fire for the same event.

Dispatch is deferred until after the transaction that recorded
notified_at has actually committed, so ack's independent write can't
race a still-open transaction it implicitly depends on being visible.

The real SSE listener (server/sse/db_event_publisher.go) acks inline,
since its work is synchronous.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEDaCB7C2NEBgyvqEtZuxY
2026-08-20 00:35:22 -06:00
angelandClaude Sonnet 5 579eae5097 amazon: add a Ready() signal for ProcessEvents' LISTEN registration
Tests (and any other caller) previously had no way to know when the
Postgres LISTEN behind the reactive event-processing loop had actually
registered, forcing a guessed sleep before relying on it. Mocks now
exposes Ready() <-chan struct{}, closed once listenForNotifications
successfully issues LISTEN. Purely additive - ProcessEvents' signature
is unchanged.

Updates the integration tests to wait on Ready() instead of a flat
sleep, which also cut TestProcessEvents_ReactsToNotification's runtime
from ~0.25s to ~0.06s with no flakes across repeated runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEDaCB7C2NEBgyvqEtZuxY
2026-08-20 00:35:22 -06:00
angel 6a473b2ea0 Claude-assisted improvements (untested)
- dev auth flow (side-step OAuth)
- db event processing integration tests
- dev scripts (eg Makefile)
- db / test db migration setup scripts.
2026-08-20 00:35:22 -06:00
angel 4748eda41e amazon events published as server side event 2026-08-20 00:35:13 -06:00
angel 16ecf2dc66 mock amazon event processing stubbed 2026-08-20 00:35:13 -06:00