---
phase: 22-storefronts-removal
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - packages/server/src/routes/admin.ts
  - packages/server/src/routes/facilitator.ts
  - packages/server/src/db/storefronts.ts
  - packages/server/src/db/index.ts
  - packages/server/src/db/types.ts
  - packages/server/src/db/migrations/004_drop_storefronts.ts
  - packages/server/src/db/migrations/index.ts
  - apps/dashboard/src/components/storefronts-section.tsx
  - apps/dashboard/src/lib/api.ts
  - apps/dashboard/src/app/dashboard/[id]/page.tsx
autonomous: true
requirements:
  - STORE-01
  - STORE-02
  - STORE-03
  - STORE-04
  - STORE-05

must_haves:
  truths:
    - "Grepping 'storefront' across packages/ and apps/ (excluding .planning/ and node_modules) returns zero matches — enforces removal across all surfaces"
    - "Migration 004_drop_storefronts runs cleanly on a fresh DB and on the existing dev DB (idempotent — second run does nothing) — honors D-02 (drop join table first, IF EXISTS) and D-03 (fresh DBs never create the tables)"
    - "routes/admin.ts and routes/facilitator.ts compile and the server serves requests with zero storefront handlers (GET /store → default Express 404) — honors D-01 (handlers deleted entirely, no 410/redirect/stub)"
    - "apps/dashboard build succeeds with no missing-import errors after StorefrontsSection and lib/api.ts storefront refs are removed — honors D-05 (single atomic plan; build never lands in a half-deleted state)"
    - "db/types.ts and db/index.ts export no storefront types or table accessors (no StorefrontRecord, no StorefrontProductRecord, no re-export of ./storefronts.js)"
    - "payment_links.image_url column and the product createProductSchema.imageUrl / updateProductSchema.imageUrl Zod fields are preserved untouched — honors D-04 (image_url belongs to the product domain, not storefronts; only the trailing comments that mentioned 'storefront' were rewritten)"
  artifacts:
    - path: "packages/server/src/db/migrations/004_drop_storefronts.ts"
      provides: "Idempotent migration dropping storefront_products then storefronts tables"
      contains: "DROP TABLE IF EXISTS storefront_products"
    - path: "packages/server/src/db/migrations/index.ts"
      provides: "Migration registration including m004"
      contains: "m004"
    - path: "packages/server/src/routes/admin.ts"
      provides: "Admin router with zero storefront handlers"
    - path: "packages/server/src/routes/facilitator.ts"
      provides: "Facilitator router with zero /store handlers"
    - path: "packages/server/src/db/index.ts"
      provides: "DB bootstrap with no storefront CREATE TABLE / index statements and no storefronts.js re-export"
    - path: "packages/server/src/db/types.ts"
      provides: "Type exports without StorefrontRecord / StorefrontProductRecord"
    - path: "apps/dashboard/src/lib/api.ts"
      provides: "API client without Storefront types or storefront methods"
    - path: "apps/dashboard/src/app/dashboard/[id]/page.tsx"
      provides: "Dashboard page with no storefronts tab, button, render branch, or import"
  deleted_artifacts:
    - "packages/server/src/db/storefronts.ts"
    - "apps/dashboard/src/components/storefronts-section.tsx"
  key_links:
    - from: "packages/server/src/db/migrations/index.ts"
      to: "packages/server/src/db/migrations/004_drop_storefronts.ts"
      via: "imported as m004 and pushed into migrations array"
      pattern: "m004"
    - from: "server startup (db/index.ts → runMigrations)"
      to: "DROP TABLE IF EXISTS storefronts"
      via: "auto-run migrations on boot"
      pattern: "DROP TABLE IF EXISTS storefronts"
---

<objective>
Excise the storefronts feature in a single atomic commit. Deletes 8 server-side storefront handlers, the db/storefronts module, the dashboard StorefrontsSection component, all storefront types and client methods, the storefronts tab integration, and the storefront bootstrap CREATE TABLE blocks. Adds migration 004_drop_storefronts to drop the tables on existing DBs. Also rewrites three trailing/leading comments on preserved code (D-04 image-url column + Zod schemas) so the comment prose no longer mentions "storefront".

Purpose: Storefronts never found product-market fit. Per D-05, a single atomic plan keeps the dashboard build green between commits (splitting would break it mid-revert).

Output: ~1000 LOC of pure deletion + one new migration file + three single-line comment rewrites. After this plan: zero `storefront` references (code or comments) in `packages/` and `apps/`, migration runs idempotently, both builds succeed, server returns default 404 on `GET /store`.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/22-storefronts-removal/22-CONTEXT.md

<!-- Migration template — the executor follows this Migration interface shape verbatim. -->
@packages/server/src/db/migrations/003_user_wallets_multi_chain.ts
@packages/server/src/db/migrations/index.ts

<interfaces>
<!-- Key contracts extracted so the executor does not need codebase exploration. -->

Migration interface (packages/server/src/db/migrations/index.ts):
```typescript
export interface Migration {
  name: string;
  up: (db: Database.Database) => void;
}
```

Migration registration pattern (packages/server/src/db/migrations/index.ts):
```typescript
import { migration as m001 } from './001_claims_server_id_nullable.js';
import { migration as m002 } from './002_campaign_audit_table.js';
import { migration as m003 } from './003_user_wallets_multi_chain.js';

const migrations: Migration[] = [m001, m002, m003];
```
Add: import + push m004 into the array, preserving order.

Storefront bootstrap block to remove (packages/server/src/db/index.ts, lines 475–501 inclusive — 27 lines):
- Lines 475–487: `-- Storefronts table` comment + `CREATE TABLE IF NOT EXISTS storefronts (...)`
- Line 488: blank line
- Lines 489–496: `-- Storefront-Products join table` comment + `CREATE TABLE IF NOT EXISTS storefront_products (...)`
- Line 497: blank line
- Lines 498–501: four `CREATE INDEX IF NOT EXISTS idx_storefront[s|_products]_*` statements

Bootstrap re-export to remove (packages/server/src/db/index.ts, line 797):
```typescript
export * from './storefronts.js';
```

Preserved code with comments that still mention "storefront" (REWRITE the comments only — see Task 2b.1):
- `packages/server/src/db/index.ts` line 118: `// Add image_url column (for storefront display)` — the ALTER TABLE block beneath it (lines 119–123) is preserved per D-04.
- `packages/server/src/routes/admin.ts` line 2319: trailing comment `// Product image for storefront` on the `createProductSchema.imageUrl` Zod field — the field itself is preserved per D-04.
- `packages/server/src/routes/admin.ts` line 2339: trailing comment `// Product image for storefront` on the `updateProductSchema.imageUrl` Zod field — the field itself is preserved per D-04.

Types to remove (packages/server/src/db/types.ts, lines 162–186 inclusive):
- Lines 162–176: `/** Storefront database record ... */` block + `export interface StorefrontRecord { ... }`
- Line 177: blank
- Lines 178–186: `/** Storefront-Product join record ... */` block + `export interface StorefrontProductRecord { ... }`

admin.ts deletions:
- Imports lines 45–57 (12 names from `'../db/storefronts.js'` inside an existing `import { ... } from ...` statement — remove the 12 names AND the trailing import line `} from '../db/storefronts.js';`). The 12 names: `createStorefront, getStorefrontById, getStorefrontBySlug, getStorefrontsByFacilitator, updateStorefront, deleteStorefront, isStorefrontSlugUnique, addProductToStorefront, removeProductFromStorefront, getStorefrontProducts, getStorefrontStats, getFacilitatorStorefrontsStats`.
- Storefront route block: lines 3005–3333 inclusive (anchor start: `// =============================================================================\n// Storefront Routes\n// =============================================================================`; anchor end: the line `});` at 3333 that closes the `DELETE .../storefronts/:storefrontId/products/:productId` handler whose body contains `console.error('Remove product from storefront error:', error);`). The next surviving line (3335) is `// ============================================` for `REFUND MANAGEMENT ENDPOINTS` — do NOT delete that.
- Do NOT delete the Zod `imageUrl` fields on lines 2319 or 2339 (product schemas — D-04). The trailing comments on those two lines DO get rewritten in Task 2b.1.

facilitator.ts deletions:
- Imports lines 17–19 (block: `getStorefrontBySlug, getStorefrontProducts, } from '../db/storefronts.js';`). Remove the 2 named imports and the trailing `} from '../db/storefronts.js';` line (and the opening `import {` if those are the only imports from that module).
- `GET /store/:slug` handler: lines 3033–3180 inclusive (anchor start: JSDoc `/**\n * Storefront by slug - Public endpoint for a specific storefront`; anchor end: `});` at line 3180 closing `router.get('/store/:slug', ...)`).
- `GET /store` handler: lines 3182–3436 inclusive (anchor start: JSDoc `/**\n * Storefront - Public page showing all active payment links for a facilitator`; anchor end: `});` at line 3436 closing `router.get('/store', ...)`). Next surviving line (3438) is `export { router as facilitatorRouter };` — do NOT delete.

apps/dashboard/src/lib/api.ts deletions:
- Types block lines 477–519 inclusive (the 5 storefront interfaces + their trailing blank line). Surviving next line (521) is `// Refund types`.
- Methods block lines 1234–1284 inclusive (8 methods: `getStorefronts`, `createStorefront`, `getStorefront`, `updateStorefront`, `deleteStorefront`, `addProductToStorefront`, `removeProductFromStorefront`, plus the `// Storefronts (product collections)` header comment at 1234). Surviving next line (1286) is `// Refund Config`.

apps/dashboard/src/app/dashboard/[id]/page.tsx deletions:
- Line 46: `import { StorefrontsSection } from '@/components/storefronts-section';` — delete the entire line.
- Line 60 Tab union: remove only `'storefronts' | ` from the union. Surviving line: `type Tab = 'transactions' | 'products' | 'webhooks' | 'refunds' | 'rewards' | 'settings';`. (Note: `'rewards'` stays — per specifics, that is Phase 24.)
- Storefronts tab button: lines 446–456 inclusive (the `<button onClick={() => setActiveTab('storefronts')} ... >Storefronts</button>` block). Adjacent buttons (Products at 437–445, Webhooks at 457–467) must remain intact.
- Storefronts tab render branch: lines 507–508 inclusive — specifically the two-line clause `) : activeTab === 'storefronts' ? (\n          <StorefrontsSection facilitatorId={id} facilitator={facilitator} />`. After deletion, the chain reads `... activeTab === 'products' ? <ProductsSection .../> ) : activeTab === 'webhooks' ? ( <WebhooksSection .../>`.
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add migration 004_drop_storefronts and register in migrations index</name>
  <files>packages/server/src/db/migrations/004_drop_storefronts.ts, packages/server/src/db/migrations/index.ts</files>
  <read_first>
    - packages/server/src/db/migrations/003_user_wallets_multi_chain.ts (template — Migration interface shape, name format, console.log convention)
    - packages/server/src/db/migrations/002_campaign_audit_table.ts (alt template — straightforward DDL pattern with no SELECT pre-check, closer to what we want)
    - packages/server/src/db/migrations/index.ts (registration point)
  </read_first>
  <action>
    Create `packages/server/src/db/migrations/004_drop_storefronts.ts` with this exact content (per D-02, drop join table first to satisfy FK; `IF EXISTS` on both for idempotency):

    ```typescript
    /**
     * Migration: Drop storefronts and storefront_products tables
     *
     * Storefronts feature removed in v1.3 (Phase 22). Drops the join table first
     * to satisfy the FK constraint, then drops storefronts itself. IF EXISTS on
     * both makes this idempotent for fresh DBs (where the bootstrap no longer
     * creates the tables) and for existing DBs.
     */
    import type Database from 'better-sqlite3';
    import type { Migration } from './index.js';

    export const migration: Migration = {
      name: '004_drop_storefronts',

      up(db: Database.Database): void {
        db.exec(`
          DROP TABLE IF EXISTS storefront_products;
          DROP TABLE IF EXISTS storefronts;
        `);
        console.log('  storefronts and storefront_products tables dropped (if present)');
      },
    };
    ```

    Then edit `packages/server/src/db/migrations/index.ts`:
    - Add `import { migration as m004 } from './004_drop_storefronts.js';` immediately after the existing `m003` import (line 17).
    - Append `m004` to the `migrations: Migration[]` array so it reads `[m001, m002, m003, m004]`.
    - Preserve trailing comma style of the existing array (each entry on its own line with trailing comma).

    Do NOT modify any other file in this task. Do NOT drop or recreate any indexes (`DROP TABLE` cascades index removal in SQLite).
  </action>
  <verify>
    <automated>test -f packages/server/src/db/migrations/004_drop_storefronts.ts && grep -q "DROP TABLE IF EXISTS storefront_products" packages/server/src/db/migrations/004_drop_storefronts.ts && grep -q "DROP TABLE IF EXISTS storefronts" packages/server/src/db/migrations/004_drop_storefronts.ts && grep -nE "DROP TABLE IF EXISTS storefront_products|DROP TABLE IF EXISTS storefronts" packages/server/src/db/migrations/004_drop_storefronts.ts | head -1 | grep -q "storefront_products" && grep -q "m004" packages/server/src/db/migrations/index.ts</automated>
  </verify>
  <acceptance_criteria>
    - `packages/server/src/db/migrations/004_drop_storefronts.ts` exists
    - File contains both `DROP TABLE IF EXISTS storefront_products` and `DROP TABLE IF EXISTS storefronts` strings
    - Join table drops BEFORE the parent table: `grep -nE "DROP TABLE IF EXISTS storefront_products|DROP TABLE IF EXISTS storefronts" packages/server/src/db/migrations/004_drop_storefronts.ts | head -1` contains `storefront_products` (enforces D-02 ordering)
    - `name: '004_drop_storefronts'` literal present in the new file
    - `packages/server/src/db/migrations/index.ts` contains `from './004_drop_storefronts.js'` and `m004` appears inside the `migrations: Migration[]` array literal
    - Full-package tsc is intentionally NOT run at task 1 time: at this point the server still contains storefront source from tasks 2/3 that will not typecheck (the project lint script `pnpm --filter @openfacilitator/server tsc --noEmit` is project-wide and will fail by design). Atomic build verification happens in task 5 after all deletions land. An isolated `npx tsc --noEmit` against the new migration file alone was considered and rejected: the file imports `'./index.js'` which requires the project's `moduleResolution: NodeNext` config and workspace context to resolve, so isolated tsc is unreliable here.
  </acceptance_criteria>
  <done>Migration file created with correct content and registered in the migrations array; file structure mirrors 003_user_wallets_multi_chain.ts pattern.</done>
</task>

<task type="auto">
  <name>Task 2: Delete storefront code from server (routes, db module, bootstrap, types)</name>
  <files>packages/server/src/routes/admin.ts, packages/server/src/routes/facilitator.ts, packages/server/src/db/storefronts.ts, packages/server/src/db/index.ts, packages/server/src/db/types.ts</files>
  <read_first>
    - packages/server/src/routes/admin.ts (lines 1–60 for import block context, lines 2315–2345 for the preserved imageUrl Zod schemas whose trailing comments get rewritten in 2b.1, lines 3000–3340 for storefront block boundaries)
    - packages/server/src/routes/facilitator.ts (lines 1–25 for import block context, lines 3030–3185 and 3180–3440 for `/store/:slug` and `/store` handler boundaries)
    - packages/server/src/db/index.ts (lines 110–125 for the preserved image_url ALTER TABLE block whose leading comment gets rewritten in 2b.1, lines 470–510 for bootstrap block context, lines 790–810 for re-export context)
    - packages/server/src/db/types.ts (lines 155–195 for storefront interface block context)
  </read_first>
  <action>
    Perform these deletions and edits in order. After each file edit, do NOT yet attempt to build — task 5 handles the build verification atomically across the server and dashboard.

    **2a. `packages/server/src/db/storefronts.ts`** — delete the entire file. Use `git rm packages/server/src/db/storefronts.ts` if convenient; `rm` is acceptable (per D-05 Claude's Discretion). 252 lines removed.

    **2b. `packages/server/src/db/index.ts`** — two deletions:
    - Delete lines 475–501 inclusive (the storefronts + storefront_products `CREATE TABLE IF NOT EXISTS` blocks and the four `CREATE INDEX IF NOT EXISTS idx_storefront*` statements). Surviving line above (474) is `CREATE INDEX IF NOT EXISTS idx_product_payments_status ON product_payments(status);` and the immediately following blank line. Surviving line below (503) is `-- Webhooks table (first-class webhook entities)`. Preserve exactly one blank line between the product_payments index block and the webhooks comment.
    - Delete line 797: `export * from './storefronts.js';` (entire line including trailing newline). The surrounding `export * from './...'` lines must remain untouched.

    **2b.1. Rewrite preserved comments to remove 'storefront' references.** Per D-04 we preserve the *code*, not the comment prose. Three trailing/leading comment rewrites — the code on each line stays untouched:

    - `packages/server/src/db/index.ts` line 118 — change:
      ```
      // Add image_url column (for storefront display)
      ```
      to:
      ```
      // Add image_url column for product image display
      ```
      The ALTER TABLE block immediately below (lines 119–123: `hasImageUrl`, `db.exec("ALTER TABLE payment_links ADD COLUMN image_url TEXT")`, `console.log('✅ Added image_url column to payment_links table')`) is preserved exactly.

    - `packages/server/src/routes/admin.ts` line 2319 — change the trailing comment from:
      ```
        imageUrl: z.string().url().max(2048).optional(), // Product image for storefront
      ```
      to:
      ```
        imageUrl: z.string().url().max(2048).optional(), // Product image
      ```
      The Zod field `imageUrl: z.string().url().max(2048).optional(),` itself is preserved exactly.

    - `packages/server/src/routes/admin.ts` line 2339 — change the trailing comment from:
      ```
        imageUrl: z.string().url().max(2048).optional().nullable(), // Product image for storefront
      ```
      to:
      ```
        imageUrl: z.string().url().max(2048).optional().nullable(), // Product image
      ```
      The Zod field `imageUrl: z.string().url().max(2048).optional().nullable(),` itself is preserved exactly.

    These three rewrites are NOT D-04 scope creep — D-04 preserves the schemas and the column, not the comment text. The grep gate in Task 4 and ROADMAP success criterion #1 are case-insensitive substring greps for `storefront`, so leaving these comments would fail the gate even though the underlying code is correct.

    **2c. `packages/server/src/db/types.ts`** — delete lines 162–186 inclusive. That is:
    - Lines 162–176: JSDoc comment `/** Storefront database record ... */` + `export interface StorefrontRecord { ... }` + closing `}`
    - Line 177: blank
    - Lines 178–186: JSDoc comment `/** Storefront-Product join record ... */` + `export interface StorefrontProductRecord { ... }` + closing `}`

    Surviving line above (161) is the closing `}` of the preceding interface. Surviving line below (187) is the blank line before `// Backwards compatibility aliases (deprecated)` at 188. Preserve exactly one blank line between the prior interface's `}` and the `// Backwards compatibility aliases` comment.

    **2d. `packages/server/src/routes/admin.ts`** — two deletions (separate from the 2b.1 comment rewrite above):
    - Imports: remove the 12 names imported from `'../db/storefronts.js'` (lines 45–57: `createStorefront, getStorefrontById, getStorefrontBySlug, getStorefrontsByFacilitator, updateStorefront, deleteStorefront, isStorefrontSlugUnique, addProductToStorefront, removeProductFromStorefront, getStorefrontProducts, getStorefrontStats, getFacilitatorStorefrontsStats`) AND the line `} from '../db/storefronts.js';` that closes that import statement. If `import {` opens that block on its own line (no other names share it), remove that opening line as well. Read lines 40–60 to confirm exact structure before editing.
    - Storefront route block: delete lines 3005–3333 inclusive. Anchor start: the line containing `// Storefront Routes` (between two `// ===...` separator lines — delete all three separator/header lines starting at the first `// ===...` at line 3005). Anchor end: the `});` at line 3333 that closes the handler whose body contains `console.error('Remove product from storefront error:', error);`. After deletion, line 3004 (`});` closing the previous Delete proxy URL endpoint) is immediately followed by a blank line and then `// ============================================` for `REFUND MANAGEMENT ENDPOINTS`. Preserve exactly one blank line.
    - Do NOT delete the Zod `imageUrl` field declarations on lines 2319 or 2339 — these stay per D-04. The trailing comments on those lines were rewritten in 2b.1.

    **2e. `packages/server/src/routes/facilitator.ts`** — two deletions plus an import edit:
    - Imports: remove `getStorefrontBySlug,` and `getStorefrontProducts,` (lines 17–18) AND the line `} from '../db/storefronts.js';` (line 19). If `import {` opens that block on its own line above and contains only those two names, also remove that opening line. Read lines 12–25 to confirm exact structure before editing.
    - `GET /store/:slug` handler: delete lines 3033–3180 inclusive. Anchor start: JSDoc `/**` at line 3033 whose next line reads ` * Storefront by slug - Public endpoint for a specific storefront`. Anchor end: `});` at line 3180 that closes `router.get('/store/:slug', requireFacilitator, ...)`.
    - `GET /store` handler: delete lines 3182–3436 inclusive. Anchor start: JSDoc `/**` at line 3182 whose next line reads ` * Storefront - Public page showing all active payment links for a facilitator`. Anchor end: `});` at line 3436 that closes `router.get('/store', requireFacilitator, ...)`. Surviving next significant line is `export { router as facilitatorRouter };` at line 3438. Preserve exactly one blank line between the previous surviving `});` and `export { router as facilitatorRouter };`.

    Per D-01: no 410, no redirect, no stub. Express default 404 is the desired behavior.

    D-04 restated for this revised plan: Preserve the `image_url` column on `payment_links` and the product `imageUrl` Zod schemas. The trailing comments that mention 'storefront' MUST be rewritten (see Task 2b.1) — preserving the code, not the prose.
  </action>
  <verify>
    <automated>test ! -f packages/server/src/db/storefronts.ts && [ "$(grep -ci 'storefront' packages/server/src/db/index.ts)" = "0" ] && [ "$(grep -ci 'storefront' packages/server/src/db/types.ts)" = "0" ] && [ "$(grep -ci 'storefront' packages/server/src/routes/admin.ts)" = "0" ] && [ "$(grep -ci 'storefront' packages/server/src/routes/facilitator.ts)" = "0" ] && [ "$(grep -c 'imageUrl: z.string().url().max(2048).optional(),' packages/server/src/routes/admin.ts)" = "1" ] && [ "$(grep -c 'imageUrl: z.string().url().max(2048).optional().nullable(),' packages/server/src/routes/admin.ts)" = "1" ] && [ "$(grep -c 'image_url TEXT' packages/server/src/db/index.ts)" -ge "1" ]</automated>
  </verify>
  <acceptance_criteria>
    - `test ! -f packages/server/src/db/storefronts.ts` (file deleted)
    - `grep -ci 'storefront' packages/server/src/db/index.ts` returns `0` (covers both 2b deletion and 2b.1 comment rewrite on line 118)
    - `grep -ci 'storefront' packages/server/src/db/types.ts` returns `0`
    - `grep -ci 'storefront' packages/server/src/routes/admin.ts` returns `0` (covers both 2d deletion and 2b.1 comment rewrites on lines 2319/2339)
    - `grep -ci 'storefront' packages/server/src/routes/facilitator.ts` returns `0`
    - `grep -c 'imageUrl: z.string().url().max(2048).optional(),' packages/server/src/routes/admin.ts` returns `1` (proves the `createProductSchema.imageUrl` Zod field preserved per D-04)
    - `grep -c 'imageUrl: z.string().url().max(2048).optional().nullable(),' packages/server/src/routes/admin.ts` returns `1` (proves the `updateProductSchema.imageUrl` nullable variant preserved per D-04)
    - `grep -c 'image_url TEXT' packages/server/src/db/index.ts` returns at least `1` (proves the `ALTER TABLE payment_links ADD COLUMN image_url TEXT` statement preserved per D-04)
    - `grep -n "image_url" packages/server/src/db/index.ts` still returns matches on the `payment_links` table CREATE block and the ALTER TABLE block — confirms D-04 preserved
    - Refund/webhook/payment-link CREATE blocks in `db/index.ts` and refund/admin handlers in `admin.ts` remain present (sanity check: `grep -c 'CREATE TABLE IF NOT EXISTS webhooks' packages/server/src/db/index.ts` returns `1`)
  </acceptance_criteria>
  <done>All server-side storefront code deleted. `image_url` column on `payment_links` preserved (with rewritten comment). Product `imageUrl` Zod schemas preserved (with rewritten trailing comments). Build will be re-verified atomically in task 5 once the dashboard side is also cleaned.</done>
</task>

<task type="auto">
  <name>Task 3: Delete storefront code from dashboard (component, api client, tab integration)</name>
  <files>apps/dashboard/src/components/storefronts-section.tsx, apps/dashboard/src/lib/api.ts, apps/dashboard/src/app/dashboard/[id]/page.tsx</files>
  <read_first>
    - apps/dashboard/src/app/dashboard/[id]/page.tsx (lines 40–70 for import + Tab type context, lines 440–520 for tab nav and render-branch context)
    - apps/dashboard/src/lib/api.ts (lines 470–525 for types block context, lines 1228–1290 for methods block context)
  </read_first>
  <action>
    **3a. `apps/dashboard/src/components/storefronts-section.tsx`** — delete the entire file (629 lines). `git rm` or `rm` is fine.

    **3b. `apps/dashboard/src/lib/api.ts`** — two deletions:
    - Types block lines 477–519 inclusive. Anchor start: `// Storefronts (product collections)` comment at line 477. Anchor end: the closing `}` of `export interface StorefrontDetailResponse extends Storefront { products: StorefrontProduct[]; }` at line 519. Surviving next line (521) is `// Refund types`. Preserve exactly one blank line between line 476 (`export type PaymentLinkDetailResponse = ProductDetailResponse;`'s trailing blank or the line itself depending on file structure) and the `// Refund types` comment.
    - Methods block lines 1234–1284 inclusive. Anchor start: `// Storefronts (product collections)` comment at line 1234. Anchor end: the `}` at line 1284 that closes `async removeProductFromStorefront(...)`. Surviving next line (1286) is `// Refund Config`. Preserve exactly one blank line between the previous method's closing `}` (line 1232's `});` for `deleteX`) and the `// Refund Config` comment.

    **3c. `apps/dashboard/src/app/dashboard/[id]/page.tsx`** — four edits:
    - Line 46: delete the entire line `import { StorefrontsSection } from '@/components/storefronts-section';`.
    - Line 60: remove `'storefronts' | ` from the `Tab` union type only. Resulting line: `type Tab = 'transactions' | 'products' | 'webhooks' | 'refunds' | 'rewards' | 'settings';`. Per CONTEXT.md specifics, `'rewards'` stays (Phase 24's job).
    - Storefronts tab button: delete lines 446–456 inclusive (the entire `<button onClick={() => setActiveTab('storefronts')} ... >Storefronts</button>` element including its closing `</button>` on line 456). Adjacent siblings — Products button (lines ~437–445) and Webhooks button (lines ~457–467 before deletion) — must remain intact.
    - Tab render branch: delete lines 507–508 inclusive. Specifically, the two-line clause:
      ```
              ) : activeTab === 'storefronts' ? (
                <StorefrontsSection facilitatorId={id} facilitator={facilitator} />
      ```
      After deletion, the chain reads: `... activeTab === 'products' ? ( <ProductsSection .../> ) : activeTab === 'webhooks' ? ( <WebhooksSection .../> ) : ...`. Validate JSX balance — the `) : activeTab === 'webhooks' ? (` clause must now immediately follow the `<ProductsSection />` block.

    Do NOT modify the `'rewards'` Tab member, the rewards button, or the rewards render branch — those belong to Phase 24.
  </action>
  <verify>
    <automated>test ! -f apps/dashboard/src/components/storefronts-section.tsx && [ "$(grep -ci 'storefront' apps/dashboard/src/lib/api.ts)" = "0" ] && [ "$(grep -ci 'storefront' apps/dashboard/src/app/dashboard/\[id\]/page.tsx)" = "0" ]</automated>
  </verify>
  <acceptance_criteria>
    - `test ! -f apps/dashboard/src/components/storefronts-section.tsx`
    - `grep -ci 'storefront' apps/dashboard/src/lib/api.ts` returns `0`
    - `grep -ci 'storefront' apps/dashboard/src/app/dashboard/[id]/page.tsx` returns `0`
    - `grep -c "'rewards'" apps/dashboard/src/app/dashboard/[id]/page.tsx` returns ≥ 3 (Tab union + button onClick + render branch — rewards UI not touched per Phase 22 scope)
    - `grep -c "ProductsSection" apps/dashboard/src/app/dashboard/[id]/page.tsx` returns ≥ 1 (sibling tab still wired)
    - `grep -c "WebhooksSection" apps/dashboard/src/app/dashboard/[id]/page.tsx` returns ≥ 1 (sibling tab still wired)
  </acceptance_criteria>
  <done>Dashboard storefront code fully removed. Rewards UI (Phase 24's domain) untouched. Build re-verified atomically in task 5.</done>
</task>

<task type="auto">
  <name>Task 4: Full-repo grep gate (no storefront refs remain in packages/ or apps/)</name>
  <files>(verification-only task — no file edits)</files>
  <read_first>
    - (none — this is a read-only verification task that greps the working tree)
  </read_first>
  <action>
    Run a repo-wide grep for `storefront` (case-insensitive) across `packages/` and `apps/`, excluding `.planning/` (historical docs) and `node_modules/`. If any match remains, the executor must locate it, determine whether it is in-scope per CONTEXT.md, and either delete it (in-scope) or rewrite it (in-scope comment on preserved code, per the Task 2b.1 pattern) or document why it was missed (out-of-scope).

    Command (run from repo root):
    ```bash
    grep -ri --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=.turbo storefront packages/ apps/ | wc -l
    ```

    Expected result: `0`.

    If the result is non-zero, dump the matches:
    ```bash
    grep -rin --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=.turbo storefront packages/ apps/
    ```
    Then handle each match per the same rules used in tasks 2 and 3: delete dead code, or rewrite the comment if it sits on preserved D-04 code. Do NOT consider this task complete until the count is `0`.

    Note: this is the verification step backing success criterion #1 from ROADMAP.md and the STORE-01..STORE-05 grep gate. No commits happen between tasks 2/3 and this task — keep it as a within-task safety net before the build verification in task 5.
  </action>
  <verify>
    <automated>[ "$(grep -ri --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=.turbo storefront packages/ apps/ | wc -l | tr -d ' ')" = "0" ]</automated>
  </verify>
  <acceptance_criteria>
    - `grep -ri --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=.turbo storefront packages/ apps/ | wc -l` returns `0`
    - No new files were created or modified by this task (verification-only)
  </acceptance_criteria>
  <done>Repo-wide `storefront` grep returns zero matches in `packages/` and `apps/` (excluding `.planning/` and build/dep dirs). Satisfies STORE-01..STORE-05 grep gate and ROADMAP success criterion #1.</done>
</task>

<task type="auto">
  <name>Task 5: Build verification + migration smoke test (server + dashboard)</name>
  <files>(verification-only task — no file edits; reads server DB after startup)</files>
  <read_first>
    - packages/server/src/db/index.ts (confirm `runMigrations(db)` is invoked at bootstrap — this is how the migration auto-runs; no separate `db push` exists per phase init notes)
    - packages/server/src/db/migrations/index.ts (confirm m004 registered after task 1)
  </read_first>
  <action>
    This task validates both builds and that migration 004 runs cleanly on the existing dev DB. Per the phase init notes, this project has no `db push` command — migrations auto-run on server startup via the `db/index.ts` bootstrap. The migration-runner smoke test replaces what would otherwise be a `db push` BLOCKING task.

    This is also where the atomic build verification deferred from Task 1 happens. At Task 1 time the server still contained storefront source so project-wide tsc could not pass; after Tasks 2 and 3 have run, `pnpm --filter @openfacilitator/server build` (which invokes `tsc`) is the right check.

    **5a. Server typecheck + build:**
    ```bash
    pnpm --filter @openfacilitator/server build
    ```
    Must exit 0. If it fails, the executor inspects errors:
    - Missing-import errors mentioning `./storefronts.js` or `Storefront*` types → a deletion in task 2 was incomplete; locate, fix, re-run.
    - Errors on `m004` or the new migration file → task 1 registration was incomplete or the new file has a typo; fix, re-run.
    - Other errors → out-of-scope; surface to user before continuing.

    **5b. Dashboard typecheck + build:**
    ```bash
    pnpm --filter @openfacilitator/dashboard build
    ```
    Must exit 0. If it fails:
    - Missing-import errors mentioning `@/components/storefronts-section` or `Storefront*` types from `lib/api` → a deletion in task 3 was incomplete; locate, fix, re-run.
    - JSX balance errors in `dashboard/[id]/page.tsx` → the tab button or render-branch edit in task 3c left unbalanced parens; re-inspect lines around 446 and 507 and repair.

    **5c. Migration smoke test on existing dev DB.** The dev DB path per CONTEXT.md is `packages/server/data/openfacilitator.db`. Approach:

    1. Check the dev DB exists. If `test -f packages/server/data/openfacilitator.db` is false, skip the existing-DB portion and verify only that the migration would run on a fresh DB (covered by start command on fresh path).
    2. Snapshot pre-migration table list:
       ```bash
       sqlite3 packages/server/data/openfacilitator.db ".tables" | tr ' ' '\n' | grep -E "^storefront(s|_products)$" | sort
       ```
       Record output (expected: either both `storefronts` and `storefront_products`, or nothing if the dev DB never had them — both states are valid going in).
    3. Run the migration by starting the server in a way that triggers `runMigrations` and then exits. The repo's typical pattern is `pnpm --filter @openfacilitator/server dev` or a one-shot script. The executor should:
       - Prefer any existing one-shot migration script if `package.json` exposes one (e.g. `pnpm --filter @openfacilitator/server migrate`).
       - Otherwise, start the dev server in the background, wait for log line `✅ Migration complete: 004_drop_storefronts` (or `🔄 Running migration: 004_drop_storefronts` followed by no error within 5s), then kill the process.
       - Acceptable alternative: write a minimal node one-liner that imports `runMigrations` and calls it against the dev DB path, then exits. The executor may choose; record the chosen method in the SUMMARY.
    4. Post-migration table check:
       ```bash
       [ "$(sqlite3 packages/server/data/openfacilitator.db '.tables' | tr ' ' '\n' | grep -cE '^storefront(s|_products)$')" = "0" ]
       ```
       Must be true.
    5. Idempotency check: trigger the migration runner a second time the same way. Expected behavior: the runner sees `004_drop_storefronts` already in the `migrations` table and skips it (no-op). No error. The post-state table check above must still pass.

    **5d. Final fresh-DB sanity (optional but recommended):**
    ```bash
    rm -f /tmp/openfac-phase22-fresh.db
    # Use whatever env var / config the server respects for DB path; if the project hardcodes the path in db/index.ts, skip this sub-step and rely on the bootstrap not creating storefront tables (verified via grep in task 2).
    ```
    If the project supports overriding the DB path via env var, point a fresh DB at it, run migrations, and confirm `storefronts` and `storefront_products` are not created. If no override mechanism exists, the grep-based verification from task 2 (zero `CREATE TABLE.*storefront` in `db/index.ts`) is sufficient evidence for the fresh-DB criterion.
  </action>
  <verify>
    <automated>pnpm --filter @openfacilitator/server build && pnpm --filter @openfacilitator/dashboard build && [ ! -f packages/server/data/openfacilitator.db ] || [ "$(sqlite3 packages/server/data/openfacilitator.db '.tables' | tr ' ' '\n' | grep -cE '^storefront(s|_products)$')" = "0" ]</automated>
  </verify>
  <acceptance_criteria>
    - `pnpm --filter @openfacilitator/server build` exits 0
    - `pnpm --filter @openfacilitator/dashboard build` exits 0
    - After migration runs on existing dev DB: `sqlite3 packages/server/data/openfacilitator.db ".tables" | tr ' ' '\n' | grep -cE '^storefront(s|_products)$'` returns `0`
    - Migration is idempotent: a second run completes without error and the post-state remains zero storefront tables
    - The `migrations` table contains a row with `name = '004_drop_storefronts'` after the first run: `sqlite3 packages/server/data/openfacilitator.db "SELECT name FROM migrations WHERE name='004_drop_storefronts';"` returns one row
    - `grep -c "CREATE TABLE IF NOT EXISTS storefronts" packages/server/src/db/index.ts` returns `0` (bootstrap will not recreate the tables on a fresh DB)
  </acceptance_criteria>
  <done>Both builds green; storefront tables dropped from existing dev DB; migration is idempotent; bootstrap will not recreate tables on fresh DBs. Satisfies ROADMAP success criteria #2, #3, #4, #5.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| client → `/api/admin/*` (admin.ts) | Authenticated admin requests cross this boundary; storefront CRUD lived here |
| client → `/store`, `/store/:slug` (facilitator.ts) | Public unauthenticated requests; storefront read endpoints lived here |
| migration runner → SQLite dev DB | Local DDL execution at server startup |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-22-01 | Elevation of Privilege | `routes/admin.ts` (surviving handlers after lines 3005–3333 deletion) | mitigate | Task 2's grep gate (`grep -ci 'storefront' packages/server/src/routes/admin.ts` returns `0`) plus task 5's `pnpm build` confirms no dangling `requireAuth` decorators were inadvertently stripped from adjacent handlers. Adjacent surviving handlers (Delete proxy URL at line 3003 close, Refund Management at line 3335 open) still use `requireAuth` — verified by line-precise deletion boundary anchored on `});` at 3333 and `// ============================================` at 3335. |
| T-22-02 | Information Disclosure | `routes/facilitator.ts` (`/store/:slug` and `/store` deletion) | mitigate | Per D-01, no stub handlers replace the deleted routes. Express default 404 leaks no facilitator metadata. The deletion boundary preserves `export { router as facilitatorRouter };` at line 3438 — no other public endpoint is removed. Verified by grep count `0` and `pnpm build` exit 0. |
| T-22-03 | Tampering | Migration 004 on production-like DB | mitigate | `DROP TABLE IF EXISTS` is idempotent and irreversible; storefronts are abandoned per CONTEXT.md decisions log (2026-05-12). The migration runs in a transaction (per `migrations/index.ts` line 55) so a partial failure rolls back. Smoke-tested on dev DB in task 5c; idempotency re-verified by running twice. No data is preserved by design (CONTEXT.md: "Pure deletion — no replacement feature, no data preservation"). |
| T-22-04 | Denial of Service | Fresh DB bootstrap after table removal | accept | Removing the bootstrap `CREATE TABLE storefronts` block could in theory crash a legacy client expecting the table. Accepted: no such client exists — storefront UI is being deleted in the same atomic commit, and the SDK skill docs (Phase 24) will be updated next. Verified by full-repo grep gate in task 4. |
| T-22-05 | Repudiation | Audit log entries referencing storefronts | accept | No audit-log writes target `storefronts` table directly (verified absence: `grep -i "storefront" packages/server/src/audit*` returns no hits — re-confirmed by full-repo grep gate in task 4). The `campaign_audit_log` table from Phase 21 is a separate concern (Phase 23/REWARDS-06). |
| T-22-06 | Spoofing | New `requireAuth`/`requireFacilitator` regressions | accept | This phase only deletes code, does not add or modify auth decorators on surviving handlers. Phase 25 (SEC-01) provides the comprehensive authentication audit. |
</threat_model>

<verification>
Phase-level checks (run after all 5 tasks complete, before commit):

1. **Grep gate (ROADMAP #1, STORE-01..05):**
   ```bash
   grep -ri --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=.turbo storefront packages/ apps/ | wc -l
   ```
   Expected: `0`.

2. **Server build (ROADMAP #3):**
   ```bash
   pnpm --filter @openfacilitator/server build
   ```
   Expected: exit 0.

3. **Dashboard build (ROADMAP #4):**
   ```bash
   pnpm --filter @openfacilitator/dashboard build
   ```
   Expected: exit 0.

4. **Migration drop verified (ROADMAP #2):**
   ```bash
   sqlite3 packages/server/data/openfacilitator.db ".tables" | tr ' ' '\n' | grep -cE "^storefront(s|_products)$"
   ```
   Expected: `0` (after migration runs).

5. **Migration recorded:**
   ```bash
   sqlite3 packages/server/data/openfacilitator.db "SELECT name FROM migrations WHERE name='004_drop_storefronts';"
   ```
   Expected: one row.

6. **Bootstrap will not recreate (ROADMAP #2 fresh-DB half):**
   ```bash
   grep -c "CREATE TABLE IF NOT EXISTS storefronts" packages/server/src/db/index.ts
   ```
   Expected: `0`.

7. **db/index.ts re-export removed (ROADMAP #5):**
   ```bash
   grep -c "from './storefronts" packages/server/src/db/index.ts
   ```
   Expected: `0`.

8. **db/types.ts interfaces removed (ROADMAP #5):**
   ```bash
   grep -cE "StorefrontRecord|StorefrontProductRecord" packages/server/src/db/types.ts
   ```
   Expected: `0`.

9. **D-04 preservation check:**
   ```bash
   grep -c "image_url" packages/server/src/db/index.ts
   ```
   Expected: ≥ 1 (the `payment_links` table still has `image_url` and the ALTER TABLE block still adds it on upgrade).
   ```bash
   grep -c "imageUrl" packages/server/src/routes/admin.ts
   ```
   Expected: ≥ 1 (product Zod schemas around lines 2319/2339 still reference `imageUrl` even though the trailing comments were rewritten).
   ```bash
   grep -c 'imageUrl: z.string().url().max(2048).optional(),' packages/server/src/routes/admin.ts
   ```
   Expected: `1` (`createProductSchema.imageUrl` preserved exactly).
   ```bash
   grep -c 'imageUrl: z.string().url().max(2048).optional().nullable(),' packages/server/src/routes/admin.ts
   ```
   Expected: `1` (`updateProductSchema.imageUrl` nullable variant preserved exactly).
   ```bash
   grep -c 'image_url TEXT' packages/server/src/db/index.ts
   ```
   Expected: ≥ 1 (the `ALTER TABLE payment_links ADD COLUMN image_url TEXT` statement preserved).

10. **Rewards UI preserved for Phase 24:**
    ```bash
    grep -c "'rewards'" apps/dashboard/src/app/dashboard/\[id\]/page.tsx
    ```
    Expected: ≥ 3 (Tab union + button + render branch — rewards tab untouched).
</verification>

<success_criteria>
All five ROADMAP.md Phase 22 success criteria satisfied:

1. **Zero storefront references** — `grep -ri storefront packages/ apps/ --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=.turbo | wc -l` returns `0`. (Note: this requires both the deletions in tasks 2/3 AND the comment rewrites in 2b.1, since the grep is a case-insensitive substring match that also catches comment prose.)
2. **Migration runs cleanly on fresh and existing DB** — `004_drop_storefronts.ts` uses `DROP TABLE IF EXISTS` (idempotent); smoke-tested on existing dev DB in task 5; bootstrap no longer creates the tables for fresh DBs.
3. **`routes/admin.ts` and `routes/facilitator.ts` compile with zero storefront handlers** — `pnpm --filter @openfacilitator/server build` exits 0; storefront route blocks deleted (admin lines 3005–3333; facilitator lines 3033–3180 and 3182–3436); `GET /store` returns default 404.
4. **Dashboard build succeeds with no missing-import errors** — `pnpm --filter @openfacilitator/dashboard build` exits 0; `StorefrontsSection` component file deleted; `lib/api.ts` storefront types/methods deleted; tab integration in `dashboard/[id]/page.tsx` cleaned (import, Tab union member, button, render branch).
5. **`db/types.ts` and `db/index.ts` export no storefront types/accessors** — `StorefrontRecord` and `StorefrontProductRecord` interfaces deleted from `types.ts`; `export * from './storefronts.js';` removed from `db/index.ts`; entire `db/storefronts.ts` file deleted.

Plus locked decisions honored:
- **D-01:** No 410/redirect/stub for `GET /store` or `GET /store/:slug` — both handlers deleted entirely.
- **D-02:** Migration `004_drop_storefronts.ts` runs `DROP TABLE IF EXISTS storefront_products; DROP TABLE IF EXISTS storefronts;` (join table first).
- **D-03:** Bootstrap `CREATE TABLE IF NOT EXISTS storefronts/storefront_products` and 4 indexes removed from `db/index.ts` lines 475–501.
- **D-04:** `payment_links.image_url` column preserved (CREATE column on the table + ALTER TABLE upgrade block both intact); product `imageUrl` Zod schemas untouched. Per the revised plan: D-04 preserves the *code*, not the comment prose — the three trailing/leading comments that mentioned "storefront" were rewritten in Task 2b.1.
- **D-05:** Single atomic plan; build verification (task 5) covers both server and dashboard before commit so the repo never lands in a half-deleted state.
</success_criteria>

<output>
After completion, create `.planning/phases/22-storefronts-removal/22-01-SUMMARY.md` summarizing:
- Files deleted (count, paths)
- Lines removed (approx total, per file)
- Migration added and registered
- Comment rewrites applied (the three lines from Task 2b.1 — exact before/after text and file:line)
- Build verification results (server + dashboard exit codes)
- Migration smoke test method chosen (one-shot script vs dev-server-then-kill vs node one-liner) and result
- Confirmation of D-04 preservation (`image_url` / `imageUrl` checks, including the four preservation grep counts from the verification section)
- Any deviations from line-precise deletion boundaries and why (expected: none)
</output>
