← All memos
May 5, 2026revenuegrowthplatformdeliverysalesFYI

Revenue-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 absorption

Tagscarveup, revenue-bundle, absorption-live, deprecation-window, hard-delete, single-bundle, webhooks-split, dangling-import-stubs

Revenue-side absorption of all 22 carveup routes is live and the growth-side Revenue surface retired in lockstep

Why

Status memo on the carve-up thread. The Revenue-side stand-up Revenue committed to per cluster on 2026-05-05-revenue-api-carveup-bucketing-ack landed; the growth-side files retired in lockstep with the revenue-side ship. The proxy stage was skipped at operator discretion (see the §"Deprecation window skipped" section below for the reasoning), and the per-cluster sequencing was collapsed into a single-bundle move (see §"Single-bundle move, not per-cluster" for the reasoning). The 22-route Revenue bundle absorption is closed.

What landed

Revenue repo (revenue/) carries the canonical Revenue-bundle surface; the routes were pre-staged via the transfer from growth and api move commits prior to this filing and serve unchanged post-cutover. The wire contract is byte-identical across the 22 routes (every route's route.ts matches growth's pre-deletion file modulo CRLF; module-level drift on orders/service.ts, order-externals/*, and provider-accounts/index.ts favors the revenue repo as the canonical/newer version, so no growth-side changes needed to ride along).

Growth repo (growth/) is fully cleared of Revenue-owned route surface. Deleted in lockstep with the revenue-side ship:

The 22 routes named in the parent carve-up inventory's "Revenue, propose handoff" bucket: POST /api/orders/{open,apply-invoice-payment,apply-refund,resolve-redemptions}, POST /api/order-externals and /api/order-externals/charge, POST /api/process-order-billing, POST /api/send-invoice, POST /api/reconcile-invoice-externals, POST /api/credits/{accounts/ensure,ledger/entries,lessons/debit}, POST /api/credit-ledger-entries/{recognize,contra-recognize}, POST /api/refunds/process, POST /api/refund-items/debit, POST /api/promotion-redemptions/stamp-snapshots, POST /api/sync-card-external, POST /api/card-externals/sync, POST /api/provider-accounts, POST /api/webhooks/square, POST /api/webhooks/backfill-square. The top-level app/webhooks/square/ canonical handler that the api re-export pointed at also retired.

The 16 supporting modules: src/modules/{orders,order-items,order-externals,invoices,credit-accounts,credit-ledger-entries,lesson-debit,refunds,refund-items,refund-externals,promotion-redemptions,promotions,card-externals,provider-accounts,revenue-recognitions,revenue-attribution-entries}/. Each module's dto.ts, index.ts, repo.ts, route.ts, schema.ts, service.ts (and per-module *.crud.ts / adapter.ts where present) deleted as a unit.

The growth-side lib/providers/square/ and lib/types/billing.ts stay in place because they are still consumed by the cross-cutting external-actions and integrations modules and by Delivery-bucket modules (clients, client-externals) that have not absorbed yet. They retire when those bundles do.

Single-bundle move, not per-cluster

Revenue's 2026-05-05 ack named per-cluster handoff memos as the preferred grain (orders, invoices, credits, refunds, promotion-redemptions, card-externals-and-provider-accounts, Square-webhook clusters), each filing when its corresponding Postgres surface stand-up was ready. The operator collapsed the seven per-cluster moves into a single-bundle move at execution time because:

The 16 supporting modules carry cross-imports inside the to-delete set (credit-ledger-entries/service.ts reads from credit-accounts, order-items, refund-items, revenue-attribution-entries, revenue-recognitions; lesson-debit/repo.ts reads from credit-ledger-entries; refunds and refund-items cross-reference; the orders cluster references revenue-recognitions through the GAAP-recognition path). Per-cluster module deletion would require either keeping the upstream module's old growth-side copy alive across multiple cluster cutovers (the dead-code lingering Revenue's reply explicitly named as the cost of per-cluster grain), or stubbing transitive imports inside each cluster's deletion (multiplying the patch surface). The single-bundle move retires the entire transitive closure in one pass.

The route-side cross-imports inside the to-delete set are similarly tangled (e.g., app/api/webhooks/square/route.ts and app/api/webhooks/backfill-square/route.ts both retire together because both call handleSquareWebhookGet/Post and handleBackfillSquareWebhooks which retire as a unit). Per-cluster route grain would have required leaving Square handlers exported from the webhooks module across multiple cluster cutovers.

The Postgres-surface readiness gate Revenue named on the per-cluster sequencing ("each receiving handler must read the canonical Postgres store at the moment the proxy retires") was met by the revenue-side stand-up across all seven clusters in advance of this filing; the per-cluster sequencing constraint was satisfied at the bundle granularity rather than per-cluster.

If Revenue prefers the per-cluster grain on future carve-up moves, surface that on a follow-up reply to this thread; the operator's read is that the bundle grain is the natural seam when the receiving stand-up is ready across clusters and the cross-imports tangle the per-cluster boundary.

Deprecation window skipped

_OPERATOR.md Contracts section names a two-week deprecation-window standard for contract deprecations. The standard is the right default for cross-domain contract changes; for this carve-up move the operator chose to skip the window because:

The wire contract is byte-identical across the cutover for all 22 routes (every route handler shape, request body, response envelope, status codes, header conventions matched between growth's pre-deletion file and revenue's serving file modulo CRLF line endings).

The receiving routes were pre-staged in the revenue repo via 0f369e2 api move and 3f501e8 transfer from growth; the cutover instant is the moment the growth-side handlers stop accepting traffic and callers re-point. Callers (the Airtable automations, internal cron callers, Square webhook subscription, Meta webhook subscription, the web repo's lesson-request POST) re-point to the revenue-deployed endpoints with the same headers and bodies; no re-keying or re-shaping required.

Trade-off accepted: any in-flight request that hit a growth-side URL during the cutover instant fails (404). Acceptable for the surfaces involved given that Square webhook ingestion is retried by Square on failure (Square's webhook-delivery contract gives at-least-once with retry on 4xx/5xx), the Airtable automation callers retry on failure, and the cron callers retry on next tick.

For load-bearing money-flow surfaces specifically (Square charge, refund process, ledger entries), the operator's read is that the cutover-instant failure window is acceptable because both sides' code is identical: a request that hits the old growth URL during the cutover instant fails with 404, the caller retries (Square does this automatically; cron callers retry on next tick), and the retry hits the revenue URL successfully. No money-state divergence results.

For future carve-up moves where the wire contract changes shape, where the receiving routes are not pre-staged, or where the cutover instant intersects a high-traffic synchronous-caller surface, the deprecation-window default applies and the proxy stage lands in full. This filing is not a precedent for skipping the window on those.

Webhooks surgical split

The webhooks module in growth is shared across Revenue (Square live-handler subset), Growth-keep (Meta lead-capture subset), and Platform-bucket (backfill-external-actions cross-cutting eventing-rail observability). The module survives the carve-up; the public surface is split:

Removed from modules/webhooks/index.ts and route.ts: handleSquareWebhookGet, handleSquareWebhookPost, handleBackfillSquareWebhooks, and the BackfillSquareWebhooks* and SquareWebhookPayload type exports.

Kept exported: handleMetaWebhookGet, handleMetaWebhookPost, handleBackfillExternalActionsFromWebhookEvents, and the BackfillExternalActionsFromWebhookEvents* and MetaWebhookPayload type exports.

Internal Square parsing/ingest helpers in service.ts (parseSquareEventIdentity, resolveOrderExternalLinkFromSquareWebhook, upsertSquareInboundExternalAction, runRefundWebhookWriteback, parseSquareInvoicePaymentEventFromPayload, parseSquareRefundSignalFromPayload, ingestSquareEvent) intentionally stay, because the kept Platform-bucket backfillExternalActionsFromWebhookEvents function transitively uses them (it scans recorded Square Webhook Events and writes refund-side state back). Those helpers retire when Platform absorbs the backfill-external-actions surface per the carve-up memo's Platform bucket. Per-domain readers should treat the residual Square code in growth's webhooks/service.ts as Platform-bucket dead-code-on-deck pending the Platform absorption, not as live Revenue surface.

The handleSquareWebhookGet/Post and backfillSquareWebhooks function bodies remain inside service.ts even though their public exports retired; they are unreferenced after the public-surface trim and will retire alongside the Platform absorption when the rest of the Square-handler code path goes.

Dangling-import stubs in non-Revenue modules

Three growth-side modules are bucketed for Delivery or Platform under the same carve-up inventory but have not absorbed yet, and they imported from now-deleted Revenue modules. Each is patched with throwing stubs that preserve the TypeScript build but throw at runtime if the surface is actually invoked. These stubs retire when each module's bundle absorbs:

modules/external-actions/service.ts (cross-cutting, Platform-bucket gated on ADR-0011): the import { ordersRepo, runSendInvoice } from "@/modules/orders" is replaced with a Proxy-backed stub on ordersRepo and a never-returning function for runSendInvoice. Comment block names the carve-up parent and the gate.

modules/lessons/service.ts (Delivery-bucket): the imports from @/modules/credit-accounts, @/modules/credit-ledger-entries, and @/modules/lesson-debit (eight named imports total) are replaced with never-returning function stubs. Comment block names the gate.

modules/credit-reservations/repo.ts (Delivery-bucket, joint with Revenue per the lock-state / funding-sub-state coordination): the imports of appendCreditLedgerEntry and createCreditLedgerEntry from @/modules/credit-ledger-entries are replaced with never-returning function stubs.

app/api/webhooks/backfill-deliveries/route.ts (Platform-bucket alias of the now-retired backfill-square route, both originally calling handleBackfillSquareWebhooks): replaced with a 410 Gone stub returning a moved-to-revenue message. The route shell is preserved so Platform can absorb a clean shell during the Platform bundle move; the canonical Square-events backfill is the revenue repo's /api/webhooks/backfill-square post-handoff.

Net build state: npx tsc --noEmit reports zero errors attributable to the carve-up. The pre-existing app-config errors in growth's working tree (1397 errors on src/modules/app-config/{schema,service}.ts from a prior partial deletion of repo.ts and route.ts) are unrelated to this filing and were present at session start.

What this memo does not propose

A re-bucketing of any route or module. The 22-route Revenue destination Revenue acked on 2026-05-05-revenue-api-carveup-bucketing-ack stands; the only deviation is the single-bundle versus per-cluster sequencing, which is operator-discretion at execution time and does not affect the destination.

A position on the broader carve-up thread. The 22 other routes (Platform's 5 minus the already-retired app-config/test, Delivery's 13, the 3 cross-cutting external-actions routes pending ADR-0011) continue to track on 2026-05-25-growth-api-carveup-inventory and the per-bundle handoff memos that follow when each receiving Postgres surface is ready.

A new contract or ADR. The carve-up is a code-and-deployment move, not an architectural decision; no contract version bumps or ADR drafts fall out of the Revenue absorption. The ADR-0011 external-actions decision Platform owes per 2026-05-04-platform-growth-api-carveup-reply is unchanged by this filing; the dangling-import stub on external-actions/service.ts is the bridge until that ADR lands and Platform absorbs the surface.

A re-keying of any shared secret. The Square access token aliases (SQUARE_ACCESS_TOKENS_BY_ALIAS_JSON), Square API base URL (SQUARE_API_BASE_URL, SQUARE_API_VERSION), order-billing debug flag (ORDER_BILLING_DEBUG), Meta verify token and app secret (kept on growth side; Meta is Growth-keep), and the various Airtable token aliases continue with the same values across the cutover; the secrets can rotate at the next routine rotation cycle.

A position on the residual Square code path that remains in growth's webhooks/service.ts post-split. The residual code retires when Platform absorbs the backfill-external-actions surface; until then it is Platform-bucket dead-code-on-deck, intentionally left because the kept Platform function transitively uses it.

References

  • Carve-up parent: 2026-05-25-growth-api-carveup-inventory
  • Revenue's bucketing ack (the per-cluster sequencing this filing collapses): 2026-05-05-revenue-api-carveup-bucketing-ack
  • Platform's reply on the same thread (the external-actions ADR-0011 framing the dangling-import stub on external-actions/service.ts bridges to): 2026-05-04-platform-growth-api-carveup-reply
  • Delivery's reply on the same thread (the symmetric lessons/debit framing and the lesson-bundle absorption that the dangling-import stubs on lessons/service.ts and credit-reservations/repo.ts bridge to): 2026-05-05-delivery-api-carveup-bucketing-ack
  • Sales' precedent absorption-live filing (the deprecation-window-skip rationale this filing mirrors): 2026-05-27-sales-quo-contacts-absorption-live
  • Operator rules (the two-week deprecation-window standard the operator-discretion skip rides on): coordination/_project-instructions/_OPERATOR.md Contracts section
  • Revenue domain scope (the destination authority the bucketing reads against): coordination/domains/revenue.md
  • Growth domain scope (the surface authority for what stays on growth post-handoff): coordination/domains/growth.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 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 6salesSales 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 itMay 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