Platform shims feasibility — BC-side extensions that pair with the marketplace app¶
Generated from a canonical source
This page is a read-only projection of docs/feasibility/platform-shims.md.
Edit the canonical file, then run npm --prefix tools/project-knowledge-derive run derive.
1. Feasibility verdict¶
Yes, and three keystones unlock most of the catalog. Platform-collaborative shims — small typed additive BC-side extensions that our marketplace app composes with — are a viable third delivery lane, sitting alongside pure-marketplace (Path A per ADR-0029) and pure-native (Path B per delivery-fork.md). They are not a compromise between the two paths; they are a distinct model in which the marketplace app remains the runtime owner while BC extends the platform contract in bounded, reversible ways that collapse the shadow stores and monkey-patches the marketplace app currently carries. Fifteen candidates pass the architectural-principles test in §2. Three of them — product.recurrence_options in Catalog, authorize/capture split in the Payments adapter contract, and a sanctioned app-emitted event topic in Domain Eventing — are keystones; nine of the remaining twelve cascade from those three ratifications. The most important reclassification in this analysis: B2B-on-subscriptions is blocked on identity federation between three separate bounded contexts (B2B Edition, Customers, Checkout). No shim resolves that. The correct frame is not "Phase 3, deferred" — it is "blocked-on-other-team, regardless of shim ambition." That distinction matters for roadmap honesty.
2. The architectural-principles test¶
Every candidate must pass eight tests to qualify as a true shim. These tests exist because the failure mode for a misclassified shim is worse than carrying the workaround: a misclassified shim asks BC to own a slice of behavior that belongs to us (policy decisions, lifecycle ownership), introduces a second source of truth that will drift, or — most dangerously — ratifies subscription as a BC-native domain by the back door, collapsing the marketplace/native distinction before BC has committed to owning the domain.
Test 1 — Single bounded context. The change must sit inside one context's natural boundary. An extension that requires a contract between Catalog, Cart, and Checkout is three shims, not one. Cross-context asks belong in §C.
Test 2 — Existing extension pattern. The shim must reuse a documented BC extension point: a typed field addition to an existing schema, a new parameter in an adapter interface, a new slot in an established slot-based extension system (storefront-kit packages, checkout step slots). Inventing a new extension point is a platform architecture decision, not a shim.
Test 3 — Single source of truth. The shim must replace our shadow store with first-class platform data, not create a second source that will drift against ours. A shim that only copies our shadow data to a new BC field — without removing ours — fails this test.
Test 4 — Data, not behavior. Typed fields, structured slots, and contract additions are shim-shaped. Policy decisions (what happens when a cart mixes subscription and non-subscription items?), lifecycle ownership (who manages subscription state transitions?), and domain enforcement (is a product eligible to subscribe?) are native-shaped. This test separates the fifteen true shims in §A from the four misclassifications in §C.
Test 5 — Public-goods test. A true shim benefits every BC merchant and every BC app, not only us. An authorize/capture split in the payments adapter contract benefits any app doing deferred-capture workflows. A checkout step slot for recurring-charge consent benefits BNPL, save-for-later, and every future subscription app. An ask that benefits only bc-subscriptions fails this test and should be re-examined as an application-layer concern.
Test 6 — Does not implicitly anoint our domain. Asking for webhook.store/subscription/* event topics ratifies subscription as a BC-native concept by the back door. A request for a sanctioned app-emitted topic prefix — where our app names its own events — does not. This distinction is not semantic hairsplitting; it determines whether BC has made a platform commitment to subscription semantics (which it has not, and may never do as a native concern).
Test 7 — Forward-compatibility. The shim must age well against known platform roadmap. The 2H'26 checkout billing-step replanting is the primary forward-risk anchor in this catalog. Any shim that binds tightly to billing-step ordering needs to be designed so the replanting does not break it. This test maps directly to §E.
Test 8 — Reversibility. A shim that sees no adoption outside bc-subscriptions should be depreciable cleanly. Fields that are added are hard to remove — but if the field is typed and additive (not replacing existing fields), the deprecation path is a nullable schema evolution, not a breaking change. Slots and adapter contract additions are reversible by definition.
All fifteen candidates in §A pass all eight tests. The four items in §C fail Test 1 or Test 4 or both. The two items in §D fail Test 3 or Test 5.
3. §A — True shims (the 15 candidates)¶
A1 — product.recurrence_options (Catalog) [KEYSTONE]¶
BC team owner: Catalog (Products domain).
Shim definition. A structured typed field addition to the BC product schema carrying: interval (enum: day | week | month | year), interval_count (int), billing_period_count (int | null for indefinite), trial_interval (int | null), trial_interval_count (int | null), commitment_interval_count (int | null), is_subscribable (bool). The field is additive — existing products without it behave identically.
// Catalog service — product.proto (additive field addition)
message RecurrenceOptions {
RecurrenceInterval interval = 1;
int32 interval_count = 2;
optional int32 billing_period_count = 3;
optional int32 trial_interval_count = 4;
optional int32 commitment_count = 5;
bool is_subscribable = 6;
}
message Product {
// ... existing fields ...
optional RecurrenceOptions recurrence_options = 42;
}
Marketplace complexity it collapses. We currently maintain a shadow store keyed by product_id in our D1 database (subscription_plans table) holding exactly this data. Every query for "is this product subscribable?" goes to our store, not BC's. Admin-side product discovery, storefront rendering, and search faceting all require a join against our shadow store. A first-class product.recurrence_options field means the Catalog domain owns the data, queries resolve natively, and our shadow store becomes a configuration surface for subscription-program settings rather than the single authority on product recurrence terms.
ADR superseded or converted-to-interim. ADR-0029 amended — the catalog shadow-store pattern in the subscription_plans table is explicitly bolt-on-only in the portability inventory; A1 converts it from required-workaround to avoidable.
Gating verdict. Ready. This is a typed additive field. The Catalog team has shipped similar structural additions (custom fields, modifiers, metafields). The public-goods argument is strong: every subscription app in the BC marketplace carries this same shadow store today.
A2 — Price-list recurrence_interval dimension (Pricing)¶
BC team owner: Pricing (Price Lists domain).
Shim definition. An additive dimension on price-list rows: recurrence_interval (matching A1's RecurrenceInterval enum) + recurrence_interval_count (int). When set, the price applies specifically when the product is purchased at that recurrence cadence. Allows a merchant to express "monthly subscribers pay $18/mo; annual subscribers pay $180/yr; one-time buyers pay $22."
Marketplace complexity it collapses. We compute discounted subscription prices in our own pricing layer, applied at checkout via our storefront component. This duplicates pricing logic that should live in BC's price-list system, and it means BC's native promotions engine operates on the pre-discount price, not the subscription price — creating a compounding-discount problem that merchants do not expect.
ADR superseded or converted-to-interim. None directly; this is additive.
Gating verdict. Ready. The price-list model already supports variant-level and customer-group-level price rows; adding a recurrence dimension is structurally parallel to adding a customer-group filter on an existing row.
A3 — line_item.recurrence block (Cart) [conditional on A1]¶
BC team owner: Cart.
Shim definition. An additive structured block on cart line items, populated when the product being added carries recurrence_options (A1). Contains: interval, interval_count, billing_period_count — the terms under which this line item will be billed recurrently. The block is read-only from the app perspective; Cart populates it from the Catalog field.
Marketplace complexity it collapses. We currently inject recurrence terms via line-item metafields, which every theme must be taught to read. Themes that have not been updated to our metafield schema render subscription line items identically to one-time purchases. A first-class line_item.recurrence block renders correctly across every theme that uses the native cart summary template, including Stencil Blueprint and Cornerstone variants we do not control.
ADR superseded or converted-to-interim. None directly; the metafield workaround is an implementation detail not yet codified in an ADR.
Gating verdict. Conditional on A1. The Cart team needs product.recurrence_options to exist before it can source the block data. Once A1 is ratified, A3 is a Cart-side additive schema change with no behavioral policy implications.
A4 — cart.subscription_consent_session_id (Cart) [conditional on A5]¶
BC team owner: Cart.
Shim definition. An opaque string field on the cart record, set by our checkout step component (A5) when recurring-charge consent is captured. The value is an opaque session reference into our consent ledger. It threads to the resulting order record, giving us a first-class audit trail joining order → consent session without requiring a post-order join against our external ledger.
Marketplace complexity it collapses. We currently thread consent state via order metafields, set in the store/order/created webhook handler after the fact. This creates a window between order creation and consent-threading where a charge could theoretically fire without a confirmed consent reference. A cart-level field, set before checkout completes, eliminates the window.
ADR superseded or converted-to-interim. None directly.
Gating verdict. Conditional on A5. The field is meaningless until the checkout step slot (A5) exists to populate it.
A5 — MIT consent checkout step slot (Checkout) [KEYSTONE — split]¶
BC team owner: Checkout.
Shim definition. A native checkout step slot for recurring-charge consent capture, structured as BC's established "additional step" extension model (parallel to the existing address / shipping / payment steps). The slot is invoked when the cart contains at least one item with a line_item.recurrence block (A3). The step contract: our app receives the cart context, renders the consent disclosure, and signals completion with a consent_session_id that Checkout persists in cart.subscription_consent_session_id (A4).
The split. The checkout step slot (this shim) is a platform extension point. The consent ledger — the record of what was disclosed, accepted, and by whom — is application behavior, owned by bc-subscriptions, not appropriate to request as a platform primitive. This distinction is load-bearing: BC adding a slot is a reversible contract addition; BC owning a consent ledger is a legal and compliance commitment that belongs to the app runtime.
Marketplace complexity it collapses. Our current approach injects JavaScript into the checkout page via Scripts API to intercept the "Place Order" button. This is unreliable across Catalyst rebuilds, inaccessible to screen readers using native focus management, and architecturally brittle against the 2H'26 billing-step replanting (§E). A native checkout step slot is resilient to replanting by design — BC is responsible for rendering the step in the correct order relative to billing.
Public-goods argument. BNPL providers, save-for-later subscriptions, and any future app requiring customer consent before a recurring commitment benefits from this slot. The ask is not subscription-specific.
ADR superseded or converted-to-interim. None directly.
Gating verdict. Ready as a platform extension proposal. The forward-risk anchor (§E) makes this the highest-urgency conversation with the Checkout team.
A6 — order.source enum + parent_subscription_id (Orders)¶
BC team owner: Orders.
Shim definition. Two additive fields on the BC order record:
- order.source extended with a subscription_renewal enum value (alongside existing checkout, api, etc.)
- order.subscription_id — an opaque external reference to the subscription record in our store
Both fields are set by our webhook handler when we create a renewal order. They are typed additive fields; BC does not interpret or enforce semantics on them — they are first-class metadata, not behavior.
Marketplace complexity it collapses. We currently use order metafields to mark renewal orders, requiring us to maintain a mapping table and an extra API call per order in our renewal flow. Native fields mean BC's native admin order list can filter on source = subscription_renewal without custom metafield queries. The customer's "Your Orders" page in the My Account surface can natively distinguish subscription orders from one-time purchases.
ADR superseded or converted-to-interim. None directly.
Gating verdict. Ready. The Orders domain has a documented extension pattern for source fields. subscription_id as an opaque external reference — not a BC-defined entity — passes Test 4 and Test 6 cleanly.
A7 — RMA subscription_period_ref (Returns)¶
BC team owner: Returns / Fulfillment.
Shim definition. An additive opaque reference field on the BC RMA (Return Merchandise Authorization) record: subscription_period_ref — an opaque identifier we set when creating an RMA for a subscription order. The value is a reference into our delivery-instance ledger, identifying which subscription period the return applies to.
Marketplace complexity it collapses. Without this field, a refund on a subscription order requires us to infer which subscription period it touches by correlating the RMA order_id against our delivery-instance records — a query that is brittle when an order spans multiple subscription periods or when the RMA arrives after a billing boundary. A first-class opaque reference eliminates the inference step and gives our reconciliation logic a deterministic join key.
ADR superseded or converted-to-interim. None directly.
Gating verdict. Ready. Opaque reference fields on platform records are the lowest-risk shim class. Returns team coordination is the only gate.
A8 — Authorize/capture split in adapter contract (Payments) [KEYSTONE]¶
BC team owner: Payments (BigPay adapter contract team).
Shim definition. Extend the BC payment adapter interface from the current atomic charge(ctx) → ChargeResult to explicit authorize(ctx) → AuthResult + capture(authRef, amount) → CaptureResult operations. Every modern PSP supports this split; BC adapters today tunnel both into a single call, making deferred-capture workflows impossible at the adapter level regardless of what the merchant configures.
// Current adapter contract (atomic)
interface PaymentAdapter {
charge(ctx: ChargeContext): Promise<ChargeResult>;
}
// Shim target (split)
interface PaymentAdapter {
charge(ctx: ChargeContext): Promise<ChargeResult>; // retained; atomic shortcut
authorize(ctx: ChargeContext): Promise<AuthorizationResult>;
capture(ref: AuthorizationRef, amount: Money): Promise<CaptureResult>;
void(ref: AuthorizationRef): Promise<VoidResult>;
}
Marketplace complexity it collapses. ADR-0038 documents the EU capture-timing gap: EU merchants selling physical-goods subscriptions are non-compliant because our charge is atomic (authorize + capture in a single request), while EU card-scheme rules require capture no earlier than shipment. ADR-0038 Phase 1 shipped a stores.capture_timing advisory column; Phase 2 (the actual deferred-capture semantics) is blocked on this adapter split. Without A8, we carry a known compliance gap for EU physical-goods merchants indefinitely.
ADR superseded or converted-to-interim. ADR-0038 Phase 2 (deferred-build clause) converts from "blocked on us" to "conditional on A8 ratification" once this shim is pursued. The Phase 2 implementation work in ADR-0038 — adapter.authorize() + adapter.capture(), store/shipment/created handler triggering capture, auth-expiry sweep cron — is our application-layer work that becomes buildable the moment BC ships the adapter split.
Gating verdict. Ready as a platform proposal. The Payments/BigPay adapter team owns this contract. The public-goods argument is strong: any BC app that needs deferred-capture (deferred billing, EU physical-goods compliance, pre-authorization hold workflows) benefits. This is not a subscription-specific ask.
A9 — Stored-instrument enumeration endpoint (Payments/Vault)¶
BC team owner: Payments (Vault API).
Shim definition. A first-class API endpoint scoped to (store_hash, customer_id) that returns the customer's vaulted instruments across all active subscriptions — without requiring an Instrument Access Token (IAT) popup flow per instrument. The endpoint returns typed instrument records: { id, type, last4, expiry, is_default, gateway_name }.
Marketplace complexity it collapses. ADR-0037 documents the current IAT popup design for the portal payment-method picker: our backend mints an IAT, the browser posts raw card data directly to the BC Payments server, and our portal renders an iframe around the IAT flow. This works but forces shoppers through a full payment-capture UI just to see their existing instruments or switch between them. A read-only enumeration endpoint — scoped by customer, not by pending order — removes the IAT round-trip for the "which of my saved cards do I want to use for this subscription?" use case. Raw card data never enters the picture; the endpoint returns tokens only.
ADR superseded or converted-to-interim. ADR-0037 — specifically the IAT popup design for the StoredInstrumentsPicker.svelte component and the open item noted in the ADR's "Does NOT cover" section — converts to interim. The picker component simplifies to a fetch-and-render pattern once the enumeration endpoint exists. The IAT flow is retained for the "add new card" case, which requires raw card data entry.
Gating verdict. Ready. The Vault API already has per-customer instrument records. A read-only enumeration endpoint scoped to customer_id is an additive surface that does not change vault write semantics. The store_payments_access_token_create scope for PAT-minting is separate from the read path this endpoint would introduce.
A10 — MIT/CIT flag in adapter charge contract (Payments)¶
BC team owner: Payments (BigPay adapter contract team, same team as A8).
Shim definition. Add a transaction_type enum to the ChargeContext (and AuthorizationContext after A8): CIT (Cardholder-Initiated Transaction) | MIT (Merchant-Initiated Transaction). PSPs use this flag for SCA (Strong Customer Authentication) exemption signaling under PSD2 — MIT transactions on stored credentials are exempt from SCA challenge. BC adapters today treat all charges as CIT by default, meaning subscription renewal charges (which are definitionally MIT) receive unnecessary SCA challenge prompts in EU regions, increasing decline rates.
Marketplace complexity it collapses. We currently rely on the is_recurring: true flag in the PAT mint call (step 1 of the three-call sequence in ADR-0037) to hint MIT semantics, but this flag is not propagated to the PSP in all adapter implementations. A typed transaction_type in the adapter contract makes the MIT/CIT distinction first-class and adapter-implementation-specific.
ADR superseded or converted-to-interim. ADR-0035 (BC Payments MIT endpoint) — this shim converts the MIT threading from a hinted workaround to a typed contract. ADR-0037's three-call sequence retains the is_recurring: true hint at the PAT layer; A10 adds the adapter-level signal.
Gating verdict. Ready. This is a typed contract addition to the adapter interface — same team as A8, parallelizable with it.
A11 — <SubscriptionTerms> slot in storefront-kit (Storefront) [conditional on A1]¶
BC team owner: Storefront (storefront-kit / Catalyst package maintainers).
Shim definition. An additive package export in @bigcommerce/storefront-kit: a <SubscriptionTerms> React Server Component (or Svelte equivalent) that receives a product prop and renders subscription term metadata — interval, trial, commitment — sourced from product.recurrence_options (A1). The component is a slot; apps override its rendering. The default render is "Subscribe and save: billed monthly."
Marketplace complexity it collapses. We currently maintain a Stencil widget injector and a Catalyst PDP component (apps/storefront-svelte/) that monkey-patches the product page to inject subscription terms. This is the source of the STENCIL_SCRIPT_NAME 422 bug documented in memory (stencil-script-name-422.md) and the injector/widget mismatch documented in storefront-widget-injector-mismatch.md. A native storefront-kit slot removes both injection mechanisms; the terms render wherever the theme author places <SubscriptionTerms />, without Scripts API involvement.
ADR superseded or converted-to-interim. ADR-0013 (storefront-components shape) — the Scripts API + widget injection layer converts from canonical to interim once A11 + A12 are available. The ADR describes this as first-class-only in the portability inventory; a storefront-kit slot is the marketplace-app equivalent of the native storefront path.
Gating verdict. Conditional on A1. The storefront-kit team needs product.recurrence_options to exist as a typed field before the component has a reliable data source. Once A1 is ratified, A11 is an additive package component requiring no new platform primitives.
A12 — Catalyst SDK subscription-aware add-to-cart hooks (Storefront)¶
BC team owner: Storefront (Catalyst SDK maintainers).
Shim definition. An additive hook in the Catalyst SDK add-to-cart flow: an onSubscriptionTermsChange callback fired when recurrence options are toggled on a product page, and a subscriptionOptions prop on the cart mutation to carry recurrence terms forward. This is a pure SDK extension — no server-side contract change required.
Marketplace complexity it collapses. Our current Catalyst PDP component overrides the add-to-cart button's behavior to intercept the mutation and inject recurrence terms. This override breaks every time the Catalyst SDK ships a major add-to-cart refactor. SDK hooks eliminate the override entirely; our code registers a callback rather than patching the button.
ADR superseded or converted-to-interim. ADR-0013 — same as A11; both together retire the Scripts API injection layer.
Gating verdict. Ready. Catalyst SDK hooks are a client-side addition with no server-side contract dependency. This is the lowest-risk shim in the catalog and can be pursued in parallel with A1/A11.
A13 — Subscription-aware search index facet (Search) [conditional on A1]¶
BC team owner: Search (Algolia-backed storefront search).
Shim definition. An additive boolean facet in BC's storefront search index: is_subscribable, derived from product.recurrence_options.is_subscribable (A1). Enables merchants to offer a "Subscribe and Save" filtered product listing page via native storefront search without a custom facet pipeline.
Marketplace complexity it collapses. We currently maintain a separate endpoint that returns subscribable product IDs, which storefronts query independently to filter search results. This is a N+1 pattern: one search call for results, one separate call to our API to filter by subscribability. A native search facet collapses this to one search call.
ADR superseded or converted-to-interim. None directly.
Gating verdict. Conditional on A1. The search index pipeline reads from the Catalog domain; is_subscribable must exist as a typed field before the index pipeline can derive it. Once A1 is ratified, the facet addition is a Search team concern — likely a pipeline configuration change.
A14 — Channel-scoped recurrence pricing (Channels) [conditional on A2]¶
BC team owner: Channels (Multi-Storefront / Channel Manager).
Shim definition. Channel scoping of A2's price-list recurrence dimension: a channel can override the store-wide subscription pricing by specifying its own recurrence_interval + price on a price-list row. Enables a merchant to offer different subscription pricing on their EU storefront vs. their US storefront.
Marketplace complexity it collapses. Multi-storefront merchants currently configure per-channel subscription pricing through our application's per-channel pricing override table — another shadow store keyed by (channel_id, product_id, recurrence_interval). A native price-list channel scope eliminates it.
ADR superseded or converted-to-interim. None directly.
Gating verdict. Conditional on A2. The channel-scoping pattern exists in BC's price-list system today; extending it to the recurrence dimension follows the existing pattern exactly.
A15 — Sanctioned app-emitted MES event topic (Domain Eventing) [KEYSTONE]¶
BC team owner: Domain Eventing / Platform Infrastructure.
Shim definition. A mechanism for BC marketplace apps to publish events on a sanctioned, verification-signed topic prefix within BC's Message Event System (MES), subject to installation verification. The contract: when our app is installed with a specific App Extension scope (e.g., app_events_publish), we may emit events under the app/<client_id>/ topic prefix. BC's webhook relay amplifies these events to other BC services that have subscribed to app-tier events. The signing contract: events are signed with our installation credential; BC's relay verifies the signature before amplification.
The critical distinction. We emit app/<client_id>/subscription.activated, app/<client_id>/subscription.charged, app/<client_id>/subscription.cancelled. We do NOT ask BC to define store/subscription/activated as a first-class BC webhook family. The former is a mechanism (app-published, verified event amplification); the latter is a platform commitment to subscription as a BC-native concept. Test 6 is the governing principle here.
Marketplace complexity it collapses. We currently deliver subscription lifecycle events to merchants via our own webhook emission pipeline (D1 outbox → Worker cron → merchant webhook endpoints per ADR-0027). This works but the events are not visible to other BC services — they cannot trigger BC-native automations (email templates, automation rules, app-to-app workflows). A sanctioned app-emitted topic makes our events first-class participants in BC's event routing without requiring BC to own subscription semantics.
Downstream unlocks. Once A15 is in place, two further extensions become shim-shaped: (a) native BC email template triggers keyed on our verified event topic (Email team); (b) BC Automation Rules entries keyed on our events (Automation team). Neither requires BC to own subscription concepts — they are general-purpose event-to-action wiring.
ADR superseded or converted-to-interim. ADR-0027 (outbox marker + catch-up cron) converts from canonical to supplementary — our outbox remains the authoritative internal record; A15 adds the platform-amplification layer on top. ADR-0010 (webhook / event egress) is amended: merchant-facing webhooks continue via our own emission; BC-internal event amplification routes through the sanctioned topic.
Gating verdict. Ready as a platform architecture proposal. The MES already supports per-service topic namespacing; extending that to verified-installation apps is a governance and security design decision, not a new technical primitive. The App Extensions scope mechanism provides the installation verification anchor.
4. §B — Keystones¶
Three shims unlock nine of the remaining twelve. Sequencing the BC-side conversations around these three first maximizes the cascade value.
A1 — product.recurrence_options. The Catalog field is the foundation of the product-side cluster. Once it exists: A3 (cart line-item recurrence block) requires only Cart consuming it; A11 (<SubscriptionTerms> storefront-kit slot) requires only a React component sourced from it; A13 (search facet) requires only the search pipeline indexing it. A14 (channel-scoped pricing) depends on A2, which is independent — but A2's usefulness multiplies with A1 because a recurrence price without a recurrence product field is an orphan configuration. In total, A1 unblocks four shims directly (A3, A11, A13, and the practical utility of A2/A14) and collapses the largest single shadow store we carry.
A8 — Authorize/capture split in adapter contract. The Payments adapter split unlocks the EU compliance gap documented in ADR-0038. Once the adapter contract exposes authorize() + capture() separately, our Phase 2 deferred-capture implementation becomes buildable: the store/shipment/created webhook handler triggers capture; the auth-expiry sweep cron manages stale authorizations. A10 (MIT/CIT flag) is the same team, same adapter contract discussion — pursue in parallel. A9 (stored-instrument enumeration) is a separate Vault API conversation but benefits from the same BC payments team relationship.
A15 — Sanctioned app-emitted MES event topic. The Domain Eventing shim is the multiplier for the entire observability and automation layer. Without it, our events are visible to merchants but invisible to BC's internal automation fabric. With it, BC email templates, Automation Rules, and any future BC-native event subscribers can react to subscription lifecycle events without BC owning subscription semantics. The downstream email-template and automation-rule extensions (not listed as separate shims because they are dependent on A15 governance being resolved first) represent a step-change in merchant experience — subscription events triggering native BC confirmation emails, abandonment flows, and renewal reminders — without bc-subscriptions owning the delivery infrastructure.
5. §C — Misclassified-as-shim: actually native¶
These four items fail Test 1 (single bounded context) or Test 4 (data, not behavior), or both. Pursuing them as shims would produce platform entanglement without delivering the value claimed.
C1 — B2B Edition federated identity [HEADLINE]¶
Why it was framed as a shim. B2B-on-subscriptions requires that a B2B company identity (managed by B2B Edition on its own GCP Postgres + auth domain) be visible in the Checkout context so that subscription consent, payment method selection, and recurrence terms can be scoped to a company account rather than an individual customer account.
Why it is not a shim. Federating B2B Edition's separate identity store into the Checkout context is not a field extension — it is a resolver that crosses three bounded contexts: B2B Edition (owns company identity), Customers (owns the BC customer account linked to the company), and Checkout (owns the session that needs to express company scope). A shim that bridges these three is not a field addition; it is platform identity federation. The Customers-domain team, the B2B Edition team, and the Checkout team all have stakes in the contract. That tripartite ownership is the distinguishing mark of native work, not shim work.
The honest delivery posture. B2B-on-subscriptions — company-scoped subscriptions, NET-terms recurring invoicing, company-level payment method management — is blocked on BC-side identity federation between B2B Edition and the core platform. No shim resolves this. The correct frame is not "Phase 3, deferred" but "blocked-on-other-team regardless of shim ambition." The team in question is not a single team; it is a tripartite coordination between B2B Edition, Customers, and Checkout that has not been initiated as a platform initiative. The right next action is described in §8.
C2 — Customer-scoped recurring-consent ledger (Customers + Legal)¶
Why it was framed as a shim. A first-class BC customer record field carrying a reference to recurring-charge consent history would simplify our MIT compliance trail.
Why it is not a shim. Consent policy — what constitutes valid recurring-charge consent, how long records must be retained, what triggers revocation — is compliance-policy ownership, not a data field. BC owning a consent ledger means BC accepting liability for the completeness of that record. That is a legal commitment, not a schema addition. We own the consent ledger in our application. A4 (cart.subscription_consent_session_id) is the shim-shaped ask: an opaque thread that connects the cart record to our ledger without BC understanding the ledger's contents.
C3 — Inventory holds for subscription commitment (Inventory)¶
Why it was framed as a shim. Subscription commitments (6-month prepaid plans) should reserve inventory for the commitment period so the product does not go out of stock mid-commitment.
Why it is not a shim. A counter without oversell policy creates drift worse than no counter at all. The behavioral question — what happens when inventory falls below the hold quantity? cancel the commitment? notify the merchant? automatically substitute? — is Inventory-domain policy. BC's Inventory domain does not expose "commitment hold" as a concept today, and adding one without resolving the oversell policy makes the field misleading. This is native-scope work or application-scope work (we manage expectation in our admin); it is not a typed additive field.
C4 — Mixed-cart recurrence policy (Cart)¶
Why it was framed as a shim. A cart-level policy field expressing how BC should handle a cart containing both subscription and one-time items (split into separate orders? force single-recurrence? surface an error?) would let us configure the behavior instead of enforcing it ourselves.
Why it is not a shim. The behavioral decision — split? error? allow? — is owned by Cart's bounded context. Cart's policy on item compatibility is not a configuration surface we can safely own as an external app; it interacts with order creation, fulfillment, tax calculation, and promotions in ways that require Cart-domain ownership. This is a platform feature request (and a valid one), not a shim.
6. §D — Misclassified-as-shim: actually pure-app¶
These two items fail the "stop trying to externalize this" test. Pursuing them as BC platform additions would create tighter coupling than the workaround they claim to replace.
D1 — Retry-aware idempotency at platform level¶
Why it was considered. A platform-level idempotency guarantee on the payment charge endpoint would remove our application-level idempotency logic (ADR-0011).
Why it belongs in the app. Retry discipline is implementation-specific. Our idempotency key contract (ADR-0011) is tuned to our retry window, our failure modes, and our state machine. Platform-enforced idempotency operates on a different time horizon (per-request, not per-subscription-cycle) and cannot know our domain semantics (a charge that fails with "insufficient funds" must not be retried idempotently; a charge that fails with "network timeout" must be). Platform idempotency and application idempotency serve different concerns. We own ADR-0011 in our worker. Two idempotency layers would produce worse drift than one — the platform layer would mask failures our application layer needs to observe.
D2 — Platform-computed customer LTV / subscription_count fields¶
Why it was considered. BC-native customer fields carrying subscription count and lifetime value would reduce the join overhead in our reporting queries.
Why it belongs in the app. These values derive from our subscription state. If BC computes them, BC must ingest our subscription events — which means BC becomes a consumer of our application state. That inverts the dependency: rather than us querying BC, BC queries us. The coupling is bidirectional and fragile. We surface LTV and subscription_count through our portal and admin surfaces, derived from our own state. If a merchant wants these values in BC's native customer record, the right mechanism is A15 (our events amplified into BC's fabric) enabling a BC automation that writes derived fields — not a BC-native compute that polls our API.
7. §E — Roadmap-collaboration (not a shim)¶
E1 — 2H'26 checkout billing-step replanting input¶
This is not a request for a new platform primitive. It is a request to land subscription-stored-instrument-selection requirements early in BC's existing billing-step replanting work (already on the Checkout team's 2H'26 roadmap), modeled on the Shop Pay "billing under each Payment Method" pattern.
The specific requirement: when BC replants the billing step to support nested payment-method detail collection (the Shop Pay equivalent), the architecture should accommodate a stored-instrument picker scoped to subscription recurrence context — not just one-time purchases. Without this input, BC may design the replanted billing step around the one-time purchase use case and require an additional replanting cycle to support subscription-specific instrument selection.
Forward-risk anchor. Everything we currently anchor to billing-step ordering — our Scripts API consent injection (A5 replaces this), our portal instrument picker (A9 simplifies this), our checkout metafield threading (A4 replaces this) — is presumed brittle against the replanting. This is the same forward-risk pattern as the promo-rules feasibility.md analysis's caution about checkout-binding: the replanting is BC's roadmap item, not ours, and the correct response is to input requirements early, not to block our shim catalog on replanting timing.
8. The B2B Edition posture, restated honestly¶
"Phase 3, deferred" was the wrong frame for B2B-on-subscriptions. The correct framing, applying the same tripartite analysis used in C1:
Ready (shim alone): B2C subscription delivery on A1 + A8 + A15. The three keystones cover the core recurring-billing use case for single-customer, standard-checkout merchants. No B2B involvement required.
Conditional (shim cluster): Storefront and cart UX consistency for all subscription merchants, conditional on A1 ratification. Channel-scoped pricing for multi-storefront merchants, conditional on A1 + A2. EU physical-goods compliance, conditional on A8. Native event amplification and email automation, conditional on A15 governance. These are tractable — they have clear single-team owners and clear predecessor conditions.
Blocked-on-other-team: B2B-on-subscriptions blocked on identity federation between B2B Edition, Customers, and Checkout (C1) — not a timeline question, a tripartite ownership question. Recurring NET-terms invoicing blocked on B2B Edition + Payments joint work — a separate initiative from subscriptions, independent of this shim catalog. Native MRR/cohort analytics blocked on BC Insights — a platform-reporting initiative, not a subscription-domain initiative.
The "blocked-on-other-team" category replaces the deferral frame with an honest dependency statement. These items are not deferred; they are waiting for BC-side organizational alignment that the bc-subscriptions program cannot initiate on its own and should not be credited as pending work.
9. Implementation readiness verdict¶
Ready. The three keystones — A1, A8, A15 — are ready to approach as BC platform proposals. A1 ratification unlocks our single largest shadow store and unblocks A3/A11/A13 immediately. A8 ratification converts ADR-0038's Phase 2 deferred-build clause from "blocked on adapter contract" to "buildable now" and enables the EU compliance path for physical-goods merchants. A15 ratification converts our subscription lifecycle events from merchant-facing-only to BC-fabric-visible, enabling downstream automation without requiring BC to own subscription semantics. A9 (vault instrument enumeration), A10 (MIT/CIT flag), and A12 (Catalyst SDK hooks) are independent ready-state shims that can be pursued in parallel with the keystones — they do not depend on each other or on the keystones.
Conditional. The product-side cluster — A3, A11, A13 — is conditional on A1. These are the shims with the highest per-merchant visible impact (subscription terms rendering natively across all themes, subscribable product search facet); they become immediate work items the moment the Catalog team ratifies A1. The channel-scoped pricing cluster — A2, A14 — is independent of A1 but its utility is proportional to A1's adoption. A4 (cart.subscription_consent_session_id) is conditional on A5's checkout step slot existing; A5 itself is ready to propose, making A4 a fast-follow. A7 (RMA subscription_period_ref) and A6 (order.source + subscription_id) are ready-state but lower urgency than the keystones.
Blocked-on-other-team. C1 (B2B federation) is the headline block — tripartite ownership, no timeline. C2 (consent ledger), C3 (inventory holds), and C4 (mixed-cart policy) are all blocked on BC domain-ownership decisions that have not been initiated. The right action for C1 specifically is described in §10.
10. Open questions before pursuing shims¶
These must close before BC-side conversations begin. None block internal design work on the app-side portions of the shim pairs.
1. Sequence of BC-side conversations. Two candidates for "first conversation": Catalog for A1 (the broadest cascade, clearest public-goods argument, likely lowest political friction since typed product fields are well-established); or Payments for A8 (the compliance urgency — EU physical-goods — is the strongest forcing function, and the adapter contract team is the same team as A10). The recommendation is A1 first because its cascade unlocks the most shims and its public-goods argument ("every subscription app in the BC marketplace carries this same shadow store") is the most accessible framing for a first BC product conversation. A8 follows immediately; the compliance forcing function accelerates it. A15 requires a Platform Infrastructure conversation about MES governance — that is a distinct relationship and can run in parallel.
2. Public-goods framing discipline. The ask to BC should never be "we want this shim for bc-subscriptions." The ask should be "the BC marketplace ecosystem needs this mechanism; here is our use case as the reference implementation." For A1: "every subscription app carries a product-recurrence shadow store; a typed catalog field collapses it for the ecosystem." For A8: "EU physical-goods compliance requires deferred capture; the adapter contract blocks every app that needs this." For A5: "recurring-charge consent capture needs a native checkout step slot; BNPL and every subscription app share this requirement." This framing is not rhetorical — it is the accurate framing. The shims pass the public-goods test precisely because the argument is true.
3. Governance of A15 sanctioned event topic. The sanctioned app-emitted topic requires BC to maintain a signing and verification contract: which apps are permitted to emit on the app/<client_id>/ prefix, how signatures are verified, what happens when an app is uninstalled (event stream revocation). This is the most novel governance ask in the catalog. Before the conversation with the Platform Infrastructure team, we need a concrete proposal for the signing contract (likely: installation-credential HMAC, verified by the relay before amplification, revoked on uninstall) and a security model for what happens if a compromised app emits malicious events under its permitted prefix.
4. B2B federation: initiate independently of subscriptions. The C1 reframe surfaces a question BC should be asked independent of subscriptions: is there a platform initiative to federate B2B Edition identity into core bounded contexts (Customers, Checkout)? Multiple marketplace apps — any app needing company-scoped workflows — face the same tripartite block. If that initiative is already planned, we need to be in the room. If it is not, the right ask is not "add B2B support to subscriptions" but "initiate a platform federation initiative that subscriptions, and other apps, can consume." Filing that as a distinct BC platform proposal — separate from the bc-subscriptions shim catalog — is the correct next action.
Cross-references¶
../strategy/delivery-shim-path.md— sibling strategy doc establishing the shim path as a third delivery lane (forward link; being authored in parallel)../strategy/delivery-fork.md— the existing marketplace-vs-native fork doc; this feasibility establishes the third lane../decisions/0029-marketplace-first-native-ready.md— current delivery posture; this doc supplements, does not supersede../decisions/0037-stored-instruments-as-canonical-charge-rail.md— converted to interim by A9 (instrument enumeration)../decisions/0038-charge-capture-timing-strategy.md— Phase 2 deferred-build clause activated by A8 (authorize/capture split)../../METHODOLOGY-AMENDMENTS.md— methodology amendments covering the three-pass research discipline and the back-door-native anti-pattern that Test 6 encodes (forward link)