7. Open decisions — what this review must answer¶
Generated from a canonical source
This page is a read-only projection of docs/solution-review/07-open-decisions.md.
Edit the canonical file, then run npm --prefix tools/project-knowledge-derive run derive.
How to use this page¶
This page is the review's output artifact. The five decisions below are the calls this room exists to make; the facilitator records a disposition for each one in the meeting, in the capture block under each decision. After the meeting, each disposition routes back into the project's coordination substrate as a tracked proposal, and this file is updated with the outcome and re-stamped.
Undecided is not the same as blocked. Every decision names a default if deferred — the path the build takes if the room does not settle it today — so that deferral is a deliberate choice with a known consequence, not a stall. Read each decision as a fork: the context a cold reader needs, the two options with their costs, the default, and the single judgment the room is being asked for.
D-1. Checkout-intent capture — accept the reconciliation pattern, or commission the server-side checkout hook?¶
Context. The platform's hosted checkout exposes no server-side hook to capture "this cart line is a subscription" at the point of purchase, and the Carts API has no line-item custom-field surface. The working pattern writes subscription intent into a cart-level metafield before checkout and reads it back from the resulting order via the store/order/created webhook, then creates the subscription. This is proven, but it is reconciliation stitched together by a webhook rather than a transaction — intent is captured before the order exists and reconciled after. The failure surface is that seam: a dropped webhook, a metafield that did not persist onto the order, or a storefront forced onto the thank-you-page fallback. This is the highest-value platform-native question in the contract. See file 06-platform-gaps-and-contract.md, Kind C.
Option A — accept the reconciliation pattern. Ship the cart-metafield-plus-webhook-fetch bridge as-is. Bounded, proven, zero checkout-pipeline risk; but not atomic with order creation, so the seam's failure modes must be monitored and reconciled operationally.
Option B — commission a server-side checkout hook. A genuine checkout-pipeline hook or line-item-level extension that captures intent atomically with order creation, removing the seam. Higher risk and effort; touches core checkout internals; viable only with a separate checkout-platform-team commitment that this engagement does not control.
Default if deferred. Option A — the contract's recommended default; the reconciliation pattern ships and the checkout-hook investment is not made.
What we need from this room. Whether the non-atomicity is acceptable for general availability, or whether the checkout-pipeline hook must be commissioned now with the checkout-platform team.
Capture: - Disposition: ☐ accept option A ☐ accept option B ☐ defer (default applies) ☐ needs work - Owner: ____________ Date: ____________ - Notes:
D-2. Subscriber portal — deepen native customer-account pages, or ship the bounded portal surface?¶
Context. The platform's customer-account area has no subscription-management surface — no view of active subscriptions and no self-service pause/skip/cancel/payment-method controls. Subscribers need self-service access without contacting the merchant. The reference implementation ships this as an external surface (a hosted portal plus a headless/embeddable variant), separate from the platform's own account pages. In a first-party native delivery, deepening the platform's own account pages directly becomes a real alternative. See file 06-platform-gaps-and-contract.md, Kind B.
Option A — deepen native customer-account pages. Host subscription management inside the platform's own account UI. Tighter UX cohesion and higher perceived trust, but a larger effort that touches core account-management screens outside the subscriptions capability's usual boundary.
Option B — ship the bounded portal surface. A portal-style surface consistent with the reference pattern. Lower integration risk, faster to ship, proven UX; visually and structurally separate from native account pages.
Default if deferred. Option B — the bounded portal surface, matching the reference pattern and carrying the lower integration risk.
What we need from this room. Which surface the subscriber portal lives on — native account pages or a bounded external surface — given the trust-versus-effort trade.
Capture: - Disposition: ☐ accept option A ☐ accept option B ☐ defer (default applies) ☐ needs work - Owner: ____________ Date: ____________ - Notes:
D-3. Runtime phase boundary — is the Cloudflare-to-GCP boundary right, and what signal triggers the migration?¶
Context. The build runs on Cloudflare today (Workers for the API and webhooks, D1 as the primary database, KV, Queues, Cron) and this is in production as Phase 1. A Phase 2 migration to Google Cloud Platform is ratified in shape — Cloud Run for the web surfaces, Cloud SQL (PostgreSQL 15) for the database — and the migration mechanics are documented. What is not settled is the trigger: the migration is described as happening at "Phase 2 subscriber scale," with no quantified signal that says when to cut over. The boundary itself is therefore a live question — is a second runtime the right destination at all, and if so, what event should start the cutover?
Option A — affirm the boundary and define a quantified trigger. Keep GCP as the Phase 2 destination and commit to a concrete cutover signal (for example, D1 or Workers scale limits being approached, or a single-cloud-compliance requirement from a regulated enterprise merchant). Gives the migration a decidable start condition instead of a vibe.
Option B — treat GCP as contingent and stay Cloudflare-native. Do not commit to a second runtime; invest in staying on Cloudflare and re-open the migration only if a specific constraint forces it. Avoids the cold-start latency and operational-surface tax of Cloud Run and the cost of maintaining migration-readiness, at the cost of the single-cloud-compliance story GCP offers regulated merchants.
Default if deferred. Stay on Cloudflare — Phase 1 is in production and the GCP migration remains planned but untriggered; nothing forces a move today.
What we need from this room. Whether the Cloudflare-to-GCP phase boundary is the right architecture, and if it is, the specific signal that should trigger the cutover.
Capture: - Disposition: ☐ accept option A ☐ accept option B ☐ defer (default applies) ☐ needs work - Owner: ____________ Date: ____________ - Notes:
D-4. Operator-console auth model — same account with a distinct session, or a separately deployed operator app?¶
Context. The operator console is the maker's view across all merchants — a cross-tenant surface whose authority is strictly stronger than any single merchant session. Its auth model must encode that asymmetry, not just be "another login." The proposed model (tracked as issue #1520) is: a separate operator-console app deployed on the same Cloudflare account as the rest of the platform, behind a Cloudflare Access zero-trust policy with GitHub OAuth as the identity provider, minting a distinct operator-session token (its own signing key, claims, and cookie) that merchant sessions cannot satisfy, with every operator API call — read or write — writing an audit-log row. Two-factor hardening becomes a policy flip when the first non-founder operator joins.
Option A — same account, distinct session, layered auth. One secret pool, one deploy graph, direct read-only binding to the existing database; isolation is enforced at the auth and route layer (distinct session type, Access policy, per-route middleware). Lower operational overhead; the isolation boundary matches the actual threat model (operator-versus-merchant authority), not account compromise.
Option B — separately deployed operator app on its own account. Full account-level isolation. Protects against an entire-account compromise, but duplicates the secret pool (rotation-drift risk), adds a second CI surface, and forces cross-account access to the primary database with added latency and auth complexity — overkill for the single-operator phase.
Default if deferred. Option A — the #1520 proposed model (same account, distinct session, Cloudflare Access + GitHub OAuth).
What we need from this room. Ratification of the topology and the Phase ½/3 auth layering — same-account with layered auth versus full account isolation.
Capture: - Disposition: ☐ accept option A ☐ accept option B ☐ defer (default applies) ☐ needs work - Owner: ____________ Date: ____________ - Notes:
D-5. B2B server-to-server auth — buyer-portal passthrough, or the partner-track credential?¶
Context. B2B Edition (company accounts, buyer hierarchies, approval workflows) is the Phase 3 enterprise scope, and it depends on this auth question. B2B Edition's GraphQL API requires a B2B JWT obtained from a buyer's storefront session token — it is customer-scoped and session-scoped, and its authorization mutation rejects tokens minted by our own OAuth app (confirmed Token mismatch on sandbox). Our Cloudflare Worker holds no buyer session, so there is no static server-to-server token path today. The proposed approach (tracked as issue #1840) is a buyer-portal passthrough: the portal, after the buyer signs in, uses the platform's Current Customer API to obtain a JWT scoped to B2B Edition's own published client ID and passes that to the authorization mutation. This is not yet de-risked — whether the Current Customer API permits one app's storefront script to request a JWT scoped to a different app's client ID is not confirmed by published docs and needs an empirical sandbox probe or BC platform-engineering confirmation.
Option A — buyer-portal passthrough (Pattern A). Achieves buyer-facing B2B coverage without any partner-program dependency, and an admin-side company-name view is available separately through Management API customer-group correlation with no B2B call at all. Cost: the core cross-client-id JWT mechanism is an unresolved feasibility question that gates the buyer-facing slices.
Option B — partner-track OAuth credential (Pattern C). A clean server-side credential that replaces the buyer-token dependency entirely. Requires BigCommerce's B2B team to add this app to the B2B Edition trust chain; timeline is unknown and depends on partner-program status.
Default if deferred. Option A — the buyer-portal passthrough, with its open feasibility question tracked as a follow-up spike; the partner track remains an upgrade path if this app reaches partner tier.
What we need from this room. Whether to commit to the buyer-portal passthrough as the B2B auth pattern given its unresolved feasibility question, accepting that it feeds — and gates — the Phase 3 B2B Edition dependency.
Capture: - Disposition: ☐ accept option A ☐ accept option B ☐ defer (default applies) ☐ needs work - Owner: ____________ Date: ____________ - Notes:
Assumption register¶
These are the platform behaviors the integration contract assumes true as of its as-of date (contract section 5.6). Each is a candidate for a vendor solution-design validation step before an estimate is finalized. The contract does not tag these with an evidence tier, so all thirteen are marked untyped; if solution-design validation finds any to be false or changed, it is raised as a scoped change request against the contract, not silently absorbed. The validation-owner column is left blank for capture in the room.
| # | Assumption | Evidence tier | What invalidation would change | Validation owner |
|---|---|---|---|---|
| 1 | The Stored Instruments vault keeps exposing the three-call access-token → instrument-discovery → charge sequence for BC Payments and Stripe, without material contract change before build start | untyped | Re-architects the core renewal charge-execution path | |
| 2 | Card vaulting at checkout stays contingent on signed-in customer context; guest checkout vaults no instrument, and this does not change before build start | untyped | Reconsiders the subscribe-path sign-in gate; could open or reshape guest-subscribe | |
| 3 | The stored-credential indicator taxonomy (CSTO/CUSE/CREC/CINS/CGEN/MUSE/MREC) and its flag semantics stay stable |
untyped | Re-derives the per-gateway CIT/MIT flag mapping; decline-rate and SCA-exemption exposure | |
| 4 | The gateway vault/MIT support posture reflects the platform's currently published compatibility position and does not change materially before build start | untyped | Re-sizes gateway MVP posture and merchant compatibility messaging | |
| 5 | The Worldpay/Paymetric network-transaction-id threading gap stays open at build start and needs the persist-and-reattach mitigation; no resolution timeline assumed | untyped | If it closes, the adapter mitigation for those gateways can retire; if it widens, mitigation scope grows | |
| 6 | Refund reconciliation via a dedicated order-refund webhook is not yet a live registered subscription and must be stood up as part of this build | untyped | If it already exists, that build item drops; if it stays absent, refund reconciliation stays in scope | |
| 7 | App Extensions support only PANEL/LINK on PRODUCTS, PRODUCT_DESCRIPTION, ORDERS, CUSTOMERS; no CARTS or CATEGORIES context exists |
untyped | Re-plans merchant-admin UX surfaces; a CARTS context would enable a cart-side admin panel |
|
| 8 | Cart-level metafields stay writable from a storefront widget and readable server-side within the store/order/created webhook window |
untyped | Removes the carrier for the D-1 checkout-intent bridge; forces a different intent-capture mechanism | |
| 9 | B2B Edition APIs (company accounts, buyer hierarchies, approval workflows) are available and stable for the Phase 3 scope | untyped | Re-plans or descopes Epic 24 B2B work and decision D-5 | |
| 10 | The platform's payments dashboard keeps surfacing settlement for vault-rail charges without additional merchant-side reconciliation work | untyped | Adds merchant-side settlement reconciliation to scope | |
| 11 | The platform offers no native deferred-capture primitive; multi-phase capture requires the build to consume shipment and order-status webhooks and manage authorization-window expiry itself | untyped | A native deferred-capture primitive would simplify the authorize/capture split | |
| 12 | Klarna/Afterpay/Clearpay, Amazon Pay, and session-bound wallet gateways stay structurally excluded from recurring/off-session charging, regardless of native-build status | untyped | Only a regulatory/design change would open BNPL or wallet recurring; not assumed and not designed for | |
| 13 | Visibility into merchant-initiated authorization revocation is not confirmed for every gateway; treat it as a best-effort signal where present, not a guaranteed control | untyped | If confirmed universal, revocation handling becomes a designed-around control rather than best-effort |
After the room¶
Each disposition captured above becomes a tracked proposal on the project's coordination substrate, carrying the option chosen and its owner. Decisions marked needs work land as homework issues with the named owner and the specific question left open. Deferred decisions are logged with their default recorded as the operating choice, so the build proceeds on a known footing rather than an ambiguous one. Once the proposals are filed, this file is updated with the outcomes — disposition, owner, and date per decision — and re-stamped with the commit that carries the update, so the page reflects what the review actually decided rather than what it was asked to decide.