chore(river): modularity + readability cleanup across the River systems#400
Merged
Conversation
Readability pass on webhookpub (no behavior change): - Delete committed AI-scratchpad text + 3 no-op `var _` compile guards in worker.go, and the now-unused errors/encoding-json/pgconn imports. - Delete unused `nowUTC` var (+ its time import) in outbox.go. - Delete the dead `oldest` telemetry scaffolding in Tick (declared, ranged into nothing, discarded). - Fix docstring drift: writeOutboxRow's "(eventually) PublishBestEffortTx" (already wired) + acknowledge that in river mode nothing LISTENs the NOTIFY; PublishBestEffortTx's reference to a webhook_publish_failures table that was never built + the "legacy-fallback" line that contradicts the file's own "no legacy fallback path left"; the pg_notify block now notes it's a harmless no-op in river mode. - Rewrite the event.go package header to describe the current durable three-layer outbox→fan-out→River pipeline (it still described the retired in-process post-commit-goroutine publisher + a tmp/ design path). - Drop stale internal "slice-N" markers; note both fan-out engines share fanOutEventCore in the matches/Publisher docs. - Standardize the fan-out job Kind() "webhook_fan_out" → "webhook_fanout" to match webhook_fanout_reconcile + the rest of the "fanout" naming (safe: E2A_WEBHOOK_FANOUT_MODE defaults legacy, so no such jobs exist in prod yet). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfAXBRMJ3KPuU3UEgwqjhS
…ending The stranded-row reconciler was duplicated across five packages (outboundsend, inboundprocess, hitlnotify, webhookdelivery, webhookpub fan-out) — ~240 lines with byte-identical control flow: scan `WHERE <pred> AND <job_col> IS NULL LIMIT N` → per-row `FOR UPDATE` re-check → skip-if-set → EnqueueXTx → stamp the job id. Only the table/predicate/job-column/log-prefix varied. Extract a single `jobs.ReconcilePending(pool, spec, enqueueTx)` + `jobs.ReconcileSpec` + `jobs.DefaultReconcileBatch`. Each package's ReconcilePending collapses to a spec literal + one call; the domain method signatures are unchanged, so every caller (main.go cutover + each ReconcileWorker) is untouched. Behavior-identical: the two packages that stamped via a store method (inboundprocess/hitlnotify) stamped with the exact same `UPDATE <table> SET <job_col>=$2 WHERE id=$1` the helper now inlines; those store methods stay for the accept-tx path. Log output (prefixes) is preserved verbatim. The interpolated identifiers are compile-time constants (documented in ReconcileSpec) — the row id is always a bound param. Net ~240 → ~40 shared lines, and the reconcile invariants (FOR UPDATE re-check, IS NULL guard) now live in one hot-path-tested place instead of five. Each package keeps its own reconcile_test.go — all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfAXBRMJ3KPuU3UEgwqjhS
Readability polish across the River worker packages (no behavior change), from the modularity review: - Document the deliberate reliance on River's 60s default JobTimeout on the four per-message workers that DON'T override Timeout() (SendWorker, DeliverWorker, InboundProcessWorker, NotifyWorker) + RetentionWorker — so the omission reads as a decision, not an oversight (the repo has an explicit "River JobTimeout trap" lesson). InboundProcessWorker notes the content screen is bounded by piguard's own 5s per-detector timeout; RetentionWorker notes its single unbounded DELETE is ctx-safe under a 60s cut and flags batching (like #397) as a future option. - jobs.New: pass Config to defaultQueueConfig(cfg) instead of six positional ints (removes an invisible-transposition hazard). - Have RegisterJobs call the NewMaintenanceWorker constructors (webhookdelivery, hitlworker, janitor) instead of rebuilding the struct literal by hand — the constructors exist precisely so tests build an identical worker; now RegisterJobs can't drift from them. - outboundsend: name the markFailed terminal-write retry magic numbers (terminalWriteRetries / terminalWriteBackoff); drop landed "(slice B/C/D)" internal markers from comments. - hitlworker: "the legacy publisher" → "the webhook publisher" (the wired OutboxPublisher is the current outbox path, not the retired one). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfAXBRMJ3KPuU3UEgwqjhS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Readability/modularity pass across the River job systems, driven by three parallel code-review agents (cross-system interface audit + two per-package reviews). No behavior change — pure cleanup, verified by each package's existing tests. Net −124 lines.
The reviews' headline conclusion: the River systems are already well-disciplined and consistent (uniform
Registrar+ two-phaseSetEnqueuer, principled queue routing, consumer-side narrow interfaces). These are the polish items that survived scrutiny.1. Hoist the copy-pasted reconciler →
jobs.ReconcilePending(biggest win)The stranded-row reconciler was duplicated across 5 packages (~240 lines) with byte-identical control flow — scan
WHERE <pred> AND <job_col> IS NULL→ per-rowFOR UPDATEre-check → enqueue → stamp. Only the table/predicate/job-column/log-prefix varied. Extracted a singlejobs.ReconcilePending(pool, spec, enqueueTx)+ReconcileSpec+DefaultReconcileBatch; each package collapses to a spec literal + one call. Behavior-identical (the two store-method stampers did the exact sameUPDATEthe helper now inlines; those methods stay for the accept-tx path). Domain method signatures unchanged, so every caller is untouched. The reconcile invariants now live in one hot-path-tested place instead of five.2. webhookpub dead code + docstring drift
var _compile-guards inworker.go(and the 3 imports they alone kept), the unusednowUTC, and deadoldesttelemetry scaffolding.writeOutboxRow's "(eventually) PublishBestEffortTx" (already wired) + the pg_notify block now notes it's a harmless no-op in river mode;PublishBestEffortTx's reference to awebhook_publish_failurestable that was never built + a "legacy-fallback" line contradicting the file itself.event.gopackage header (it still described the retired in-process goroutine publisher) to the current three-layer outbox→fan-out→River pipeline.Kind()webhook_fan_out→webhook_fanout(safe — flag defaultslegacy, no such jobs in prod yet).3. Timeout documentation + small consistency polish
JobTimeouton the workers that don't overrideTimeout()— so the omission reads as a decision, not an oversight (the repo has an explicit "JobTimeout trap" lesson).InboundProcessWorkernotes the content screen is bounded by piguard's 5s per-detector timeout.jobs.New:defaultQueueConfig(cfg)instead of six positional ints (transposition hazard).RegisterJobsnow calls theNewMaintenanceWorkerconstructors instead of rebuilding the struct literal (they exist precisely so tests build an identical worker).markFailedterminal-write retry magic numbers; "legacy publisher" → "webhook publisher" in hitlworker.Explicitly left alone (reviews confirmed these are correct, not gaps)
EnqueueSendTx/EnqueueDeliveryTx/EnqueueFanOutTx) stay separate — domain-meaningful names, textbook consumer-side ISP.webhookpub(sharesfanOutEventCorewith the outbox), delivery inwebhookdelivery— the split tracks real coupling.Noted follow-ups (not in this PR)
OutboxWorker+webhook_events_newNOTIFY trigger (the file organization now makes this a clean delete).inboundprocessretention uses an unbounded DELETE (same pattern perf(janitor): batch the retention DELETEs on prod-sized tables #397 batched for the janitor) — documented, batching deferred.🤖 Generated with Claude Code