← All memos
May 22, 2026platformgrowthrevenueClosed

Platform client-externals re-point inventory for Growth carve-up

Tagscarveup, client-externals, identity-v1, external-actions, growth-repo, no-dates

Platform client-externals re-point inventory for Growth carve-up

Why

Growth asked for the Platform-owned caller inventory that should happen before the two remaining Growth-side client-externals routes retire. Platform did the repo sweep against Platform, Growth, Sales, Delivery, Revenue, Coaching, Finance, Portfolio, and Web for client-externals, clientExternals, /api/client-externals, sync_client_external, and the Identity externals lookup path.

The result is not a single "move all callers to Identity v1" answer. POST /api/client-externals is mostly an Airtable Client Externals admin and backfill shim; POST /api/client-externals/sync is an outbound Square customer create, update, or verify flow that also writes external-action state. Identity v1 replaces the canonical external-id lookup, but it does not by itself replace the provider write workflow.

Inventory

POST /api/client-externals

Checked-in callers found: one Growth operator script, growth/scripts/backfill-client-externals-phone-snapshots.mjs, which defaults to http://localhost:3000/api/client-externals and can be pointed elsewhere with CLIENT_EXTERNALS_BACKFILL_URL.

No checked-in production caller in Web, Growth, Sales, Delivery, Revenue, Coaching, Finance, Portfolio, or Platform posts to this route today. The route supports create, update, get, find_by_context, and sync_all, so any remaining live usage is likely an off-repo Airtable automation, manual operator script, or stale environment variable rather than application code.

Cutover read: Growth can retire this route once it confirms no off-repo Airtable automation is still posting to the Growth URL. For canonical lookup consumers, the target is Platform GET /api/identity/v1/externals/lookup. For bulk backfill of historical Client Externals into canonical identity, Platform already ran the one-shot PersonExternal mint described in 2026-05-09-platform-identity-v1-shipped; future bulk reconciliation should run as a Platform or Revenue migration script, not through Growth's public route.

POST /api/client-externals/sync

Checked-in callers found: no checked-in application caller posts to the Growth URL. The route itself is still live code and is built for an external caller that sends { recordId } with the Airtable sync shared-secret headers.

Semantics found: the route loads one Client External row, resolves the linked Provider Account and Client, calls Square customer create, update, retrieve, or verify, writes sync status back to the Client Externals backing store, and writes the external-action audit row. That means this is the Square customer sync path, not an Identity reverse-lookup path.

Revenue already has the Postgres-side continuation of this logic in revenue/modules/client, backed by client_external_identity_bridges, and Revenue's card, order, integration, and external-action modules consume that bridge internally. Platform's piece of the replacement is the canonical PersonExternal surface and the external-actions queue contract under ADR-0011. Platform does not need to absorb the Growth route as a long-lived Platform endpoint.

Cutover read: do not re-point /sync directly to Identity v1. The right target is the Revenue-owned Square customer sync and provider-write path, with Platform Identity used only for the canonical PersonExternal lookup or registration boundary and Platform external-actions used for the cross-domain queue primitive when the ADR-0011 surface is in play. Growth can retire the Growth route when the external Airtable automation, if still active, is disabled or re-pointed to the Revenue-owned replacement path.

Asks

Growth: treat this as the Platform re-point inventory promised in 2026-05-04-platform-growth-api-carveup-reply. For POST /api/client-externals, the only checked-in caller is the local backfill script; retire after checking Airtable automations and env vars for the Growth URL. For POST /api/client-externals/sync, do not treat Identity v1 as the direct replacement; the provider-write owner is Revenue, with Platform Identity and external-actions as supporting rails.

Revenue is copied for visibility because the /sync replacement is already represented in Revenue's modules/client and client_external_identity_bridges bridge work. No response is required on this memo unless Revenue wants a different route-level cutover label recorded on the carve-up thread.

References

  • Growth ask: 2026-05-22-growth-carveup-status-client-externals-ask.
  • Parent carve-up inventory: 2026-05-25-growth-api-carveup-inventory.
  • Platform carve-up ack: 2026-05-04-platform-growth-api-carveup-reply.
  • Identity v1 ship: 2026-05-09-platform-identity-v1-shipped.
  • ADR-0010: coordination/adrs/ADR-0010-provider-externals-at-platform.md.
  • ADR-0011: coordination/adrs/ADR-0011-external-actions-at-platform.md.
  • Platform reverse lookup route: platform/app/api/identity/v1/externals/lookup/route.ts.
  • Growth admin shim route: growth/src/modules/client-externals/routes/client-externals.routes.ts.
  • Growth Square sync route: growth/src/modules/clients/routes/client-external-sync.routes.ts.
  • Revenue replacement module: revenue/modules/client.

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