feat: event-driven notification & delivery platform (#37) - #66
Merged
josueazc merged 16 commits intoJul 28, 2026
Merged
Conversation
…ing, and delivery types (Velar-Bonds#37)
…s, receipts, DLQ, archival (Velar-Bonds#37) Adds a monthly-partitioned outbox_events table populated via AFTER INSERT/UPDATE triggers on bonds/transfers/reports, giving true same-transaction outbox capture with zero changes to existing service code (this repo has no app-level DB transactions). Also adds notification_dedup, notification_preferences, notification_quiet_hours, notification_digest_settings, notification_receipts, notification_dlq, and additive columns + an archive table for the existing notifications table. All RLS policies reuse the existing public.auth_role() helper to avoid the recursion bug already fixed once in this repo. Validated end-to-end against a throwaway local Postgres (full migration chain applies cleanly; bond/transfer/report triggers fire exactly once per real status transition and correctly skip no-op updates; RLS owner isolation and tse/admin-only DLQ visibility both verified functionally).
…, circuit breaker, DLQ, dedup (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…reference routing engine (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…elar-Bonds#37) The migration/architecture doc said "days" used ISO weekday numbering (0=Monday), but packages/types and the routing engine implementation both correctly use 0=Sunday..6=Saturday (JS Date.getDay()). Fixed the two doc/comment mentions to match the actual, tested implementation.
…ine with XSS sanitization (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…reconnection, receipts (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…nts/DLQ adapters + digest compiler (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…tification gaps in requestBond/rejectReturn/reports (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…-center endpoints (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…ed payloads, metrics/SLI endpoint (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…e center (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
…ds#37) Caught during manual browser verification: the new inbox route was missing from middleware.ts's PROTECTED/matcher lists, so it skipped the server-side auth redirect every other protected route gets and fell through to client-side-only redirect. Now consistent with /marketplace, /mis-bonos, /negociaciones, etc.
… provider plug-in points (Velar-Bonds#37) Co-authored-by: Cursor <cursoragent@cursor.com>
|
@Villarley is attempting to deploy a commit to the josueazc's projects Team on Vercel. A member of the Team first needs to authorize it. |
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.
Summary
Implements issue #37 — an event-driven notification & delivery platform extending VELAR's existing minimal in-app notification system. Everything is exercisable via in-memory fakes and property/concurrency tests: no VELAR database, secrets, or external API credentials required for
npm run build/npm run lint/npm run testin eitherapps/apiorapps/web.Full design doc:
docs/notifications/ARCHITECTURE.md. Reference docs updated:docs/BACKEND.md§10,docs/FRONTEND_GUIDE.md§15.Architecture in one paragraph
This codebase has no application-level DB transactions (no
.rpc()usage anywhere), so reliable outbox capture is done via PostgresAFTER INSERT/UPDATEtriggers onbonds/transfers/reportsthat write to a monthly-partitionedoutbox_eventstable in the same transaction as the domain write — zero changes to existing service call sequencing. A pollingOutboxDispatcherdrains the outbox with per-recipient ordering (hand-rolled async semaphore + promise chaining), exponential backoff + full jitter, per-channel circuit breakers, and a dedup store proving exactly-once-ish delivery under retries/replay. A pure, property-tested routing engine (DST-correct quiet hours vialuxon, digest cadence/windowing, per-category/channel opt-outs) decides which channels fire. Channel adapters (in-app real, email/web-push behindNoop*Providerstubs pending real credentials) render through a versioned, i18n, XSS-sanitized template engine. A resumable realtime transport (cursor/catch-up/receipts) ships with an in-memory fake and aws-based real implementation (not yet wired to a live gateway — documented as a known, intentional gap). Observability (metrics, tracing hooks, admin SLI endpoint) and security (rate limiting, HMAC payload signing, reused auth guards + RLS) round it out.What's included
20260702000000_notification_platform.sql(outbox + triggers + dedup/preferences/quiet-hours/digest-settings/receipts/DLQ/archive tables, RLS via the existingpublic.auth_role()helper),20260703000000_notification_digest_queue.sql. Both functionally validated against a throwaway local Postgres (not a VELAR database) — triggers fire exactly once per real status transition, RLS owner-isolation and tse/admin-only DLQ visibility verified live, not just read.packages/types/src/notifications/*(domain events, preferences, routing, delivery) plus additive enum values innotification.ts/audit.ts.apps/api/src/notifications/): outbox dispatcher core, dedup/preferences/recipient-directory/DLQ stores (in-memory + Postgres), routing engine, channel adapters, template engine, realtime transport, Postgres adapters + digest compiler, module wiring, inbox/preference-center endpoints (all registered in the sharedapiContractscontract-validation system), observability + security.docs/AGENTS.md§3 — nothing rewritten):BondsService.requestBond(),TransfersService.rejectReturn(), andReportsService.create()/review()previously had zero audit/notification wiring; now they do./notificacionesfull inbox (filters, search, grouping, bulk actions, infinite scroll via keyset pagination),/configuracion/notificacionespreference center, a live-badge abstraction (PollingLiveSourcein production,InMemoryLiveSourcefake), and the existing bell refactored onto that same abstraction without behavior changes.Verified
apps/api: 276 tests passing (concurrency/property tests for exactly-once delivery, per-recipient ordering, backpressure/DLQ; property tests for DST-correctness and digest windowing; XSS/snapshot tests for templates),tsc --noEmitclean,npm run build/lintclean.apps/web: 42 tests passing,tsc/lint clean,npm run buildclean (confirmed the one prerender quirk hit during verification — Next.js needingNEXT_PUBLIC_SUPABASE_*publishable config to prerender any Supabase-client page — is pre-existing onmain, not introduced here; verified via a control build againstorigin/main)./notificacioneswas missing frommiddleware.ts's protected-route list (separate commit).Known, documented gaps (not silently papered over)
InMemoryRealtimeTransportin production wiring today;WebSocketRealtimeTransportexists but isn't wired to a live HTTP-upgrade/gateway endpoint yet.TransfersService.rejectReturn()doesn't changetransfers.status, so it doesn't trigger the outbox (only the direct audit/notification calls added here cover it) — documented rather than worked around, to avoid changing existing status-transition behavior outside this issue's scope.Test plan
npm run build/npm run lint/npm run testpass inapps/apiandapps/webwith no credentials set/notificacionesand/configuracion/notificacionesrender/redirect correctly (full login flow not exercised — would require live Supabase credentials, out of scope)🤖 Generated with Claude Code