Shopify Settings Program — Stacked PR Status
Visual snapshot of the four-plan settings program shipping to Shopify's
storefront admin (components/shopify/admin_server + components/storefront_admin).
Each plan ships as one PR, stacked on the previous. Updated by hand; not auto-generated.
Plans
| # | Plan | Doc |
|---|---|---|
| 1 | Settings Concurrency Control | settings-concurrency-control.md |
| 2 | Settings Versioning | settings-versioning.md |
| 3 | Storefront Admin SSO | storefront-admin-sso.md |
| 4 | Theme-Targeted Deploys | theme-targeted-deploys.md |
Merge order (the chain to read first)
Linear merge sequence of every PR — both the ones in flight today and the deferred PRs that haven't been raised yet. Read top-to-bottom; each box merges into the one above it. The per-plan detail diagram further down adds scope and rebase-strip notes but is harder to read at a glance.
┌─────────────────────────────────┐
│ main │
└────────────────┬────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ #3732 · MERGED (3898b363a) │
│ Plan 1 — Concurrency Phase 1 │
│ record_version + conditional writes + 409 handler │
│ + generic record_exceptions module │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ #3694 · APPROVED + MERGEABLE (f24049ef6) │
│ Plan 3 — SSO (PR1 + PR4 of 7) │
│ auth substrate + CLI token backend │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ #3702 · APPROVED + MERGEABLE (4a9e8eeda) │
│ Plan 2 — Versioning │
│ versions API + UI + capture in funnel + tier-1 guard │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ pending raise · backend ~12/22 commits │
│ Plan 4 — Theme-Targeted Deploys │
│ per-theme staged records + deploy + rollback + theme UI │
│ blocked on: Liquid spike + backend complete │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ DEFERRED · no PR yet │
│ Plan 3 — PR2 — Controller memberships │
│ prerequisite for session-auth (membership lookup, role │
│ enforcement) │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ DEFERRED · no PR yet │
│ Plan 3 — PR3 — Session JWT issuance │
│ ◄── unblocks end-to-end CLI token minting (nothing today │
│ has `tokens:manage` until sessions exist) │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ DEFERRED · no PR yet │
│ Plan 3 — PR5 — storefront_admin worker + CORS │
│ wires the React frontend through session-auth (instead of │
│ API keys); needs PR3 │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ DEFERRED · no PR yet │
│ Plan 3 — PR6 — Tokens management UI │
│ ◄── the user-facing SSO UI; needs PR3 + PR5 │
└──────────────────────────────┬──────────────────────────────┘
▲
┌──────────────────────────────┴──────────────────────────────┐
│ DEFERRED · no PR yet │
│ Plan 3 — PR7 — Legacy-key ratchet (allow → warn → deny) │
│ phase-out of raw API keys; needs production metrics │
│ showing legacy-key traffic ≈ 0 first │
└─────────────────────────────────────────────────────────────┘
Architectural follow-ups inside Plans 1, 2, and 4 (stream-driven mirror, sharding, IndexSettings concurrency adoption, embedded-app versions UI, Asset-API embed detection, etc.) are not numbered PRs and aren't in this chain. They're listed in the per-plan diagram below.
Per-plan detail
The per-plan diagram below carries more context per plan (scope, what each plan provides to others, rebase-strip checklists). Use the merge-order diagram above when you just want to know "what merges next." Use this one when you want to know what's actually inside each PR.
Each plan box shows its current PR; deferred work in each plan is shown as its own box below the parent.
┌─────────────────────────────────┐
│ main │
└────────────────┬────────────────┘
│ merge
▼
┌───────────────────────────────────────────────────────────────┐
│ Plan 1 · Concurrency Phase 1 │
│ branch: feat/record-lock (reraised from feat/opt-lock) │
│ PR: #3732 (MERGED · 3898b363a) │
│ base: main │
│ scope: record_version + conditional writes + 409 handler │
│ + all 7 writer conversions + metafield reconcile │
│ + W4 push-script `--base-version` / `--force` │
│ + generic record_exceptions module │
│ provides: extra_transact_items hook · RESERVED kwargs │
│ (event_type, restored_from, source_scope, │
│ source_version) · RecordConflictError · 409 handler │
│ · RecordPayloadTooLargeError (413) │
│ rebase-strip: none │
└───────────────────────────┬───────────────────────────────────┘
│ deferred (no PR yet)
▼
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Stream-driven metafield/KV mirroring │
│ Needs a DDB stream on ShopifyEntitiesTable (none today) + │
│ stream consumer. Eliminates the residual "both last writers │
│ crash between DDB and metafield write" divergence window the │
│ 2-step in-route reconciliation bounds today. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Record sharding for the 400KB DDB limit │
│ Alarmed at 300KB. Binding ceiling is the 128KB metafield │
│ mirror (tier 1); realistic response is splitting the mirror, │
│ not the DDB record. Tier 2 has ~3.3x headroom. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · IndexSettings table concurrency adoption │
│ Same disease in a different table. Generic │
│ BaseRepository.put_item_versioned / _version_condition │
│ primitives are written for cross-repo adoption; no IndexSet- │
│ tings call sites changed. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Optional `backfill_settings_record_version.py` │
│ Cosmetic uniformity for dashboards; not a rollout dependency. │
└───────────────────────────────────────────────────────────────┘
│ merge to main
▼
┌───────────────────────────────────────────────────────────────┐
│ Plan 3 · Storefront Admin SSO (PR1 + PR4 of 7-PR sequence) │
│ branch: feat/storefront-auth │
│ PR: #3694 (APPROVED + MERGEABLE · awaiting human │
│ merge · f24049ef6) │
│ base: main (auto-updated after #3732 merge) │
│ scope: canonical actor grammar + scope registry │
│ + StorefrontAuthContext + authenticate_storefront_ │
│ request + require_scopes() + CLI token model/repo/ │
│ service/CRUD routes + ecom_routes.get_account swap │
│ provides: models/auth.py grammar (imported by 2 + 4) │
│ · scope registry · require_scopes() decorator │
│ rebase-strip: none (self-contained) │
│ ishaaq bot: 5 reviews · "no blocking issues" · won't auto- │
│ approve security-sensitive auth code (policy) │
└───────────────────────────┬───────────────────────────────────┘
│ deferred sub-PRs (no PR yet)
▼
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · PR2 — Controller memberships │
│ Required for session-auth lookups (who belongs to which │
│ account, role enforcement). │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · PR3 — Session JWT issuance │
│ ◄── BLOCKS END-TO-END CLI TOKEN MINTING. Nothing has │
│ `tokens:manage` until sessions exist; PR1+PR4 ship the │
│ routes but no client can authenticate against them. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · PR5 — storefront_admin worker + CORS │
│ Wires the React frontend through the session-auth flow │
│ instead of API keys; needs PR3. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · PR6 — Tokens management UI │
│ ◄── THE USER-FACING SSO UI. Page in storefront_admin to mint, │
│ list, revoke CLI tokens. Needs PR3 + PR5. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · PR7 — Legacy-key ratchet (allow → warn → deny) │
│ Phase-out of raw API keys on storefront routes. We stay on │
│ `allow` indefinitely until this lands (zero impact on │
│ existing API-key consumers). │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Plan 3 §11 follow-ups (5 items, non-PR-shaped) │
│ • Register `mqsft_` prefix with GitHub secret scanning │
│ • Revisit MERCHANDISER `settings:deploy_live` once staged │
│ saves exist (§4.4A) │
│ • Env-gate `MARQO_API_KEY_SECRET` fallback in │
│ `utils/api_key_utils.py:150-159` │
│ • Optional session `jti` claim (multi-tab distinction) │
│ • Hygiene: CLI_TOKEN_PREFIX dedup · _TOKEN_PK_PREFIX → │
│ DynamoDBKeys registry · _now_iso → base helper │
└───────────────────────────────────────────────────────────────┘
│ merge to main
▼
┌───────────────────────────────────────────────────────────────┐
│ Plan 2 · Settings Versioning │
│ branch: feat/ver-history │
│ PR: #3702 (APPROVED + MERGEABLE · awaiting human │
│ merge · 4a9e8eeda) │
│ base: feat/storefront-auth (until SSO merges, then main) │
│ scope: SHOPVER#{domain} partition + S3 snapshot store + │
│ SettingsVersionService.capture in funnel + list/ │
│ get/diff/restore API + backfill script + UI panel │
│ (storefront_admin: versions route + list + diff + │
│ restore confirm dialog) + tier-1 metafield guard │
│ sizing only mirrored fields │
│ provides: SettingsVersionService (imported by Plan 4 for │
│ theme-scope capture wiring) · TIER1_METAFIELD_ │
│ LIMIT_BYTES constant │
│ rebase-strip after SSO merges (DONE during cascade absorb): │
│ • restore endpoint reads `auth.actor_id` from │
│ StorefrontAuthContext (no `f"api_key:..."` literals) │
│ • Depends(require_scopes("settings:write", │
│ "settings:deploy_live")) on restore-to-live │
│ • tests on SSO's StorefrontAuthContext fixtures │
└───────────────────────────┬───────────────────────────────────┘
│ deferred (no PR yet)
▼
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Embedded Shopify admin app versions UI │
│ Captures fire for those saves (shared SettingsService); the │
│ list/diff/restore UI is storefront_admin only for v1. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · admin_worker (internal staff dashboard) versions │
│ Out of scope for v1. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Staged-target restore │
│ Restore against `theme#{theme_id}#…` records is reserved in │
│ the schema but not implemented; activates with Plan 4's │
│ staged saves. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · P3 reviewer-agreed cleanups │
│ Hoist `SNAPSHOT_FORMAT_VERSION` from inline imports · │
│ `extract_content_fields` `or {}` falsy-collapse · │
│ paginated-helper cosmetic. │
└───────────────────────────────────────────────────────────────┘
│ merge to main
▼
┌───────────────────────────────────────────────────────────────┐
│ Plan 4 · Theme-Targeted Deploys │
│ branch: feat/theme-stage │
│ PR: pending raise (backend ~12/22 commits) │
│ base: feat/ver-history (until versioning merges, then │
│ main) │
│ scope: per-theme staged records + theme GraphQL query + │
│ GET /themes + extended GET/POST /settings?theme_id │
│ + POST /settings/deploy + DELETE theme record + │
│ Liquid embed change (Plan A or B per spike) + │
│ loader debug attrs + storefront_admin editor │
│ (theme picker, deploy dialog, target-keyed state) │
│ provides: settings:deploy_live scope is named here (Plan 3 │
│ implements; Plan 4 enforces on new routes) │
│ gates: Liquid spike (human-run, pending) blocks backend │
│ commit #17; UI (#18-#22) blocked on spike + │
│ backend complete │
│ rebase-strip after versioning merges: │
│ • DROP: ShopifySettings.backed_up_at / │
│ backup_of_live_version fields │
│ • DROP: SETTINGS#DEPLOY#BACKUP record + sk shape │
│ • DROP: rollback_deploy service method + POST .../rollback │
│ route + RollbackRequest/Response models │
│ • REPLACE rollback affordance with link to versioning's │
│ POST /versions/{N}/restore (UI: overflow menu → │
│ "Restore previous version") │
│ • WIRE: SettingsVersionService.capture(scope=f"theme:{id}") │
│ on staged save (update_theme_ui_components) │
│ • VERIFY: deploy_settings already passes event_type="deploy" │
│ + source_scope + source_version through funnel → versioning│
│ capture fires for free; add integration test │
│ • ADD: Depends(require_scopes(...)) on every new route per │
│ plan §3 matrix │
│ • SWAP: ApiKeyAuthRequestContext → StorefrontAuthContext │
│ • SWAP: hardcoded actor strings → auth.actor_id │
└───────────────────────────┬───────────────────────────────────┘
│ deferred (out of v1 per plan)
▼
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Detect Marqo app embed enablement on staging theme │
│ Reading the theme's `config/settings_data.json` via Asset API │
│ to programmatically detect embed enablement. Editor shows │
│ hint text instead. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Per-theme widget bundle testing │
│ Bundle is shared (theme-agnostic); only settings are theme- │
│ scoped. Testing a new widget bundle per theme is out of v1. │
└───────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ DEFERRED · Sharding (per shared trajectory) │
│ Single item per scope now; if a shop crosses the 300KB DDB │
│ alarm, move to manifest + N shard items. Realistic trigger │
│ is mirror-side splitting first. │
└───────────────────────────────────────────────────────────────┘
Cross-plan dependencies (what each plan owns)
Plan 1 (Concurrency) ─── owns ───► record_version + conditional writes
extra_transact_items hook
RESERVED kwargs threaded through funnel
RecordConflictError + 409 handler
RecordPayloadTooLargeError (413)
record_exceptions module
Plan 3 (SSO) ─── owns ───► models/auth.py canonical grammar
StorefrontAuthContext + actor_id
scope registry + require_scopes()
CLI token model + verification
actor_display field
Plan 2 (Versioning) ─── owns ───► SHOPVER#{domain} partition
SettingsVersionService.capture
restore API + UI
content_hash + changed_summary
TIER1_METAFIELD_LIMIT_BYTES constant
versioning_exceptions module
Plan 4 (Theme) ─── owns ───► per-theme staged records
Liquid role-based resolution
deploy via canonical funnel +
event_type="deploy"
theme-scope capture wiring for Plan 2
Why sequential merge (not parallel)
The parallel-fan-out alternative is faster to set up but ships transitional bridge code that has to be cleaned up in a follow-up PR:
| Without sequential merge | With sequential merge |
|---|---|
Versioning's restore stamps api_key:... by hand (no SSO yet) | Versioning's restore reads auth.actor_id from StorefrontAuthContext |
Theme-deploy ships SETTINGS#DEPLOY#BACKUP rolling record as a transitional undo | Theme-deploy delegates undo to versioning's POST /versions/{N}/restore |
| Theme-deploy's new routes have no scope enforcement | Theme-deploy enforces scopes from day one |
| Staged-theme saves never produce version records | update_theme_ui_components calls SettingsVersionService.capture(scope=f"theme:{id}") |
| A follow-up integration PR is needed | No follow-up needed |
The cost is wall-clock time: later PRs wait for earlier ones to merge before they can do their rebase-and-strip step. The benefit is no transitional code in production and no integration follow-up PR.
Review process
Each PR runs the same loop the lead applied to PR #3732:
- Implementer (subagent in own worktree) ships first cut + tests inline per commit (CLAUDE.md DoD)
- Three review lenses dispatched in parallel as transient
code-verifiersubagents: reuse, quality, efficiency - Findings filtered through six gates (already filed / already deliberated / does the fix fix the problem / verified against current code / fix verified / witnessed)
- Lead relays surviving P0/P1 findings to the implementer teammate; iterate
- Local /review PASS → teammate runs
/raisefrom their worktree, taggingishaaqas reviewer - Monitor (15-min poll) spawned per PR; lead relays ishaaq + claude[bot] comments back to the teammate; iterate until approved
- Approved + CI green PR sits waiting for its upstream to merge to main
- When upstream merges: teammate rebases or merges, strips transitional code per the checklist above, pushes — re-triggers review
- Final merge by human
Status legend
| Symbol | Meaning |
|---|---|
MERGED | Squashed onto main |
APPROVED + MERGEABLE | All reviews PASS, CI green, queued for human's merge button |
cascade absorb in flight | Following an upstream merge or rename; tracking back to MERGEABLE |
approved · awaiting upstream merge | All reviews PASS, but the upstream PR hasn't merged yet — sits until it does |
open · iterating review | PR raised, reviewers active |
round N review | Implementation done locally, round N of three-lens review in progress |
pending raise | All local review rounds PASS, awaiting /raise |
pending rebase-strip | Upstream merged, teammate needs to rebase + run their strip checklist |