Skip to main content
Proposed (STA-806). It names the ports through which 0356 (a provider per tenant and Market) is carried out, and settles that ADR’s open question 1. It reads with 0308 (connections), 0355 (one Integration per provider) and 0370 (market packs). It answers the wave 10 notifications builder’s questions 1, 2 and 9 and the payments builder’s question 7.
Amended 2026-10-26 (STA-1282, capability map M2, owner decision R3-1): decision 1’s list gains verification (KYC and KYB, pack key providers.verification). Figentra’s own checks of a tenant are pinned to platform Connections (ProviderResolver#candidates(..., { pin: "managed" })), so a tenant’s own account never answers them; a tenant’s checks of its own customers resolve the tenant’s own Connection first, then the managed one. @stackra/identity keeps the Verification port and runs the list in VerificationService#verifyThrough.

Context

Wave 10 built, in @stackra/integrations, a ProviderResolver service over three dependencies (the ConnectionStore, a ProviderDefaults port and a MarketContext port), a CredentialRef type <provider>:conn_<id>, and a VoiceGateway interface for telephony. It left open which Market keys a non-payment capability, who binds the two ports, and how a tenant gets a provider Figentra runs for it. Since then the owner set the principle “managed or bring your own” for every provider category (2026-10-26), review round 2 named the records the ports read (X2-1, X2-18), round 3 named the managed half (R3-SO-03), the overlay subject (R3-SO-01) and the voice split (R3-SO-11), and X2-30 found that a licensed Saudi carrier offers a SIP trunk, not conversational steps.

Decision

  1. ProviderResolver is the one choice rule. Every capability with several providers (SMS, WhatsApp, voice, speech, mail, geocoding, accounting, tax, terminals, shipping) resolves the Connection for a call through ProviderResolver: the tenant’s own Connection of a provider the Market offers wins; otherwise the Market’s platform Connection serves; a stored record reloads the exact Connection that served it. Payments keep their own PaymentGateways resolver on the same rule. @Replaces is never used for this (0356 decision 1).
  2. ProviderDefaults reads two records. Its driver reads market-pack:<code>.providers for what depends on the market (payments, sign-in, messaging, tax, terminals, telephony) and region-map:<region>.providers for what depends on where the tenant’s data lives (mail, AI, speech, storage), and returns the providers in order with the platform Connection of each (X2-1). A tenant changes its provider through its own Connection, never by editing a pack.
  3. MarketContext resolves by subject. Each pack key declares the subject it applies to: location, buyer, entity or tenant (R3-SO-01). MarketContext answers the Market of a call (market(), as built) and gains packFor(record, subject) for record rules. The seeded subjects for providers are: SMS, WhatsApp and voice by the recipient (buyer), because sender-ID, consent and quiet-hours rules are the destination’s law; mail, accounting and fiscal by the legal entity (entity); payments by the checkout’s Market (0244); a shared process (a data subject request clock, retention, a breach notice) takes the strictest active pack (RV-4, X2-18). The subject of a key is pack data; the driver lives in the OS tenancy service.
  4. Who binds the ports. @stackra/market-packs verifies and resolves packs; the OS tenancy and markets service fetches pack versions (through @stackra/assets content hashes), and binds ProviderDefaults, MarketContext and the other pack readers (MarketPaymentDefaults, FiscalPackSource, settings floor sources) over it. No Stackra package fetches a pack.
  5. Managed or bring your own. A Connection is one of two modes: managed (Figentra’s account and key, plus a per-tenant resource on it: a messaging subaccount and its registrations, a connected payments account, a sending domain, a WhatsApp number, a tax company) or own (the tenant’s own account and key). An Integration that offers the managed mode implements ManagedProvisioning (create, verify, suspend and remove the per-tenant resource, report its status and its cost events), run by workflow-template:os:managed-provisioning:<category>. Which modes a category offers per pack, and the fee of each, are provider-category:<category> and rate-card records (R3-SO-03). The Connection carries mode: live | test; the tenant kind sets it (X2-24).
  6. Credential references. A record that must remember an account stores a CredentialRef, <provider>:conn_<id>, never a secret. ProviderResolver#byRef loads that Connection through the store, which applies the caller’s scope and decrypts. A reference whose Connection is gone or out of scope refuses; it never falls back to another Connection. A reference is classified internal; the Connection’s secrets stay in the integrations vault.
  7. Telephony is two ports. VoiceGateway (in @stackra/integrations/contracts) places, steers, records and ends calls over a provider (Twilio) or a SIP driver for a carrier that only gives a trunk. Speech (streaming speech-to-text and text-to-speech) is a separate port; language and dialect come from pack data, the provider from region-map:<region>.providers.speech (in-region and without retention for the health and privileged classes), and a device-audio driver serves drive-through and till microphones. The voice provider per pack is pack data (X2-30). Calls and speech are metered as voice.minutes and speech.seconds (R3-SO-11).
  8. One Meta Integration. WhatsApp, ads, catalogue and tracking are roles of one Meta Integration in @stackra/integrations (0355 decision 5); notifications uses its WhatsApp role.
  9. Values. Pack data: providers.* per market, each key’s subject, telephony and speech providers, recording notice floors. Seed data: region-map providers, provider-category records, rate cards, managed-provisioning workflow templates. Settings: none. Rule sets: message channel order and fallback (rule-set:os:message-fallback). Deployment data: platform Connection ids.

Consequences

  • A provider change for a market is a pack edit; a tenant’s own provider is an app-market install.
  • Every call can say which Connection served it, which reconciliation and audit need.
  • The SIP driver and the Speech port are new build items; the speech-through-voice-provider shortcut of wave 10 goes away.

Enforcement

planned — @stackra/integrations tests: resolution order, byRef refusing a missing Connection, and no fallback for a stored record; the market-pack section schema refuses a providers key without a subject.

Supersedes and sources

Supersedes: none. Settles 0356 open question 1. Sources: notes/handover/build-wave10-2026-10-25.md (notifications builder, questions 1, 2 and 9; payments builder, question 7); notes/handover/cross-product-answers.md (managed or bring your own); notes/handover/review-round-2/cross.md (X2-1, X2-18, X2-24, X2-30); notes/handover/review-round-3/stackra-os.md (R3-SO-01, R3-SO-03, R3-SO-11, R3-SO-19).

Amendment 2026-10-26: decisions 5 and 8, vendor code in OS integrations (STA-1280, 0443)

  • Decision 5. The ManagedProvisioning port, the Connection modes and the provisioning workflow contracts stay in @stackra/integrations. Each vendor’s implementation of ManagedProvisioning lives with that vendor’s Integration in the OS integrations service (owner R3-8). A managed Connection may also point at a self-hosted open-source default that Figentra runs on the regional tools stack (Documenso, PaddleOCR or Surya, Nominatim and Photon, LibreTranslate, Chatwoot; owner R3-5); it resolves through ProviderResolver like any other Connection.
  • Decision 8. The one Meta Integration is an OS integrations class, not a class in @stackra/integrations; the rule of one Integration with several roles stands.