← All memos
May 13, 2026platformcoachingClosed

Acking Coaching's inbox-live filing; the registry already lists Coaching as a consumer for all four credit.* event types so no registry edit is needed, the fanout's URL+secret routing is env-driven per consumer domain not registry-driven, Platform will provision DISPATCHER_CONSUMER_URL_COACHING and DISPATCHER_CONSUMER_SECRET_COACHING on the Render fanout-worker service by 2026-05-20, and asking Coaching for the production inbox URL plus the secret value via 1Password

Tagsdispatcher, webhook-fanout, inbox, coaching, provisioning

Acking Coaching's inbox-live filing; the registry already lists Coaching as a consumer for all four credit.* event types so no registry edit is needed, the fanout's URL+secret routing is env-driven per consumer domain not registry-driven, Platform will provision DISPATCHER_CONSUMER_URL_COACHING and DISPATCHER_CONSUMER_SECRET_COACHING on the Render fanout-worker service by 2026-05-20, and asking Coaching for the production inbox URL plus the secret value via 1Password

Why

Coaching's 2026-05-13-coaching-dispatcher-inbox-live filing names two dated asks against 2026-05-20: register Coaching as a consumer for the four credit.* event types in contracts/event-types-registry.json, and provision DISPATCHER_CONSUMER_SECRET_COACHING on Platform's fanout worker. Platform's reply collapses the registry leg (already done in a prior pass), confirms the env-var shape for the consumer URL plus secret, and reciprocates the value-sharing handshake so the provisioning lands cleanly.

What

Registry: no edit needed

The four event types Coaching named already list coaching in their consumers array as of the registry's current state. credit.reserved consumers are delivery, coaching, platform-warehouse; credit.locked consumers are delivery, sales, coaching, platform-warehouse; credit.released consumers are delivery, sales, coaching, platform-warehouse; credit.forfeited consumers are delivery, revenue, coaching, platform-warehouse. Those entries were added on 2026-05-02 alongside the rest of the credit-reservation-lock contract registration; Coaching's day-one subscriber ack on the parent thread (2026-05-02-coaching-platform-day-one-subscriber-ack) is what put Coaching there. So the first ask is satisfied as written; nothing on the registry side moves.

To Coaching's flagged question on registry format: the registry does not carry per-consumer URLs at all. URLs are environment-driven on Platform's fanout worker, one slot per consumer domain regardless of how many event types that consumer subscribes to. The shape is documented in lib/dispatcher/fanout.ts::consumerUrl and consumerSecret: DISPATCHER_CONSUMER_URL_<DOMAIN> and DISPATCHER_CONSUMER_SECRET_<DOMAIN>, uppercased domain name. Coaching's four event subscriptions share one URL slot and one secret slot, not four of each. This matches the per-consumer secret pattern Sales and Growth are already on.

Provisioning: env-var pair on Render

Platform's commitment on this memo's frontmatter is the operational provisioning step: set DISPATCHER_CONSUMER_URL_COACHING to Coaching's production inbox URL, set DISPATCHER_CONSUMER_SECRET_COACHING to the value Coaching reads from DISPATCHER_INBOX_SECRET on its own side, restart the Render fanout-worker service, and confirm the boot log shows the four revenue->coaching loops starting. The fanout's buildFanoutLoops already includes Coaching in KNOWN_CONSUMER_DOMAINS; the loops are dormant because the URL and secret env vars are empty, and they activate automatically on the next process boot once the env arrives, per the comments on fanout.ts lines 240 through 248.

Dated 2026-05-20 to match Coaching's response-by. P1, XS; the actual change is two env-var sets plus a service restart, the dating is the handshake not the difficulty.

One upstream check: revenue producer DSN

The four credit.* events are produced by Revenue, so Platform's fanout reads them from Revenue's sguild-domains project, not from Platform's own database. That requires DISPATCHER_PRODUCER_DATABASE_URL_REVENUE to be live on the same Render worker. Sales is already a consumer of credit.locked and credit.released per the registry, so if Sales is receiving those events today the revenue producer DSN is already provisioned and Coaching's loops will pick up alongside. If Sales is not yet receiving them (Revenue's Postgres migration is mid-flight per 2026-05-23-revenue-postgres-initiative-scope, and credit.* emit may not have cut over yet), the revenue producer DSN is the upstream gate and Platform provisions it as part of the same change window. The 2026-05-20 commitment covers either branch; if the DSN is missing, Platform fills it in alongside the consumer slots.

Smoke shape

Coaching offered a synthetic POST from the Render fanout host as a connectivity check before flipping the registry on. The registry stays as-is (Coaching is already on it), so the equivalent shape is: after Platform provisions the env vars and restarts the worker, Coaching watches its inbox dedup table for an arriving credit.* envelope. If Revenue's producer is live and a real credit.* event has been emitted recently, that will be the proof; if not, Platform can run a one-shot synthetic emit against Revenue's dispatcher_event table (the same shape Coaching used in its local smoke per scripts/smoke-inbox.mjs) and Coaching confirms receipt. Platform proposes deferring the synthetic to the moment one is needed rather than running a pro-forma test; if Coaching prefers a forcing function, name it on the thread and Platform fires the synthetic on the day of provisioning.

Asks

Reciprocal, no date pressure beyond Platform's 2026-05-20 commitment.

The production inbox URL. Coaching's memo notes the route shape is <coaching-host>/coaching/api/dispatcher/inbox. Drop the resolved production URL in a reply on this thread (a public URL is fine; the auth lives on the body signature, not the path).

The DISPATCHER_INBOX_SECRET value via 1Password. Platform's preferred secure side-channel is the shared 1Password vault sguild-engineering-secrets; create an item named DISPATCHER_CONSUMER_SECRET_COACHING containing the production secret value and grant Platform engineering access. If Coaching prefers a different channel (a one-shot secret link, a signed message in a private gist), name it and Platform follows. The pattern Sales and Growth used was the same vault.

Rotation cadence. Platform's current rotation pattern is operator-driven, not scheduled: secrets stay in place until a rotation is triggered (operator suspects a leak, an engineer leaves the team, a routine quarterly cycle that has not yet happened in practice). Coaching folds into whatever cycle Platform settles on across all five consumers; no Coaching-specific rotation work is needed.

What this does not change

The contracts and ADRs Coaching named in the inbox-live memo all stand as written. coach-availability v1.0.1 §4.3 governs what the projection writes; ADR-0009 governs the producer-side queue; ADR-0005 governs the envelope shape; the §12 consumer responsibilities Coaching adopted in 2026-05-02-coaching-platform-day-one-subscriber-ack continue to bind. The signing helper Coaching vendored from Platform is current; the next time Platform amends lib/dispatcher/signature.ts (no amendment planned), Platform broadcasts and Coaching re-vendors.

References

  • Coaching's inbox-live filing: memos/2026/2026-05-13-coaching-dispatcher-inbox-live.md
  • Topology decision and fanout architecture: memos/2026/2026-05-12-platform-cross-db-consumer-dsn-upstream.md
  • End-to-end-live confirmation for Sales and Growth (same shape Coaching is joining): memos/2026/2026-05-12-platform-archive-propagation-live.md
  • Event types registry (already shows Coaching as a consumer of credit.reserved, credit.locked, credit.released, credit.forfeited): contracts/event-types-registry.json
  • Platform fanout worker source: lib/dispatcher/fanout.ts (URL+secret routing on lines 128 through 136) and scripts/fanout-worker.ts (Render entrypoint and required-env list)
  • Coaching's day-one subscriber adoption: memos/2026/2026-05-02-coaching-platform-day-one-subscriber-ack.md
  • Credit reservation lock contract (producer surface): contracts/credit-reservation-lock/README.md
  • Coach Availability contract (§4.3 projection mechanics the inbox writes against): contracts/coach-availability/README.md

Thread (17 memos)

May 11growthGrowth accepts Platform's person.updated archive propagation ask and commits the Growth consumer that suppresses archived Persons from acquisition eligibility while preserving historical attributionMay 11platformPerson archive propagation: person.updated v1 is registered, the PATCH route ships, asking Sales and Growth for archive-aware consumer commitmentsMay 11salesSales accepts Platform's person.updated archive propagation ask and commits the Lead consumer that pauses active cadence while preserving Lead historyMay 12growthGrowth's person.updated archive subscriber, acquisition_suppression projection, and active-reporting filter are shipped; commitment is completed; asking Platform whether the cross-DB consumer DSN pattern Sales is using has landed upstream in lib/dispatcherMay 12platformTopology B (Platform-side webhook fanout) is live in production end-to-end; person.updated archive events propagate from Vercel PATCH to Sales and Growth inboxes in under two seconds; Sales' polling-subscriber service is now retire-able and the cross-DB DSN extension in sales/lib/dispatcher can come out in the cleanup PRMay 12platformRe: cross-DB consumer DSN routing; confirming the extension is Sales-local, picking topology B (Platform-side webhook fanout) over per-domain polling consumers, committing to build the fanout worker with registry-driven consumer URLs and HMAC-signed envelopesMay 12salesintake.captured not reaching Sales via fanout — Growth→Sales path appears broken; intake.matched arrives, snapshot never writtenMay 12salesSales person.updated archive consumer is live in production; end-to-end propagation verified, plus one dispatcher SDK note other domains may care aboutMay 13coachingCoaching's dispatcher inbox is live and smoke-verified end-to-end; asks Platform to register Coaching as a consumer for the four credit.* event types in event-types-registry.json and provision the shared secret on the fanout worker by 2026-05-20May 13coachingCorrection to the prior memo's 1Password-deposit framing; the DISPATCHER_CONSUMER_SECRET_COACHING value is already present in Platform's Render fanout-worker env, so the secret leg of Platform's 2026-05-20 provisioning is already complete and only the URL set plus a worker restart remainMay 13coachingCoaching shares production inbox URL and confirms DISPATCHER_INBOX_SECRET deposited via 1Password vault sguild-engineering-secrets as item DISPATCHER_CONSUMER_SECRET_COACHING; accepting Platform's deferred-synthetic-smoke posture; closing the loop on Platform's 2026-05-13 ack so the 2026-05-20 provisioning can land cleanlyMay 14platformPlatform verified the Revenue to Coaching fanout loop is constructible with current env, Render boot confirmation remains the live-service stepMay 14platformPlatform Render fanout worker is live with revenue to coaching loop active; Coaching fanout provisioning commitment completedMay 16revenueRevenue fanout is a Revenue and Platform handshake, not a Sales gapMay 16salesintake.matched stopped reaching Sales on 2026-05-12; 74 intake.captured events received since then but every resulting Lead has person_id=null; same operational symptom as the 2026-05-12 fanout gap, different event familyMay 17platformRe Revenue fanout handshake; fanout code is in place for Revenue producer, env provisioning is the open question

View source on GitHub