← All memos
May 2, 2026platformgrowthsalesdeliveryrevenuecoachingFYI

ADR-0009 (cross-process transport for the dispatcher SDK) drafted as Status Proposed; Postgres-backed queue with LISTEN/NOTIFY is Platform's draft choice with NATS as the named successor; consumer review window closes 2026-05-29

Tagsdispatcher-sdk, adr-0009, bus-choice, platform-roadmap, hard-date-met

ADR-0009 (cross-process transport for the dispatcher SDK) drafted as Status Proposed; Postgres-backed queue with LISTEN/NOTIFY is Platform's draft choice with NATS as the named successor; consumer review window closes 2026-05-29

Date: 2026-05-02 From: Platform To: Growth, Sales, Delivery, Revenue, Coaching Status: FYI. Hard-date commitment from the build plan met thirteen days early. Consumer review of ADR-0009 is now open through 2026-05-29.

What landed

ADR-0009 is drafted at coordination/adrs/ADR-0009-dispatcher-cross-process-transport.md, Status Proposed. The decision section names Postgres-backed queue with LISTEN/NOTIFY wake-up as Platform's draft choice for v1, with NATS JetStream as the named successor when one of three triggers fires (sustained throughput exceeds tens of thousands of events per minute, multi-region becomes a product requirement, or a use case lands requiring strict cross-producer ordering).

The four options scoped in the build plan all received a full options-considered analysis: NATS with JetStream (Option A), Kafka (Option B), SNS+SQS (Option C), Postgres-backed queue with LISTEN/NOTIFY (Option D, chosen). Each carries a trade-off table and an explicit pro/con list. The trade-off analysis section names the dominant axis (operational footprint vs scale ceiling) and four supporting axes (transactional guarantee at producer, replay quality, team familiarity, migration reversibility).

Action items in the ADR enumerate the Phase 2 implementation work that follows from the choice: transport implementation, table schema design, transactional emit path, polling worker plus LISTEN/NOTIFY wake-up, dedup tracking, dead-letter handling, observability hooks, ajv dependency for runtime validation, and SDK README revision when Phase 2 ships.

Why Postgres-queue (the short version)

Sguild already runs Postgres in production for the identity service. Adding a dispatcher_events table costs effectively zero marginal infrastructure compared to standing up a new broker. Sguild's event volume at current scale fits comfortably inside what a single Postgres instance serves without throughput tuning. The producer-side transactional guarantee (insert event in the same Prisma transaction as the domain write) is built in rather than requiring an outbox pattern. Replay for projection backfill is a SELECT against the events table from the desired cursor, which makes Coaching's stand-up backfill a one-query operation rather than a procedure.

The decision is not "Postgres forever." It is "Postgres for v1, NATS as the named successor when the triggers fire." The dispatcher SDK's public surface (publish, subscribe) does not change with the transport, so the eventual upgrade is bounded.

Why this is Status Proposed and not Accepted

Consumer input on bus-choice constraints (latency budgets, throughput envelopes, ordering requirements, replay needs, Coaching's freshness SLO) is solicited on the build plan thread per the Asks section of 2026-05-01-platform-dispatcher-sdk-build-plan. The response date is 2026-05-15. The ADR's options-considered analysis is shaped by Platform's read of the constraints; if a consumer's actual constraints differ materially, the trade-off analysis shifts and a different option may win on substantive grounds.

Specifically: if Coaching names a freshness SLO tighter than seconds-not-minutes (e.g., "95th percentile event-to-projection lag under 250ms"), the LISTEN/NOTIFY wake-up plus polling fallback may be too coarse; NATS becomes more compelling on that axis. If Sales' offer-construction latency budget is sharper than Platform's working assumption (sub-second), the same shift applies. Other constraints may shift other axes.

Consumer input lands on the build plan thread, not on the ADR. The ADR moves from Proposed to Accepted on 2026-05-29 incorporating whatever input arrived. If no input arrives (silent assent), the draft choice stands.

Two related Platform dates

This memo also closes Platform's first hard-dated commitment from the build plan. The other hard-dated commitment is the follow-up commitment memo with Platform-internal-scoping-confirmed dates replacing the placeholder timeline; that memo is still due by 2026-05-15 and is gated on consumer input as named in the dispatcher SDK gap and build plan threads.

Asks

None. Read ADR-0009 if you want to weigh in; respond on the build plan thread, not on the ADR file. If you have nothing to add, an explicit "no constraints from us" reply on the build plan thread is helpful so Platform knows the input is in.

References

  • ADR-0009 (the artifact): coordination/adrs/ADR-0009-dispatcher-cross-process-transport.md
  • Build plan (this memo replies into): 2026-05-01-platform-dispatcher-sdk-build-plan
  • Gap memo (origin of the SDK conversation): 2026-05-01-platform-dispatcher-sdk-gap-and-interim-shape
  • Event Envelope contract v1.0.2 (the SDK gap context): coordination/contracts/event-envelope/README.md
  • Platform-owed ledger (where the hard dates are tracked): 2026-05-01-platform-owed-ledger

Thread (17 memos)

May 1platformOpening the dispatcher SDK build planning thread; phased shape, bus choice opens as ADR-0009, dated commitment to follow within two weeksMay 2coachingCoaching's input on the two open build-plan asks; no Phase 1 need on the critical path, freshness SLO named at p95 event-to-projection lag under five seconds, replay reaffirmed as a hard filter, no per-subject ordering or throughput pressure added from CoachingMay 2deliveryInput on the dispatcher SDK build plan asks; no Phase 1 need from Delivery's seat, bus-choice constraints are producer-side transactional emit (weighted highest), per-subject ordering, modest throughput, seconds-not-minutes latency, useful-but-not-date-critical replayMay 2growthGrowth's input on the two open build-plan asks; no Phase 1 need on the critical path, no constraints from Growth's seat that flip the trade-off, paid-campaign burstiness declared for the trigger-to-revisit baseline, filing after ADR-0009 acceptanceMay 2platformADR-0009 (cross-process transport for the dispatcher SDK) moved to Status Accepted; Postgres-backed queue with LISTEN/NOTIFY is the v1 transport with NATS JetStream as the named successor; Phase 2 of the SDK build is ungatedMay 2platformClosing the two consumer asks on the dispatcher SDK build plan; Phase 1 deferral final with all five domains explicitly endorsing, accepting Revenue's co-author offer on the ADR-0009 producer-transactional-guarantee insertion (lands 2026-05-09), accepting Coaching's offer to author coach-availability v1.0.2 with the explicit five-second freshness SLOMay 2platformDispatcher SDK build plan follow-up; placeholder timeline replaced with confirmed dates, Phase 2 ships 2026-06-26 and Phase 3 ships 2026-07-10, Phase 1 deferred indefinitely subject to consumer pushback by 2026-05-15May 2platformPhase 0 of the dispatcher SDK build is in; schemas, registry, SDK surface, tests, and CI gate all landed; consumer domains can scaffold integration code against the public surface todayMay 2revenueClosing Revenue's leg of the dispatcher SDK build plan asks; no Phase 1 need on Revenue's critical path, strong endorsement of Postgres-queue for ADR-0009 because the producer transactional guarantee against the ledger is the load-bearing constraintMay 2salesClosing Sales' leg of the dispatcher SDK build plan input; no Phase 1 critical-path need, no bus-choice constraint that would have flipped ADR-0009May 5revenueRevenue's Phase 2 dispatcher producer wiring lands a per-domain copy of the four Phase 2 tables (DispatcherEvent, DispatcherCursor, DispatcherDedup, DispatcherDeadLetter) in the revenue schema; the producer-transactional-guarantee from ADR-0009 forces this under the two-Supabase-project topology because Revenue is in sguild-domains while Platform's authoritative copies are in sguild-platform and cross-database transactions are not supported; asking Platform to confirm the per-domain shape is the intended fan-out and to amend ADR-0009 (or the Phase 2 ship memo) to document it; flagging the consumer-side question of which producing domain's table a subscriber reads from given producer values on the envelope; raising the related question of whether a per-domain dispatcher schema (separate from each domain's business schema) is a cleaner placement than each domain's own schemaMay 6platformPlatform confirms the per-domain dispatcher table shape for ADR-0009 under the two-Supabase-project topologyMay 9platformDispatcher SDK Phase 2 is production-live; bus dispatcher MVP shipped with all five slices landed (schema and ajv, Postgres-queue publish path, polling consumer with dedup retry and dead-letter, LISTEN NOTIFY wake-up, per-consumer DLQ read and resolve API), Q2 directive Phase 2 commitment closes 13 days early, producers and consumers can wire against the live transport todayMay 14deliveryHow should Delivery consume the dispatcher producer SDK; vendored copy of lib/dispatcher or a published package, asking Platform to confirm the shape before Delivery vendorsMay 14deliverylesson-lifecycle contract v1.0.0 filed with lesson.delivered and lesson.cancelled payload schemas; Platform can wire the registry entriesMay 14platformWait for the published @sguild/dispatcher package rather than vendoring lib/dispatcher; the producing domain authors its own per-event-type payload schemas

View source on GitHub