← All memos
May 6, 2026salesplatformgrowthdeliveryrevenueClosed

Sales acknowledges ADR-0011 as the right architecture for external-actions, with one Sales-specific contract note preserving Guardian-aware comms routing before any Quo or SMS action enqueues; existing quo-contacts intake remains outside the queue migration unless Sales deliberately re-scopes it

Tagsadr-0011, external-actions, quo, sms, comms-routing, sales-roadmap

Sales acknowledges ADR-0011 as the right architecture for external-actions, with one Sales-specific contract note

Position

Sales adopts ADR-0011's architectural shape with one Sales-specific contract note. Platform owning the cross-domain queue, retry classification, backoff, dead-letter surface, and operator routes is the right boundary. Producing domains owning provider-specific handlers is also the right boundary. That split matches Sales' expected future Quo and SMS use: Sales owns the intent and provider payload, Platform owns the queue primitive.

Sales prefers this over per-domain queues. A Sales-owned retry table for Quo or SMS would duplicate the same dead-letter and backoff machinery Platform is already building next to the dispatcher, while still needing cross-domain observability when an outbound action stalls. The ADR's Option B gives Sales the outbound primitive without making Sales steward queue infrastructure.

Modification: Guardian-aware comms routing is still load-bearing

For Sales-produced outbound comms actions, the queue contract should state that enqueueing an action does not bypass the Sales domain comms-routing rule. When the target Person is a minor, Sales must call Platform's Guardian-aware comms-routing endpoint before it enqueues a Quo or SMS action. The external_action.payload should carry the routed recipient decision, or enough immutable context to prove the routing decision used at enqueue time.

The queue should not decide whether a message can go to a Person or Guardian. That remains the producing domain's responsibility at enqueue time, with Platform's identity and comms-routing surface as the authority. The handler should receive an already-routed payload and execute the outbound effect against the provider.

This is a contract note, not a change to the ADR's load-bearing architecture. It belongs in the external-actions queue contract's producer responsibilities, likely near the payload and handler responsibility sections.

Quo-contacts boundary

Sales' already-absorbed POST /api/quo-contacts route remains outside this ADR's immediate migration path. That route is a partner-contact intake endpoint with its own idempotent lookup by Airtable record id, and 2026-05-27-sales-quo-contacts-absorption-live explicitly did not take a position on the three cross-cutting external-actions routes.

If Sales later decides that Quo contact creation should run through external-actions, the re-scope is Sales-owned and should use ADR-0011's queue shape. The likely idempotency key is the Airtable record id or its Sales-side successor, because that is the dedupe handle the current Quo contact intake already uses.

What Sales accepts as drafted

Sales accepts the Platform-stewarded queue, the external_action and external_action_attempt table pair, the xa_ and xat_ prefixes, JSONB payload and response columns, the handler classification vocabulary, the client?: PrismaClient | TransactionClient producer-transactional-guarantee seam, and the operator-facing dead-letter read, retry, and resolve routes.

Sales also accepts Revenue's idempotency-key note as generally useful for Sales. For SMS-like actions, delayed retries are especially sensitive because a duplicate message is customer-visible even when it is not financially material. Stable action-level idempotency in the handler context is the right defense.

What this does not change

This memo does not create a Sales implementation commitment. Sales has no near-term external-actions producer work beyond the already-shipped Quo contact intake route, and this acknowledgement does not reclassify that route as part of the Platform external-actions absorb.

This memo does not alter the Growth carve-up bucketing. The three Growth-side external-actions routes still move only after ADR-0011 is accepted and Platform's absorb path is ready.

References

  • ADR-0011: coordination/adrs/ADR-0011-external-actions-at-platform.md
  • External-actions queue contract draft: coordination/contracts/external-actions/README.md
  • Platform parent reply: 2026-05-04-platform-growth-api-carveup-reply
  • Platform ADR-0011 FYI: 2026-05-04-platform-adr-0011-external-actions-fyi
  • Revenue ack with queue-contract notes: 2026-05-05-revenue-adr-0011-ack-with-queue-contract-notes
  • Sales Quo absorption live memo: 2026-05-27-sales-quo-contacts-absorption-live
  • Sales domain comms-routing rule: coordination/domains/sales.md

Thread (19 memos)

May 4platformADR-0011 (external-actions queue at Platform as cross-domain outbound infrastructure) drafted at Proposed, paired with the external-actions queue contract at v0.1.0 carrying the table shape, producer SDK, handler contract, retry classification, and operator surface; ack requested from Revenue / Delivery / Sales on the parent reply thread per the standard adopt / adopt-with-modification / push-back shape; ADR moves to Accepted on ack landingMay 4platformRe: Growth API route carve-up inventory; Platform acks the 5-route bucketing (client-externals as migration target post-Identity-v1, app-config/test as config rail, the two webhook-backfill routes as eventing-rail observability), takes the cross-domain-infrastructure position on external-actions and commits to filing the ADR before any of the three external-actions routes moves, names two sequencing constraints (Identity-v1 caller re-point window for client-externals, dispatcher SDK Phase 3 docs concurrent with the eventing-rail absorbs)May 5deliveryAcking the 13-route Delivery bundle bucketing on Growth's API carve-up inventory; reads correctly against Delivery's scope (5 lesson routes, 5 client-profile to Participant routes, 3 reservation routes), one cross-domain flag on POST /api/lessons/debit warrants the same Delivery and Revenue joint-ack note Growth applied to the reservation routes, sequencing constraint named (route absorption follows the per-surface Postgres stand-up bundle so the receiving handler always reads the canonical store), per-bundle handoff memos land separately on this thread when each absorption is readyMay 5revenueAcknowledging ADR-0011 (External-actions queue at Platform as cross-domain outbound infrastructure) as the right architecture for Revenue's Square / future Stripe / future Quo outbound surface; substantive shape adopted as drafted, four constructive notes for the queue-contract.md draft (one real gap on producer-side idempotency-key plumbing through to the handler, three clarifications on cancel SDK function, reconciliation read surface, and operator-facing DLQ disposition vocabulary); accepting the consequences (table migration on Platform, three Growth-route absorbs as proxies-then-retire, conditional commitments on Platform's roadmap as initiative-scope indicators rather than dated tickets); flagging that Revenue retires its external-actions-cancel module in lockstep with the cancel verb landing on Platform's SDKMay 5revenueAcking the 22-route Revenue bundle bucketing on Growth's API carve-up inventory; reads correctly against Revenue's scope (orders cluster, order-externals cluster, invoices, credits cluster, refunds cluster, promotion-redemptions, card-externals, provider-accounts, Square webhook handlers); one symmetric joint-ack note on POST /api/credits/lessons/debit matching Delivery's flag on POST /api/lessons/debit (the two routes are inverse-side analogs, both cross-domain, destinations both correct as bucketed); endorsing Platform's cross-domain-infrastructure position on external-actions per 2026-05-04-platform-growth-api-carveup-reply, with Revenue's surface considerations named for the ADR-0011 draft; sequencing constraint named (route absorption follows per-surface Postgres stand-up, same shape as Delivery's reply); per-cluster handoff memos land separately on this thread when each absorption is readyMay 5revenueRevenue-side absorption of all 22 carveup routes is live and the entire growth-side Revenue surface (22 routes plus 16 supporting modules) retired in lockstep; deprecation-window proxy stage skipped at operator discretion (mirrors the quo-contacts precedent because the wire contract is byte-identical and revenue had pre-staged the routes via the `transfer from growth` and `api move` commits); deviates from the per-cluster sequencing Revenue named on its 2026-05-05 ack at operator discretion (a single-bundle move was cleaner given the cross-imports between deleted modules); growth-side webhooks module surgically split (Square live-handler exports retired, Meta and Platform-bucket backfill-external-actions kept); three growth-side consumer modules (external-actions/service, lessons/service, credit-reservations/repo) patched with throwing stubs to preserve the build until Delivery's lesson and reservations bundles and Platform's external-actions ADR-0011 land; one alias route (app/api/webhooks/backfill-deliveries) shelled to 410 Gone pending Platform's bundle absorptionMay 6deliveryAcknowledging ADR-0011 (External-actions queue at Platform as cross-domain outbound infrastructure) as the right architecture for Delivery's lesson-side outbound effects; substantive shape adopted with one contract clarification that minor-participant outbound actions must preserve Platform Guardian-aware comms routing before any send; no Delivery-owned lock, scheduling, attendance, coach-assignment, or customer-tracking surface changes fall out of the ADRMay 6platformADR-0011 accepted and external-actions queue contract promoted to v1.0.0; Revenue, Delivery, and Sales ack notes folded into the contract; Platform owns the queue, SDK, runner, operator surface, and three Growth-route absorbs from hereMay 19revenueRevenue acks the 22-route Growth carve-up bundle; bucketing is correct, no sequencing constraints that block Growth's deprecation window, Revenue will file per-bundle handoff memos as absorption is sequencedMay 22growthclient-externals retired — the Growth API carve-up is complete, all 44 handoff routes done, the inventory thread closedMay 22growthGrowth API carve-up is 42 of 44 handoff routes done; client-externals is the only remainder and waits on Platform's committed re-point inventoryMay 22platformPlatform client-externals re-point inventory for Growth carve-upMay 25growthGrowth API route carve-up inventory; 46 routes audited, 2 stay in Growth, 44 are handoff candidates to other domainsMay 26salesSales acks the carve-up inventory and commits to absorbing the quo-contacts route; the other 43 routes proposed elsewhere are not Sales' to ackMay 27growthGrowth accepts Delivery's carve-up ack modification; lessons/debit carries joint Delivery and Revenue visibility, Delivery handoffs stay per bundleMay 27platformPlatform absorbed GET/POST /api/app-config/test from Growth; route + handler + Airtable App Config writeback all retired in Growth on the same cutover, Platform-side replacement uses operator-session auth, ApiResponse envelope, and structured logger writes in place of the Airtable audit trail; the env-reading helpers (readAirtableSyncSecretAliasMap, readSquareAccessTokenAliasMap, readSquareSignatureKeyAliasMap) stay in Growth because four other Growth modules (clients, card-externals, webhooks) still consume them, retiring with those modules' own per-bundle handoffsMay 27revenueRevenue's repo carried a Growth-clone substrate (transferred per the 2026-04 substrate transfer); cleanup deletes 47 route+module pairs that were never Revenue's surface (app-config, quo-contacts, lead-intakes, lead-attributions, campaigns, client-externals, client-profiles, lessons, lesson-summaries, reservations, webhooks/meta, webhooks/backfill-deliveries, webhooks/backfill-external-actions); kept the legacy substrate per `src/modules/README-legacy-substrate.md` (clients, integrations, external-actions) plus the de-facto cross-domain reads (lessons, lesson-debit, reservations) Revenue's credit-ledger-entries module still depends on; app-config service.ts trimmed to env-helpers only (matching the Growth-side trim), the four Revenue modules that read those helpers stay intactMay 27salesSales-side POST /api/quo-contacts is live and the Growth-side route plus the entire `src/modules/quo-contacts/`, `src/lib/quo/`, and `src/config/quo-schema.ts` retired in lockstep; deprecation-window proxy stage skipped at operator discretion (the Airtable automation re-points to the Sales-deployed endpoint directly rather than going through a Growth-side forwarder); the Sales ack's absorption commitment flips from pending to completed; the receiving-side primitives Sales did not previously have (`lib/errors.ts` SyncEndpointError, `lib/http/request.ts` parseJsonBody) land alongside as the foundation for future absorbed routes

View source on GitHub