Output: {"status":{"019c5b58-fbd3-7121-8e98-7306145e5f38":{"completed":"**General conventions**\n\n- Every table has `id` INTEGER PRIMARY KEY except junction tables/pivots where specified composite keys apply.  \n- Monetary amounts are stored as INTEGER minor units (cents) with `_amount` suffix.  \n- Booleans are INTEGER NOT NULL DEFAULT `0`.  \n- JSON data is stored as TEXT (Laravel casts).  \n- Timestamps are TEXT (ISO-8601); soft deletes only when called out.  \n- Enumerations are TEXT checked via `CHECK` constraints (see each table).  \n\n**Creation order (per Phase 1→10 in `specs/09-IMPLEMENTATION-ROADMAP.md`)**\n\n1. **Phase 1 (Foundation)** – `organizations`, `stores`, `store_domains`, augment `users`, `store_users`, `store_settings`.  \n2. **Phase 2 (Catalog)** – `products`, `product_options`, `product_option_values`, `product_variants`, `variant_option_values`, `inventory_items`, `collections`, `collection_products`, `product_media`.  \n3. **Phase 3 (Themes & Layout)** – `themes`, `theme_files`, `theme_settings`, `pages`, `navigation_menus`, `navigation_items`.  \n4. **Phase 4 (Cart/Checkout/Discounts/Shipping/Taxes)** – `carts`, `cart_lines`, `checkouts`, `shipping_zones`, `shipping_rates`, `tax_settings`, `discounts`.  \n5. **Phase 5 (Payments/Orders/Fulfillment)** – `customers`, `customer_addresses`, `orders`, `order_lines`, `payments`, `refunds`, `fulfillments`, `fulfillment_lines`.  \n6. **Phase 7 (Analytics)** – `analytics_events`, `analytics_daily`.  \n7. **Phase 10 (Apps & Webhooks)** – `apps`, `app_installations`, `oauth_clients`, `oauth_tokens`, `webhook_subscriptions`, `webhook_deliveries`.  \n8. **Phase 4/5 dependencies** also imply `search_settings`, `search_queries` (Phase 8) feed off catalog data; create alongside Phase 4/5 as per roadmap.  \n\n**Phase 1 – Foundation**\n\n1. `organizations`  \n   - Columns: `id` PK, `name` TEXT NOT NULL, `billing_email` TEXT NOT NULL, `created_at` TEXT NULL, `updated_at` TEXT NULL.  \n   - Indexes: `idx_organizations_billing_email` (`billing_email`).  \n\n2. `stores`  \n   - Columns: `id`, `organization_id` INTEGER NOT NULL FK → `organizations(id)` ON DELETE CASCADE, `name`, `handle` TEXT UNIQUE, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'suspended'`), `default_currency` TEXT NOT NULL DEFAULT `'USD'`, `default_locale` TEXT NOT NULL DEFAULT `'en'`, `timezone` TEXT NOT NULL DEFAULT `'UTC'`, timestamps.  \n   - Indexes: unique `idx_stores_handle` (`handle`), `idx_stores_organization_id`, `idx_stores_status`.  \n\n3. `store_domains`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `hostname` TEXT UNIQUE, `type` TEXT NOT NULL DEFAULT `'storefront'` CHECK IN (`'storefront'`, `'admin'`, `'api'`), `is_primary` BOOLEAN DEFAULT `0`, `tls_mode` TEXT NOT NULL DEFAULT `'managed'` CHECK IN (`'managed'`, `'bring_your_own'`), `created_at` TEXT NULL.  \n   - Indexes: unique `idx_store_domains_hostname`, `idx_store_domains_store_id`, composite `idx_store_domains_store_primary` (`store_id`, `is_primary`).  \n\n4. `users` (default Laravel users migration augmented)  \n   - Columns: `id`, `email` TEXT UNIQUE, `password_hash` TEXT NOT NULL (maps to Laravel `password`), `name`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'disabled'`), `email_verified_at`, `last_login_at`, `two_factor_secret`, `two_factor_recovery_codes`, `two_factor_confirmed_at`, `remember_token`, timestamps.  \n   - Indexes: `idx_users_email` unique, `idx_users_status`.  \n\n5. `store_users` (pivot)  \n   - Columns: `store_id` FK → `stores(id)` ON DELETE CASCADE, `user_id` FK → `users(id)` ON DELETE CASCADE, `role` TEXT NOT NULL DEFAULT `'staff'` CHECK IN (`'owner'`, `'admin'`, `'staff'`, `'support'`), `created_at` TEXT NULL.  \n   - Primary key: composite (`store_id`, `user_id`).  \n   - Indexes: `idx_store_users_user_id`, `idx_store_users_role` (`store_id`, `role`).  \n\n6. `store_settings`  \n   - Columns: `store_id` PK/FK → `stores(id)` ON DELETE CASCADE, `settings_json` TEXT NOT NULL DEFAULT `'{}'`, `updated_at` TEXT NULL.  \n\n**Phase 2 – Catalog**\n\n1. `products`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `title`, `handle` TEXT (unique per store), `status` TEXT NOT NULL DEFAULT `'draft'` CHECK IN (`'draft'`, `'active'`, `'archived'`), `description_html`, `vendor`, `product_type`, `tags` TEXT NOT NULL DEFAULT `'[]'`, `published_at`, timestamps.  \n   - Indexes: `idx_products_store_handle` unique (`store_id`, `handle`), `idx_products_store_id`, `idx_products_store_status`, `idx_products_published_at`, `idx_products_vendor`, `idx_products_product_type`.  \n\n2. `product_options`  \n   - Columns: `id`, `product_id` FK → `products(id)` ON DELETE CASCADE, `name` TEXT NOT NULL, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_product_options_product_id`, unique `idx_product_options_product_position` (`product_id`, `position`).  \n\n3. `product_option_values`  \n   - Columns: `id`, `product_option_id` FK → `product_options(id)` ON DELETE CASCADE, `value`, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_product_option_values_option_id`, unique `idx_product_option_values_option_position` (`product_option_id`, `position`).  \n\n4. `product_variants`  \n   - Columns: `id`, `product_id` FK → `products(id)` ON DELETE CASCADE, `sku`, `barcode`, `price_amount` INTEGER NOT NULL DEFAULT `0`, `compare_at_amount` INTEGER NULL, `currency` TEXT NOT NULL DEFAULT `'USD'`, `weight_g` INTEGER NULL, `requires_shipping` BOOLEAN NOT NULL DEFAULT `1`, `is_default` BOOLEAN NOT NULL DEFAULT `0`, `position` INTEGER NOT NULL DEFAULT `0`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'archived'`), timestamps.  \n   - Indexes: `idx_product_variants_product_id`, `idx_product_variants_sku`, `idx_product_variants_barcode`, `idx_product_variants_product_position`, `idx_product_variants_product_default`.  \n\n5. `variant_option_values` (junction)  \n   - Columns: `variant_id` FK → `product_variants(id)` ON DELETE CASCADE, `product_option_value_id` FK → `product_option_values(id)` ON DELETE CASCADE.  \n   - Primary key: (`variant_id`, `product_option_value_id`).  \n   - Index: `idx_variant_option_values_value_id` (`product_option_value_id`).  \n\n6. `inventory_items`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `variant_id` FK → `product_variants(id)` ON DELETE CASCADE UNIQUE, `quantity_on_hand` INTEGER NOT NULL DEFAULT `0`, `quantity_reserved` INTEGER NOT NULL DEFAULT `0`, `policy` TEXT NOT NULL DEFAULT `'deny'` CHECK IN (`'deny'`, `'continue'`).  \n   - Indexes: unique `idx_inventory_items_variant_id`, `idx_inventory_items_store_id`.  \n\n7. `collections`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `title`, `handle` TEXT UNIQUE per store, `description_html`, `type` TEXT NOT NULL DEFAULT `'manual'` CHECK IN (`'manual'`, `'automated'`), `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'draft'`, `'active'`, `'archived'`), timestamps.  \n   - Indexes: unique `idx_collections_store_handle`, `idx_collections_store_id`, `idx_collections_store_status`.  \n\n8. `collection_products` (pivot)  \n   - Columns: `collection_id` FK → `collections(id)` ON DELETE CASCADE, `product_id` FK → `products(id)` ON DELETE CASCADE, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Primary key: (`collection_id`, `product_id`).  \n   - Indexes: `idx_collection_products_product_id`, `idx_collection_products_position`.  \n\n9. `product_media`  \n   - Columns: `id`, `product_id` FK → `products(id)` ON DELETE CASCADE, `type` TEXT NOT NULL DEFAULT `'image'` CHECK IN (`'image'`, `'video'`), `storage_key`, `alt_text`, `width` INTEGER, `height` INTEGER, `mime_type`, `byte_size` INTEGER, `position` INTEGER NOT NULL DEFAULT `0`, `status` TEXT NOT NULL DEFAULT `'processing'` CHECK IN (`'processing'`, `'ready'`, `'failed'`), `created_at`.  \n   - Indexes: `idx_product_media_product_id`, `idx_product_media_product_position`, `idx_product_media_status`.  \n\n**Phase 3 – Themes, Pages, Navigation**\n\n1. `themes`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `name`, `version`, `status` TEXT NOT NULL DEFAULT `'draft'` CHECK IN (`'draft'`, `'published'`), `published_at`, timestamps.  \n   - Indexes: `idx_themes_store_id`, `idx_themes_store_status`.  \n\n2. `theme_files`  \n   - Columns: `id`, `theme_id` FK → `themes(id)` ON DELETE CASCADE, `path` TEXT NOT NULL, `storage_key`, `sha256`, `byte_size` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: unique `idx_theme_files_theme_path` (`theme_id`, `path`), `idx_theme_files_theme_id`.  \n\n3. `theme_settings`  \n   - Columns: `theme_id` PK/FK → `themes(id)` ON DELETE CASCADE, `settings_json` TEXT NOT NULL DEFAULT `'{}'`, `updated_at` TEXT NULL.  \n\n4. `pages`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `title`, `handle` TEXT UNIQUE per store, `body_html`, `status` TEXT NOT NULL DEFAULT `'draft'` CHECK IN (`'draft'`, `'published'`, `'archived'`), `published_at`, timestamps.  \n   - Indexes: unique `idx_pages_store_handle`, `idx_pages_store_id`, `idx_pages_store_status`.  \n\n5. `navigation_menus`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `handle` TEXT UNIQUE per store, `title`, timestamps.  \n   - Indexes: unique `idx_navigation_menus_store_handle`, `idx_navigation_menus_store_id`.  \n\n6. `navigation_items`  \n   - Columns: `id`, `menu_id` FK → `navigation_menus(id)` ON DELETE CASCADE, `type` TEXT NOT NULL DEFAULT `'link'` CHECK IN (`'link'`, `'page'`, `'collection'`, `'product'`), `label`, `url`, `resource_id` INTEGER NULL, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_navigation_items_menu_id`, `idx_navigation_items_menu_position`.  \n\n**Phase 4 – Cart, Checkout, Discounts, Shipping, Taxes**\n\n1. `carts`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `currency` TEXT NOT NULL DEFAULT `'USD'`, `cart_version` INTEGER NOT NULL DEFAULT `1`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'converted'`, `'abandoned'`), timestamps.  \n   - Indexes: `idx_carts_store_id`, `idx_carts_customer_id`, `idx_carts_store_status`.  \n\n2. `cart_lines`  \n   - Columns: `id`, `cart_id` FK → `carts(id)` ON DELETE CASCADE, `variant_id` FK → `product_variants(id)` ON DELETE CASCADE, `quantity` INTEGER NOT NULL DEFAULT `1`, `unit_price_amount`, `line_subtotal_amount`, `line_discount_amount`, `line_total_amount` (all INTEGER NOT NULL DEFAULT `0`).  \n   - Indexes: `idx_cart_lines_cart_id`, unique `idx_cart_lines_cart_variant` (`cart_id`, `variant_id`).  \n\n3. `checkouts`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `cart_id` FK → `carts(id)` ON DELETE CASCADE, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `status` TEXT NOT NULL DEFAULT `'started'` CHECK IN (`'started'`, `'addressed'`, `'shipping_selected'`, `'payment_selected'`, `'completed'`, `'expired'`), `payment_method` TEXT NULL CHECK IN (`'credit_card'`, `'paypal'`, `'bank_transfer'`), `email`, `shipping_address_json`, `billing_address_json`, `shipping_method_id` INTEGER NULL, `discount_code`, `tax_provider_snapshot_json`, `totals_json`, `expires_at`, timestamps.  \n   - Indexes: `idx_checkouts_store_id`, `idx_checkouts_cart_id`, `idx_checkouts_customer_id`, `idx_checkouts_status`, `idx_checkouts_expires_at`.  \n\n4. `shipping_zones`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `name`, `countries_json` TEXT NOT NULL DEFAULT `'[]'`, `regions_json` TEXT NOT NULL DEFAULT `'[]'`.  \n   - Index: `idx_shipping_zones_store_id`.  \n\n5. `shipping_rates`  \n   - Columns: `id`, `zone_id` FK → `shipping_zones(id)` ON DELETE CASCADE, `name`, `type` TEXT NOT NULL DEFAULT `'flat'` CHECK IN (`'flat'`, `'weight'`, `'price'`, `'carrier'`), `config_json` TEXT NOT NULL DEFAULT `'{}'`, `is_active` BOOLEAN NOT NULL DEFAULT `1`.  \n   - Indexes: `idx_shipping_rates_zone_id`, `idx_shipping_rates_zone_active`.  \n\n6. `tax_settings`  \n   - Columns: `store_id` PK/FK → `stores(id)` ON DELETE CASCADE, `mode` TEXT NOT NULL DEFAULT `'manual'` CHECK IN (`'manual'`, `'provider'`), `provider` TEXT NOT NULL DEFAULT `'none'` CHECK IN (`'stripe_tax'`, `'none'`), `prices_include_tax` BOOLEAN NOT NULL DEFAULT `0`, `config_json` TEXT NOT NULL DEFAULT `'{}'`.  \n\n7. `discounts`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `type` TEXT NOT NULL DEFAULT `'code'` CHECK IN (`'code'`, `'automatic'`), `code` TEXT NULL UNIQUE per store, `value_type` TEXT NOT NULL CHECK IN (`'fixed'`, `'percent'`, `'free_shipping'`), `value_amount` INTEGER NOT NULL DEFAULT `0`, `starts_at` TEXT NOT NULL, `ends_at` TEXT NULL, `usage_limit` INTEGER NULL, `usage_count` INTEGER NOT NULL DEFAULT `0`, `rules_json` TEXT NOT NULL DEFAULT `'{}'`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'draft'`, `'active'`, `'expired'`, `'disabled'`), timestamps.  \n   - Indexes: unique `idx_discounts_store_code` (`store_id`, `code`), `idx_discounts_store_id`, `idx_discounts_store_status`, `idx_discounts_store_type`.  \n\n**Phase 5 – Payments, Orders, Fulfillment, Customers**\n\n1. `customers`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `email` TEXT NOT NULL, `password_hash` TEXT NULL, `name` TEXT NULL, `marketing_opt_in` BOOLEAN NOT NULL DEFAULT `0`, timestamps.  \n   - Indexes: unique `idx_customers_store_email` (`store_id`, `email`), `idx_customers_store_id`.  \n\n2. `customer_addresses`  \n   - Columns: `id`, `customer_id` FK → `customers(id)` ON DELETE CASCADE, `label`, `address_json` TEXT NOT NULL DEFAULT `'{}'`, `is_default` BOOLEAN NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_customer_addresses_customer_id`, `idx_customer_addresses_default`.  \n\n3. `orders`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `order_number` TEXT NOT NULL, `payment_method` TEXT NOT NULL CHECK IN (`'credit_card'`, `'paypal'`, `'bank_transfer'`), `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'paid'`, `'fulfilled'`, `'cancelled'`, `'refunded'`), `financial_status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'authorized'`, `'paid'`, `'partially_refunded'`, `'refunded'`, `'voided'`), `fulfillment_status` TEXT NOT NULL DEFAULT `'unfulfilled'` CHECK IN (`'unfulfilled'`, `'partial'`, `'fulfilled'`), `currency` TEXT NOT NULL DEFAULT `'USD'`, `_amount` columns (`subtotal_amount`, `discount_amount`, `shipping_amount`, `tax_amount`, `total_amount`) all INTEGER NOT NULL DEFAULT `0`, `email`, `billing_address_json`, `shipping_address_json`, `placed_at`, timestamps.  \n   - Indexes: unique `idx_orders_store_order_number` (`store_id`, `order_number`), `idx_orders_store_id`, `idx_orders_customer_id`, `idx_orders_store_status`, `idx_orders_store_financial`, `idx_orders_store_fulfillment`, `idx_orders_placed_at`.  \n\n4. `order_lines`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `product_id` FK → `products(id)` ON DELETE SET NULL, `variant_id` FK → `product_variants(id)` ON DELETE SET NULL, `title_snapshot` TEXT NOT NULL, `sku_snapshot` TEXT NULL, `quantity` INTEGER NOT NULL DEFAULT `1`, `unit_price_amount`, `total_amount` INTEGER NOT NULL DEFAULT `0`, `tax_lines_json` TEXT NOT NULL DEFAULT `'[]'`, `discount_allocations_json` TEXT NOT NULL DEFAULT `'[]'`.  \n   - Indexes: `idx_order_lines_order_id`, `idx_order_lines_product_id`, `idx_order_lines_variant_id`.  \n\n5. `payments`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `provider` TEXT NOT NULL DEFAULT `'mock'` CHECK IN (`'mock'`), `method` TEXT NOT NULL CHECK IN (`'credit_card'`, `'paypal'`, `'bank_transfer'`), `provider_payment_id`, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'captured'`, `'failed'`, `'refunded'`), `amount` INTEGER NOT NULL DEFAULT `0`, `currency` TEXT NOT NULL DEFAULT `'USD'`, `raw_json_encrypted` TEXT NULL, `created_at`.  \n   - Indexes: `idx_payments_order_id`, `idx_payments_provider_id` (`provider`, `provider_payment_id`), `idx_payments_method`, `idx_payments_status`.  \n\n6. `refunds`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `payment_id` FK → `payments(id)` ON DELETE CASCADE, `amount` INTEGER NOT NULL DEFAULT `0`, `reason`, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'processed'`, `'failed'`), `provider_refund_id`, `created_at`.  \n   - Indexes: `idx_refunds_order_id`, `idx_refunds_payment_id`, `idx_refunds_status`.  \n\n7. `fulfillments`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'shipped'`, `'delivered'`), `tracking_company`, `tracking_number`, `tracking_url`, `shipped_at`, `created_at`.  \n   - Indexes: `idx_fulfillments_order_id`, `idx_fulfillments_status`, `idx_fulfillments_tracking` (`tracking_company`, `tracking_number`).  \n\n8. `fulfillment_lines`  \n   - Columns: `id`, `fulfillment_id` FK → `fulfillments(id)` ON DELETE CASCADE, `order_line_id` FK → `order_lines(id)` ON DELETE CASCADE, `quantity` INTEGER NOT NULL DEFAULT `1`.  \n   - Indexes: `idx_fulfillment_lines_fulfillment_id`, unique `idx_fulfillment_lines_fulfillment_order_line` (`fulfillment_id`, `order_line_id`).  \n\n**Phase 7 – Analytics**\n\n1. `analytics_events`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `type` TEXT NOT NULL, `session_id`, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `properties_json` TEXT NOT NULL DEFAULT `'{}'`, `client_event_id` TEXT NULL, `occurred_at` TEXT NULL, `created_at` TEXT NULL.  \n   - Indexes: `idx_analytics_events_store_id`, `idx_analytics_events_store_type`, `idx_analytics_events_store_created`, `idx_analytics_events_session`, `idx_analytics_events_customer`, unique `idx_analytics_events_client_event` (`store_id`, `client_event_id`).  \n\n2. `analytics_daily`  \n   - Columns: `store_id` FK → `stores(id)` ON DELETE CASCADE, `date` TEXT NOT NULL (YYYY-MM-DD), `orders_count`, `revenue_amount`, `aov_amount`, `visits_count`, `add_to_cart_count`, `checkout_started_count`, `checkout_completed_count` (all INTEGER NOT NULL DEFAULT `0`).  \n   - Primary key: composite (`store_id`, `date`).  \n   - Index: `idx_analytics_daily_store_date`.  \n\n**Phase 10 – Apps & Webhooks**\n\n1. `apps`  \n   - Columns: `id`, `name`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'disabled'`), `created_at` TEXT NULL.  \n   - Index: `idx_apps_status`.  \n\n2. `app_installations`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `app_id` FK → `apps(id)` ON DELETE CASCADE, `scopes_json` TEXT NOT NULL DEFAULT `'[]'`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'suspended'`, `'uninstalled'`), `installed_at` TEXT NULL.  \n   - Indexes: unique `idx_app_installations_store_app` (`store_id`, `app_id`), `idx_app_installations_store_id`, `idx_app_installations_app_id`.  \n\n3. `oauth_clients`  \n   - Columns: `id`, `app_id` FK → `apps(id)` ON DELETE CASCADE, `client_id` TEXT NOT NULL UNIQUE, `client_secret_encrypted` TEXT NOT NULL, `redirect_uris_json` TEXT NOT NULL DEFAULT `'[]'`.  \n   - Indexes: unique `idx_oauth_clients_client_id`, `idx_oauth_clients_app_id`.  \n\n4. `oauth_tokens`  \n   - Columns: `id`, `installation_id` FK → `app_installations(id)` ON DELETE CASCADE, `access_token_hash` TEXT NOT NULL UNIQUE, `refresh_token_hash` TEXT NULL, `expires_at` TEXT NOT NULL.  \n   - Indexes: `idx_oauth_tokens_installation_id`, unique `idx_oauth_tokens_access_hash`, `idx_oauth_tokens_expires_at`.  \n\n5. `webhook_subscriptions`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `app_installation_id` FK → `app_installations(id)` ON DELETE CASCADE, `event_type` TEXT NOT NULL, `target_url` TEXT NOT NULL, `signing_secret_encrypted` TEXT NOT NULL, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'paused'`, `'disabled'`).  \n   - Indexes: `idx_webhook_subscriptions_store_id`, `idx_webhook_subscriptions_store_event`, `idx_webhook_subscriptions_installation`.  \n\n6. `webhook_deliveries`  \n   - Columns: `id`, `subscription_id` FK → `webhook_subscriptions(id)` ON DELETE CASCADE, `event_id` TEXT NOT NULL, `attempt_count` INTEGER NOT NULL DEFAULT `1`, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'success'`, `'failed'`), `last_attempt_at`, `response_code` INTEGER NULL, `response_body_snippet` TEXT NULL.  \n   - Indexes: `idx_webhook_deliveries_subscription_id`, `idx_webhook_deliveries_event_id`, `idx_webhook_deliveries_status`, `idx_webhook_deliveries_last_attempt`.  \n\n**Phase 8 – Search (per Phase 4/5 timeline)**\n\n1. `search_settings`  \n   - Columns: `store_id` PK/FK → `stores(id)` ON DELETE CASCADE, `synonyms_json` TEXT NOT NULL DEFAULT `'[]'`, `stop_words_json` TEXT NOT NULL DEFAULT `'[]'`, `updated_at` TEXT NULL.  \n\n2. `search_queries`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `query` TEXT NOT NULL, `filters_json` TEXT NULL, `results_count` INTEGER NOT NULL DEFAULT `0`, `created_at` TEXT NULL.  \n   - Indexes: `idx_search_queries_store_id`, `idx_search_queries_store_created`, `idx_search_queries_store_query`.  \n\n**Enums summary (checked via `CHECK` constraints on each table):**\n\n- Store-related: `StoreStatus` (`active`, `suspended`), `StoreDomainType` (`storefront`, `admin`, `api`), `StoreDomainTlsMode` (`managed`, `bring_your_own`), `StoreUserRole` (`owner`, `admin`, `staff`, `support`).  \n- Product/catalog: `ProductStatus` (`draft`, `active`, `archived`), `VariantStatus` (`active`, `archived`), `CollectionType` (`manual`, `automated`), `CollectionStatus` (`draft`, `active`, `archived`), `MediaType` (`image`, `video`), `MediaStatus` (`processing`, `ready`, `failed`), `InventoryPolicy` (`deny`, `continue`).  \n- Themes/navigation: `ThemeStatus` (`draft`, `published`), `PageStatus` (`draft`, `published`, `archived`), `NavigationItemType` (`link`, `page`, `collection`, `product`).  \n- Cart/checkout/etc: `CartStatus` (`active`, `converted`, `abandoned`), `CheckoutStatus` plus `PaymentMethod` etc noted above, `ShippingRateType` (`flat`, `weight`, `price`, `carrier`), `TaxMode` (`manual`, `provider`), `TaxProvider` (`stripe_tax`, `none`), `DiscountType` (`code`, `automatic`), `DiscountValueType` (`fixed`, `percent`, `free_shipping`).  \n- Orders: `OrderStatus`, `FinancialStatus`, `FulfillmentStatus`, `PaymentStatus`, `RefundStatus`, `FulfillmentStatus`, `WebhookStatus`, etc enumerations specified on each table.  \n\nThis blueprint gives the precise column definitions, constraints, indexes, foreign-key references, enumerations, and the sequential order in which the migrations should be created."}},"timed_out":false}


# Collaboration Mode: Default

You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.

Your active mode changes only when new developer instructions with a different `<collaboration_mode>...</collaboration_mode>` change it; user requests or tool descriptions do not change mode by themselves. Known mode names are Default and Plan.

## request_user_input availability

The `request_user_input` tool is unavailable in Default mode. If you call it while in Default mode, it will return an error.

If a decision is necessary and cannot be discovered from local context, ask the user directly. However, in Default mode you should strongly prefer executing the user's request rather than stopping to ask questions.

Summary: auto

# Shop

Self-contained e-commerce platform with multi-tenant support, product catalog, cart, checkout, payments (mock PSP), order management, customer accounts, and an admin panel.

## Tech Stack

- PHP 8.4 / Laravel 12
- Livewire v4 / Flux UI Free v2
- Tailwind CSS v4
- SQLite
- Pest v4

## Specs

The complete specification is in `specs/`. Start with `specs/09-IMPLEMENTATION-ROADMAP.md` for the build order, then reference the other files as needed:

- `specs/01-DATABASE-SCHEMA.md` - Database schema
- `specs/02-API-ROUTES.md` - Routes and API endpoints
- `specs/03-ADMIN-UI.md` - Admin panel
- `specs/04-STOREFRONT-UI.md` - Storefront
- `specs/05-BUSINESS-LOGIC.md` - Business logic
- `specs/06-AUTH-AND-SECURITY.md` - Auth and security
- `specs/07-SEEDERS-AND-TEST-DATA.md` - Seeders and test data
- `specs/08-PLAYWRIGHT-E2E-PLAN.md` - E2E browser tests
- `specs/09-IMPLEMENTATION-ROADMAP.md` - Implementation roadmap

## Skills
A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.
### Available skills
- skill-creator: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations. (file: /Users/fabianwesner/.codex/skills/.system/skill-creator/SKILL.md)
- skill-installer: Install Codex skills into $CODEX_HOME/skills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos). (file: /Users/fabianwesner/.codex/skills/.system/skill-installer/SKILL.md)
### How to use skills
- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
- How to use a skill (progressive disclosure):
  1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.
  2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed.
  3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.
  4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.
  5) If `assets/` or templates exist, reuse them instead of recreating from scratch.
- Coordination and sequencing:
  - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
  - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
- Context hygiene:
  - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
  - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.
  - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.


**Noting potential lingering process**


Output: {"status":{"019c5b58-fbe1-7342-85dc-6a22cde32fda":{"completed":"Solution: The requested matrix is broken into four sections (Web routes, Storefront REST API, Admin REST API, and OAuth/App endpoints) with each entry linking the path/method to the middleware/auth requirements, the success-response expectations defined in `specs/02-API-ROUTES.md`, and the Playwright suites/tests from `specs/08-PLAYWRIGHT-E2E-PLAN.md` that exercise the UI surface built on top of those routes so you can trace coverage.\n\n**Web Routes Matrix**\n| Route | Method | Auth / Middleware | Success expectations | Playwright coverage |\n|---|---|---|---|---|\n| `/` | GET | `web`, `store.resolve` | `Storefront\\Home` renders “Acme Fashion”, expected HTTP 200 and no JS errors | Suite 1.1 (Smoke) |\n| `/collections` | GET | `web`, `store.resolve` | `Storefront\\Collections\\Index` shows “Collections”, product tiles | Suite 1.2, 1.9; Suite 7 (Browsing) tests collection navigation |\n| `/collections/{handle}` | GET | `web`, `store.resolve` | `Storefront\\Collections\\Show` shows collection title, filters | Suite 1.2 & 7.4 |\n| `/products/{handle}` | GET | `web`, `store.resolve` | `Storefront\\Products\\Show` shows title, price, variant selectors | Suite 1.3; Suites 7 & 8 add-to-cart flow |\n| `/cart` | GET | `web`, `store.resolve` | `Storefront\\Cart\\Show` with line items, totals and discount controls | Suites 1.4, 8.2–8.12 |\n| `/search` | GET | `web`, `store.resolve` | `Storefront\\Search\\Index` shows query, results, sorts | Suite 1.8; Suite 7 (search) |\n| `/pages/{handle}` | GET | `web`, `store.resolve` | `Storefront\\Pages\\Show` renders page content (e.g., about) | Suites 1.7, 7.6 |\n| `/account/login` | GET/POST | `web` (no auth) | `Storefront\\Account\\Auth\\Login` renders form and throttles `login` tier | Suite 1.5 (page load); Suite 10 covers login validation |\n| `/account/register` | GET | `web` | `Storefront\\Account\\Auth\\Register` | Suite 10 (registration) |\n| `/forgot-password`, `/reset-password/{token}` | GET/POST | `web` | Livewire auth flows | Covered indirectly by Suite 10 mail flows |\n| `/account` | GET | `web`, `store.resolve`, `auth:customer` | `Storefront\\Account\\Dashboard` with orders/address shortcuts | Suite 10 (dashboard) |\n| `/account/orders` | GET | same as above | `Storefront\\Account\\Orders\\Index` shows order list | Suite 10 (history) |\n| `/account/orders/{orderNumber}` | GET | same as above | `Storefront\\Account\\Orders\\Show` | Suite 10 |\n| `/account/addresses` | GET/POST/PUT | `auth:customer` | Address book management view | Suite 10 |\n| `/checkout/{checkoutId}` | GET | `web`, `store.resolve` | Multi-step checkout UI `Storefront\\Checkout\\Show` that orchestrates API calls | Suite 9 (full flow) |\n| `/checkout/{checkoutId}/confirmation` | GET | `web`, `store.resolve` | Confirmation page showing order summary | Suite 9.1, 9.9, 9.10 |\n\n**Admin Web Routes (Livewire)**\n| Route | Method | Auth / Middleware | Expectations | Playwright coverage |\n|---|---|---|---|---|\n| `/admin/login`, `/admin/forgot-password`, `/admin/reset-password` | GET/POST | `web` (no auth) | Livewire auth views, rate-limited to `login` tier | Suite 2.1–2.4 |\n| `/admin` | GET | `web`, `auth`, `verified`, `store.resolve`, `role.check` | `Admin\\Dashboard` with KPIs (HTTP 200, no JS errors) | Suite 2.1 (after login) |\n| `/admin/products` and `/admin/products/*` | GET | same as above | Product index/create/edit forms (`Admin\\Products\\*`) | Suite 3 (Product Management) |\n| `/admin/orders` and `/admin/orders/{order}` | GET | same | `Admin\\Orders\\Index`/`Show` | Suite 4 |\n| `/admin/customers`, `/admin/customers/{customer}` | GET | same | Customer list/detail | Suite 16 |\n| `/admin/discounts` | GET | same | Discounts index/form | Suite 5 |\n| `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes` | GET | same | Settings tabs for shipping/taxes | Suite 6 |\n| `/admin/collections`, `/admin/collections/{collection}` | GET | same | Collection management | Suite 15 |\n| `/admin/pages`, `/admin/pages/{page}` | GET | same | CMS page CRUD | Suite 17 |\n| `/admin/analytics`, `/admin/search/settings`, `/admin/themes`, `/admin/apps`, `/admin/navigations`, `/admin/developers` | GET | same | Various admin dashboards and editors | Suites 6 (settings/analytics), 18 (analytics), 2 (navigation/others via dashboard nav) |\n| `/admin/inventory`, `/admin/shipping`, `/admin/themes/{theme}/editor`, `/admin/apps/{installation}` | GET | same | Inventory/collector & app-specific views | Suite 6 (shipping), internal coverage via Suites 3–18 where applicable |\n\n**Storefront REST API Matrix (`/api/storefront/v1`)**\n| Endpoint | Method | Auth / Middleware | Success response expectation | UI coverage |\n|---|---|---|---|---|\n| `/api/storefront/v1/carts` | POST | `store.resolve`, rate limit `api.storefront`, no auth | `201 Created` cart object (id, lines, totals) | Suite 8 (cart creation when adding to cart) |\n| `/api/storefront/v1/carts/{cartId}` | GET | same | `200 OK` cart snapshot with lines/totals | Suite 8 (cart view) |\n| `/api/storefront/v1/carts/{cartId}/lines` | POST | same | `201` cart with new line; errors `404`, `409`, `422` | Suite 8 (adding/line updates) |\n| `/api/storefront/v1/carts/{cartId}/lines/{lineId}` | PUT/DELETE | same | `200` updated cart; `409` for version conflicts | Suite 8 (quantity adjustments, removals) |\n| `/api/storefront/v1/checkouts` | POST | `checkout` tier, no auth | `201` checkout object (status `started`, lines, totals) | Suite 9 (start checkout) |\n| `/api/storefront/v1/checkouts/{id}` | GET | `checkout` tier | `200` current checkout state; `410` if expired | Suite 9 |\n| `/api/storefront/v1/checkouts/{id}/address` | PUT | same | `200` checkout with address, `status=addressed`, shipping methods populated | Suite 9.1–9.7 |\n| `/api/storefront/v1/checkouts/{id}/shipping-method` | PUT | same | `200`, `status=shipping_selected`, totals + tax snapshot | Suite 9.1–9.3 |\n| `/api/storefront/v1/checkouts/{id}/payment-method` | PUT | same | `200`, `status=payment_selected` | Suite 9 (payment selection) |\n| `/api/storefront/v1/checkouts/{id}/apply-discount` | POST | same | `200` totals with discount data; errors `400/404/410/422` | Suites 8.6–8.12 and 9.4 |\n| `/api/storefront/v1/checkouts/{id}/discount` | DELETE | same | `200` totals recalculated without discount | Suite 9 discount removal path |\n| `/api/storefront/v1/checkouts/{id}/pay` | POST | same | `200` with order info (`order.status`, `payment_method`, optional bank transfer instructions); `422` for declines (`card_declined`, `insufficient_funds`), `409` if wrong state | Suite 9.1 (credit card success), 9.9 (PayPal), 9.10 (bank transfer), 9.11–9.12 (declines) |\n| `/api/storefront/v1/orders/{orderNumber}` | GET | `token` parameter (HMAC) | `200` order summary (status, totals, addresses) | Suite 9 confirmation page; Suite 10 order detail |\n| `/api/storefront/v1/search` | GET | `search` tier | `200` hits + facets + pagination; `422` for malformed filters | Suite 7 (search results) |\n| `/api/storefront/v1/search/suggest` | GET | `search` tier | `200` autocomplete suggestions | Suite 7 (search typing) |\n| `/api/storefront/v1/analytics/events` | POST | `analytics` tier | `202 Accepted` with `accepted`/`rejected` counts | Logged indirectly via JS instrumentation tested in Suite 1 smoke paths |\n\n**Admin REST API Matrix (`/api/admin/v1`)**\n| Endpoint(s) | Method | Auth / Middleware | Success response | Playwright coverage |\n|---|---|---|---|---|\n| `/platform/organizations` | POST | `auth:sanctum`, scope `manage-platform` | `201` organization payload | Not yet covered by UI tests (future integration) |\n| `/platform/stores` | POST | same | `201` store payload | Not yet covered |\n| `/stores/{storeId}/invites` | POST | same | `201` invite details | Not yet covered |\n| `/stores/{storeId}/me` | GET | Sanctum token | `200` membership + permissions list | Used as part of admin session initialization (Suite 2) |\n| `/stores/{storeId}/products[/{productId}]` | GET/POST/PUT/DELETE + `/media/presign-upload` | Sanctum token with `read/write-products`, `rate limit api.admin` | JSON product resources or presigned URL; consistent `data` wrapper | Suite 3 (Product CRUD) |\n| `/stores/{storeId}/collections[/{id}]` | GET/POST/PUT/DELETE | `read/write-collections` | `200`/`201` collection payload | Suite 15 (Collection Management) |\n| `/stores/{storeId}/orders[/{orderId}]` | GET + fulfillment/refund subroutes | `read-orders`/`write-orders` | `200` order + `201` fulfillments/refunds | Suite 4 (Order flow) |\n| `/stores/{storeId}/discounts[/{id}]` | GET/POST/PUT/DELETE | `read/write-discounts` | `200`/`201` discount payload | Suite 5 (Discount Management) |\n| `/stores/{storeId}/shipping/zones[/{zoneId}]` | GET/POST/PUT + `/rates` | `read/write-settings` | `200` zone with rates, `201` rate | Suite 6 (Settings) |\n| `/stores/{storeId}/tax/settings` | GET/PUT | `read/write-settings` | `200` tax config payload | Suite 6 |\n| `/stores/{storeId}/themes`, `/themes/{themeId}/publish`, `/themes/{themeId}/settings` | POST/PUT | `write-themes` | `201` theme metadata, `200` publish/settings updates | Suite 6 (themes part of settings navigation) |\n| `/stores/{storeId}/pages[/{pageId}]` | GET/POST/PUT/DELETE | `read/write-content` | `200`/`201` page payload | Suite 17 (Page Management) |\n| `/stores/{storeId}/search/reindex` | POST | `write-settings` | `202` queued job | Suite 6 touches search settings (manual coverage) |\n| `/stores/{storeId}/search/status` | GET | `read-settings` | `200` status data | Suite 6 |\n| `/stores/{storeId}/analytics/summary` | GET | `read-analytics` | `200` summary with totals/daily/top_products | Suite 18 (Analytics Dashboard) |\n| `/stores/{storeId}/exports/orders` | POST | `read-orders` | `202` export job | Suite 18 export UI path |\n| `/stores/{storeId}/exports/{exportId}` | GET | `read-orders` | `200` download metadata | Suite 18 poll page |\n| `/stores/{storeId}/customers` | GET | `read-customers` | `200` customer list | Suite 16 (Customer Management) |\n\n**OAuth and App API Matrix**\n| Endpoint | Method | Auth / Middleware | Expected behavior | Playwright coverage |\n|---|---|---|---|---|\n| `/oauth/authorize` | GET | `web`, `auth` | Shows OAuth approval prompt; future (501) | Not covered (deferred) |\n| `/oauth/token` | POST | Public | Issues tokens (authorization code or refresh) | Not covered yet |\n| `/api/apps/v1/stores/{storeId}/products` | GET | OAuth token scope `read-products` | Same schema as admin product list | Not covered; reserved for future app integrations |\n| `/api/apps/v1/stores/{storeId}/orders` | GET | Scope `read-orders` | Order list | Future coverage |\n| `/api/apps/v1/stores/{storeId}/customers` | GET | Scope `read-customers` | Customer list | Future coverage |\n\n**Notes:** \n- All frontend endpoints are suffixed with the middleware stack in the spec (e.g., `store.resolve`, `auth:customer`, `auth` + `verified`, `role.check`), so the matrix references those exact requirements.\n- The Storefront API replies include traceable fields (cart lines/totals, checkout states, order details); the Playwright Cart/Checkout suites drive those flows to validate the expected `200/201` responses and business logic errors (discount rejection, payment failure codes).\n- Admin UI routes/rendered APIs are exercised across Suites 2–6 and 15–18, ensuring authentication, CRUD, and analytics dashboards all return the expected Livewire views.\n- Rate limits from Section 7 apply (e.g., `login`, `api.storefront`, `checkout`, `search`, `analytics`, `api.admin`). Error structures (422 validation, 403 auth, 409 conflict, 400 business logic, 500 server) are standardized across APIs.\n\nTests were not run in this turn."}},"timed_out":false}


Summary: auto


**Closing agents to free slots**


I now have complete spec extraction for schema, routes, UI, and business logic. Next step is execution: I’ll create a concrete progress tracker, then spin up worker agents for isolated implementation slices (foundation/schema, catalog+cart services, UI/routes/tests), and integrate + commit each iteration.


**Closing agent connections**


Output: {"status":{"completed":"**General conventions**\n\n- Every table has `id` INTEGER PRIMARY KEY except junction tables/pivots where specified composite keys apply.  \n- Monetary amounts are stored as INTEGER minor units (cents) with `_amount` suffix.  \n- Booleans are INTEGER NOT NULL DEFAULT `0`.  \n- JSON data is stored as TEXT (Laravel casts).  \n- Timestamps are TEXT (ISO-8601); soft deletes only when called out.  \n- Enumerations are TEXT checked via `CHECK` constraints (see each table).  \n\n**Creation order (per Phase 1→10 in `specs/09-IMPLEMENTATION-ROADMAP.md`)**\n\n1. **Phase 1 (Foundation)** – `organizations`, `stores`, `store_domains`, augment `users`, `store_users`, `store_settings`.  \n2. **Phase 2 (Catalog)** – `products`, `product_options`, `product_option_values`, `product_variants`, `variant_option_values`, `inventory_items`, `collections`, `collection_products`, `product_media`.  \n3. **Phase 3 (Themes & Layout)** – `themes`, `theme_files`, `theme_settings`, `pages`, `navigation_menus`, `navigation_items`.  \n4. **Phase 4 (Cart/Checkout/Discounts/Shipping/Taxes)** – `carts`, `cart_lines`, `checkouts`, `shipping_zones`, `shipping_rates`, `tax_settings`, `discounts`.  \n5. **Phase 5 (Payments/Orders/Fulfillment)** – `customers`, `customer_addresses`, `orders`, `order_lines`, `payments`, `refunds`, `fulfillments`, `fulfillment_lines`.  \n6. **Phase 7 (Analytics)** – `analytics_events`, `analytics_daily`.  \n7. **Phase 10 (Apps & Webhooks)** – `apps`, `app_installations`, `oauth_clients`, `oauth_tokens`, `webhook_subscriptions`, `webhook_deliveries`.  \n8. **Phase 4/5 dependencies** also imply `search_settings`, `search_queries` (Phase 8) feed off catalog data; create alongside Phase 4/5 as per roadmap.  \n\n**Phase 1 – Foundation**\n\n1. `organizations`  \n   - Columns: `id` PK, `name` TEXT NOT NULL, `billing_email` TEXT NOT NULL, `created_at` TEXT NULL, `updated_at` TEXT NULL.  \n   - Indexes: `idx_organizations_billing_email` (`billing_email`).  \n\n2. `stores`  \n   - Columns: `id`, `organization_id` INTEGER NOT NULL FK → `organizations(id)` ON DELETE CASCADE, `name`, `handle` TEXT UNIQUE, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'suspended'`), `default_currency` TEXT NOT NULL DEFAULT `'USD'`, `default_locale` TEXT NOT NULL DEFAULT `'en'`, `timezone` TEXT NOT NULL DEFAULT `'UTC'`, timestamps.  \n   - Indexes: unique `idx_stores_handle` (`handle`), `idx_stores_organization_id`, `idx_stores_status`.  \n\n3. `store_domains`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `hostname` TEXT UNIQUE, `type` TEXT NOT NULL DEFAULT `'storefront'` CHECK IN (`'storefront'`, `'admin'`, `'api'`), `is_primary` BOOLEAN DEFAULT `0`, `tls_mode` TEXT NOT NULL DEFAULT `'managed'` CHECK IN (`'managed'`, `'bring_your_own'`), `created_at` TEXT NULL.  \n   - Indexes: unique `idx_store_domains_hostname`, `idx_store_domains_store_id`, composite `idx_store_domains_store_primary` (`store_id`, `is_primary`).  \n\n4. `users` (default Laravel users migration augmented)  \n   - Columns: `id`, `email` TEXT UNIQUE, `password_hash` TEXT NOT NULL (maps to Laravel `password`), `name`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'disabled'`), `email_verified_at`, `last_login_at`, `two_factor_secret`, `two_factor_recovery_codes`, `two_factor_confirmed_at`, `remember_token`, timestamps.  \n   - Indexes: `idx_users_email` unique, `idx_users_status`.  \n\n5. `store_users` (pivot)  \n   - Columns: `store_id` FK → `stores(id)` ON DELETE CASCADE, `user_id` FK → `users(id)` ON DELETE CASCADE, `role` TEXT NOT NULL DEFAULT `'staff'` CHECK IN (`'owner'`, `'admin'`, `'staff'`, `'support'`), `created_at` TEXT NULL.  \n   - Primary key: composite (`store_id`, `user_id`).  \n   - Indexes: `idx_store_users_user_id`, `idx_store_users_role` (`store_id`, `role`).  \n\n6. `store_settings`  \n   - Columns: `store_id` PK/FK → `stores(id)` ON DELETE CASCADE, `settings_json` TEXT NOT NULL DEFAULT `'{}'`, `updated_at` TEXT NULL.  \n\n**Phase 2 – Catalog**\n\n1. `products`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `title`, `handle` TEXT (unique per store), `status` TEXT NOT NULL DEFAULT `'draft'` CHECK IN (`'draft'`, `'active'`, `'archived'`), `description_html`, `vendor`, `product_type`, `tags` TEXT NOT NULL DEFAULT `'[]'`, `published_at`, timestamps.  \n   - Indexes: `idx_products_store_handle` unique (`store_id`, `handle`), `idx_products_store_id`, `idx_products_store_status`, `idx_products_published_at`, `idx_products_vendor`, `idx_products_product_type`.  \n\n2. `product_options`  \n   - Columns: `id`, `product_id` FK → `products(id)` ON DELETE CASCADE, `name` TEXT NOT NULL, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_product_options_product_id`, unique `idx_product_options_product_position` (`product_id`, `position`).  \n\n3. `product_option_values`  \n   - Columns: `id`, `product_option_id` FK → `product_options(id)` ON DELETE CASCADE, `value`, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_product_option_values_option_id`, unique `idx_product_option_values_option_position` (`product_option_id`, `position`).  \n\n4. `product_variants`  \n   - Columns: `id`, `product_id` FK → `products(id)` ON DELETE CASCADE, `sku`, `barcode`, `price_amount` INTEGER NOT NULL DEFAULT `0`, `compare_at_amount` INTEGER NULL, `currency` TEXT NOT NULL DEFAULT `'USD'`, `weight_g` INTEGER NULL, `requires_shipping` BOOLEAN NOT NULL DEFAULT `1`, `is_default` BOOLEAN NOT NULL DEFAULT `0`, `position` INTEGER NOT NULL DEFAULT `0`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'archived'`), timestamps.  \n   - Indexes: `idx_product_variants_product_id`, `idx_product_variants_sku`, `idx_product_variants_barcode`, `idx_product_variants_product_position`, `idx_product_variants_product_default`.  \n\n5. `variant_option_values` (junction)  \n   - Columns: `variant_id` FK → `product_variants(id)` ON DELETE CASCADE, `product_option_value_id` FK → `product_option_values(id)` ON DELETE CASCADE.  \n   - Primary key: (`variant_id`, `product_option_value_id`).  \n   - Index: `idx_variant_option_values_value_id` (`product_option_value_id`).  \n\n6. `inventory_items`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `variant_id` FK → `product_variants(id)` ON DELETE CASCADE UNIQUE, `quantity_on_hand` INTEGER NOT NULL DEFAULT `0`, `quantity_reserved` INTEGER NOT NULL DEFAULT `0`, `policy` TEXT NOT NULL DEFAULT `'deny'` CHECK IN (`'deny'`, `'continue'`).  \n   - Indexes: unique `idx_inventory_items_variant_id`, `idx_inventory_items_store_id`.  \n\n7. `collections`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `title`, `handle` TEXT UNIQUE per store, `description_html`, `type` TEXT NOT NULL DEFAULT `'manual'` CHECK IN (`'manual'`, `'automated'`), `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'draft'`, `'active'`, `'archived'`), timestamps.  \n   - Indexes: unique `idx_collections_store_handle`, `idx_collections_store_id`, `idx_collections_store_status`.  \n\n8. `collection_products` (pivot)  \n   - Columns: `collection_id` FK → `collections(id)` ON DELETE CASCADE, `product_id` FK → `products(id)` ON DELETE CASCADE, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Primary key: (`collection_id`, `product_id`).  \n   - Indexes: `idx_collection_products_product_id`, `idx_collection_products_position`.  \n\n9. `product_media`  \n   - Columns: `id`, `product_id` FK → `products(id)` ON DELETE CASCADE, `type` TEXT NOT NULL DEFAULT `'image'` CHECK IN (`'image'`, `'video'`), `storage_key`, `alt_text`, `width` INTEGER, `height` INTEGER, `mime_type`, `byte_size` INTEGER, `position` INTEGER NOT NULL DEFAULT `0`, `status` TEXT NOT NULL DEFAULT `'processing'` CHECK IN (`'processing'`, `'ready'`, `'failed'`), `created_at`.  \n   - Indexes: `idx_product_media_product_id`, `idx_product_media_product_position`, `idx_product_media_status`.  \n\n**Phase 3 – Themes, Pages, Navigation**\n\n1. `themes`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `name`, `version`, `status` TEXT NOT NULL DEFAULT `'draft'` CHECK IN (`'draft'`, `'published'`), `published_at`, timestamps.  \n   - Indexes: `idx_themes_store_id`, `idx_themes_store_status`.  \n\n2. `theme_files`  \n   - Columns: `id`, `theme_id` FK → `themes(id)` ON DELETE CASCADE, `path` TEXT NOT NULL, `storage_key`, `sha256`, `byte_size` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: unique `idx_theme_files_theme_path` (`theme_id`, `path`), `idx_theme_files_theme_id`.  \n\n3. `theme_settings`  \n   - Columns: `theme_id` PK/FK → `themes(id)` ON DELETE CASCADE, `settings_json` TEXT NOT NULL DEFAULT `'{}'`, `updated_at` TEXT NULL.  \n\n4. `pages`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `title`, `handle` TEXT UNIQUE per store, `body_html`, `status` TEXT NOT NULL DEFAULT `'draft'` CHECK IN (`'draft'`, `'published'`, `'archived'`), `published_at`, timestamps.  \n   - Indexes: unique `idx_pages_store_handle`, `idx_pages_store_id`, `idx_pages_store_status`.  \n\n5. `navigation_menus`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `handle` TEXT UNIQUE per store, `title`, timestamps.  \n   - Indexes: unique `idx_navigation_menus_store_handle`, `idx_navigation_menus_store_id`.  \n\n6. `navigation_items`  \n   - Columns: `id`, `menu_id` FK → `navigation_menus(id)` ON DELETE CASCADE, `type` TEXT NOT NULL DEFAULT `'link'` CHECK IN (`'link'`, `'page'`, `'collection'`, `'product'`), `label`, `url`, `resource_id` INTEGER NULL, `position` INTEGER NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_navigation_items_menu_id`, `idx_navigation_items_menu_position`.  \n\n**Phase 4 – Cart, Checkout, Discounts, Shipping, Taxes**\n\n1. `carts`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `currency` TEXT NOT NULL DEFAULT `'USD'`, `cart_version` INTEGER NOT NULL DEFAULT `1`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'converted'`, `'abandoned'`), timestamps.  \n   - Indexes: `idx_carts_store_id`, `idx_carts_customer_id`, `idx_carts_store_status`.  \n\n2. `cart_lines`  \n   - Columns: `id`, `cart_id` FK → `carts(id)` ON DELETE CASCADE, `variant_id` FK → `product_variants(id)` ON DELETE CASCADE, `quantity` INTEGER NOT NULL DEFAULT `1`, `unit_price_amount`, `line_subtotal_amount`, `line_discount_amount`, `line_total_amount` (all INTEGER NOT NULL DEFAULT `0`).  \n   - Indexes: `idx_cart_lines_cart_id`, unique `idx_cart_lines_cart_variant` (`cart_id`, `variant_id`).  \n\n3. `checkouts`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `cart_id` FK → `carts(id)` ON DELETE CASCADE, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `status` TEXT NOT NULL DEFAULT `'started'` CHECK IN (`'started'`, `'addressed'`, `'shipping_selected'`, `'payment_selected'`, `'completed'`, `'expired'`), `payment_method` TEXT NULL CHECK IN (`'credit_card'`, `'paypal'`, `'bank_transfer'`), `email`, `shipping_address_json`, `billing_address_json`, `shipping_method_id` INTEGER NULL, `discount_code`, `tax_provider_snapshot_json`, `totals_json`, `expires_at`, timestamps.  \n   - Indexes: `idx_checkouts_store_id`, `idx_checkouts_cart_id`, `idx_checkouts_customer_id`, `idx_checkouts_status`, `idx_checkouts_expires_at`.  \n\n4. `shipping_zones`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `name`, `countries_json` TEXT NOT NULL DEFAULT `'[]'`, `regions_json` TEXT NOT NULL DEFAULT `'[]'`.  \n   - Index: `idx_shipping_zones_store_id`.  \n\n5. `shipping_rates`  \n   - Columns: `id`, `zone_id` FK → `shipping_zones(id)` ON DELETE CASCADE, `name`, `type` TEXT NOT NULL DEFAULT `'flat'` CHECK IN (`'flat'`, `'weight'`, `'price'`, `'carrier'`), `config_json` TEXT NOT NULL DEFAULT `'{}'`, `is_active` BOOLEAN NOT NULL DEFAULT `1`.  \n   - Indexes: `idx_shipping_rates_zone_id`, `idx_shipping_rates_zone_active`.  \n\n6. `tax_settings`  \n   - Columns: `store_id` PK/FK → `stores(id)` ON DELETE CASCADE, `mode` TEXT NOT NULL DEFAULT `'manual'` CHECK IN (`'manual'`, `'provider'`), `provider` TEXT NOT NULL DEFAULT `'none'` CHECK IN (`'stripe_tax'`, `'none'`), `prices_include_tax` BOOLEAN NOT NULL DEFAULT `0`, `config_json` TEXT NOT NULL DEFAULT `'{}'`.  \n\n7. `discounts`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `type` TEXT NOT NULL DEFAULT `'code'` CHECK IN (`'code'`, `'automatic'`), `code` TEXT NULL UNIQUE per store, `value_type` TEXT NOT NULL CHECK IN (`'fixed'`, `'percent'`, `'free_shipping'`), `value_amount` INTEGER NOT NULL DEFAULT `0`, `starts_at` TEXT NOT NULL, `ends_at` TEXT NULL, `usage_limit` INTEGER NULL, `usage_count` INTEGER NOT NULL DEFAULT `0`, `rules_json` TEXT NOT NULL DEFAULT `'{}'`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'draft'`, `'active'`, `'expired'`, `'disabled'`), timestamps.  \n   - Indexes: unique `idx_discounts_store_code` (`store_id`, `code`), `idx_discounts_store_id`, `idx_discounts_store_status`, `idx_discounts_store_type`.  \n\n**Phase 5 – Payments, Orders, Fulfillment, Customers**\n\n1. `customers`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `email` TEXT NOT NULL, `password_hash` TEXT NULL, `name` TEXT NULL, `marketing_opt_in` BOOLEAN NOT NULL DEFAULT `0`, timestamps.  \n   - Indexes: unique `idx_customers_store_email` (`store_id`, `email`), `idx_customers_store_id`.  \n\n2. `customer_addresses`  \n   - Columns: `id`, `customer_id` FK → `customers(id)` ON DELETE CASCADE, `label`, `address_json` TEXT NOT NULL DEFAULT `'{}'`, `is_default` BOOLEAN NOT NULL DEFAULT `0`.  \n   - Indexes: `idx_customer_addresses_customer_id`, `idx_customer_addresses_default`.  \n\n3. `orders`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `order_number` TEXT NOT NULL, `payment_method` TEXT NOT NULL CHECK IN (`'credit_card'`, `'paypal'`, `'bank_transfer'`), `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'paid'`, `'fulfilled'`, `'cancelled'`, `'refunded'`), `financial_status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'authorized'`, `'paid'`, `'partially_refunded'`, `'refunded'`, `'voided'`), `fulfillment_status` TEXT NOT NULL DEFAULT `'unfulfilled'` CHECK IN (`'unfulfilled'`, `'partial'`, `'fulfilled'`), `currency` TEXT NOT NULL DEFAULT `'USD'`, `_amount` columns (`subtotal_amount`, `discount_amount`, `shipping_amount`, `tax_amount`, `total_amount`) all INTEGER NOT NULL DEFAULT `0`, `email`, `billing_address_json`, `shipping_address_json`, `placed_at`, timestamps.  \n   - Indexes: unique `idx_orders_store_order_number` (`store_id`, `order_number`), `idx_orders_store_id`, `idx_orders_customer_id`, `idx_orders_store_status`, `idx_orders_store_financial`, `idx_orders_store_fulfillment`, `idx_orders_placed_at`.  \n\n4. `order_lines`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `product_id` FK → `products(id)` ON DELETE SET NULL, `variant_id` FK → `product_variants(id)` ON DELETE SET NULL, `title_snapshot` TEXT NOT NULL, `sku_snapshot` TEXT NULL, `quantity` INTEGER NOT NULL DEFAULT `1`, `unit_price_amount`, `total_amount` INTEGER NOT NULL DEFAULT `0`, `tax_lines_json` TEXT NOT NULL DEFAULT `'[]'`, `discount_allocations_json` TEXT NOT NULL DEFAULT `'[]'`.  \n   - Indexes: `idx_order_lines_order_id`, `idx_order_lines_product_id`, `idx_order_lines_variant_id`.  \n\n5. `payments`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `provider` TEXT NOT NULL DEFAULT `'mock'` CHECK IN (`'mock'`), `method` TEXT NOT NULL CHECK IN (`'credit_card'`, `'paypal'`, `'bank_transfer'`), `provider_payment_id`, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'captured'`, `'failed'`, `'refunded'`), `amount` INTEGER NOT NULL DEFAULT `0`, `currency` TEXT NOT NULL DEFAULT `'USD'`, `raw_json_encrypted` TEXT NULL, `created_at`.  \n   - Indexes: `idx_payments_order_id`, `idx_payments_provider_id` (`provider`, `provider_payment_id`), `idx_payments_method`, `idx_payments_status`.  \n\n6. `refunds`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `payment_id` FK → `payments(id)` ON DELETE CASCADE, `amount` INTEGER NOT NULL DEFAULT `0`, `reason`, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'processed'`, `'failed'`), `provider_refund_id`, `created_at`.  \n   - Indexes: `idx_refunds_order_id`, `idx_refunds_payment_id`, `idx_refunds_status`.  \n\n7. `fulfillments`  \n   - Columns: `id`, `order_id` FK → `orders(id)` ON DELETE CASCADE, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'shipped'`, `'delivered'`), `tracking_company`, `tracking_number`, `tracking_url`, `shipped_at`, `created_at`.  \n   - Indexes: `idx_fulfillments_order_id`, `idx_fulfillments_status`, `idx_fulfillments_tracking` (`tracking_company`, `tracking_number`).  \n\n8. `fulfillment_lines`  \n   - Columns: `id`, `fulfillment_id` FK → `fulfillments(id)` ON DELETE CASCADE, `order_line_id` FK → `order_lines(id)` ON DELETE CASCADE, `quantity` INTEGER NOT NULL DEFAULT `1`.  \n   - Indexes: `idx_fulfillment_lines_fulfillment_id`, unique `idx_fulfillment_lines_fulfillment_order_line` (`fulfillment_id`, `order_line_id`).  \n\n**Phase 7 – Analytics**\n\n1. `analytics_events`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `type` TEXT NOT NULL, `session_id`, `customer_id` FK → `customers(id)` ON DELETE SET NULL, `properties_json` TEXT NOT NULL DEFAULT `'{}'`, `client_event_id` TEXT NULL, `occurred_at` TEXT NULL, `created_at` TEXT NULL.  \n   - Indexes: `idx_analytics_events_store_id`, `idx_analytics_events_store_type`, `idx_analytics_events_store_created`, `idx_analytics_events_session`, `idx_analytics_events_customer`, unique `idx_analytics_events_client_event` (`store_id`, `client_event_id`).  \n\n2. `analytics_daily`  \n   - Columns: `store_id` FK → `stores(id)` ON DELETE CASCADE, `date` TEXT NOT NULL (YYYY-MM-DD), `orders_count`, `revenue_amount`, `aov_amount`, `visits_count`, `add_to_cart_count`, `checkout_started_count`, `checkout_completed_count` (all INTEGER NOT NULL DEFAULT `0`).  \n   - Primary key: composite (`store_id`, `date`).  \n   - Index: `idx_analytics_daily_store_date`.  \n\n**Phase 10 – Apps & Webhooks**\n\n1. `apps`  \n   - Columns: `id`, `name`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'disabled'`), `created_at` TEXT NULL.  \n   - Index: `idx_apps_status`.  \n\n2. `app_installations`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `app_id` FK → `apps(id)` ON DELETE CASCADE, `scopes_json` TEXT NOT NULL DEFAULT `'[]'`, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'suspended'`, `'uninstalled'`), `installed_at` TEXT NULL.  \n   - Indexes: unique `idx_app_installations_store_app` (`store_id`, `app_id`), `idx_app_installations_store_id`, `idx_app_installations_app_id`.  \n\n3. `oauth_clients`  \n   - Columns: `id`, `app_id` FK → `apps(id)` ON DELETE CASCADE, `client_id` TEXT NOT NULL UNIQUE, `client_secret_encrypted` TEXT NOT NULL, `redirect_uris_json` TEXT NOT NULL DEFAULT `'[]'`.  \n   - Indexes: unique `idx_oauth_clients_client_id`, `idx_oauth_clients_app_id`.  \n\n4. `oauth_tokens`  \n   - Columns: `id`, `installation_id` FK → `app_installations(id)` ON DELETE CASCADE, `access_token_hash` TEXT NOT NULL UNIQUE, `refresh_token_hash` TEXT NULL, `expires_at` TEXT NOT NULL.  \n   - Indexes: `idx_oauth_tokens_installation_id`, unique `idx_oauth_tokens_access_hash`, `idx_oauth_tokens_expires_at`.  \n\n5. `webhook_subscriptions`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `app_installation_id` FK → `app_installations(id)` ON DELETE CASCADE, `event_type` TEXT NOT NULL, `target_url` TEXT NOT NULL, `signing_secret_encrypted` TEXT NOT NULL, `status` TEXT NOT NULL DEFAULT `'active'` CHECK IN (`'active'`, `'paused'`, `'disabled'`).  \n   - Indexes: `idx_webhook_subscriptions_store_id`, `idx_webhook_subscriptions_store_event`, `idx_webhook_subscriptions_installation`.  \n\n6. `webhook_deliveries`  \n   - Columns: `id`, `subscription_id` FK → `webhook_subscriptions(id)` ON DELETE CASCADE, `event_id` TEXT NOT NULL, `attempt_count` INTEGER NOT NULL DEFAULT `1`, `status` TEXT NOT NULL DEFAULT `'pending'` CHECK IN (`'pending'`, `'success'`, `'failed'`), `last_attempt_at`, `response_code` INTEGER NULL, `response_body_snippet` TEXT NULL.  \n   - Indexes: `idx_webhook_deliveries_subscription_id`, `idx_webhook_deliveries_event_id`, `idx_webhook_deliveries_status`, `idx_webhook_deliveries_last_attempt`.  \n\n**Phase 8 – Search (per Phase 4/5 timeline)**\n\n1. `search_settings`  \n   - Columns: `store_id` PK/FK → `stores(id)` ON DELETE CASCADE, `synonyms_json` TEXT NOT NULL DEFAULT `'[]'`, `stop_words_json` TEXT NOT NULL DEFAULT `'[]'`, `updated_at` TEXT NULL.  \n\n2. `search_queries`  \n   - Columns: `id`, `store_id` FK → `stores(id)` ON DELETE CASCADE, `query` TEXT NOT NULL, `filters_json` TEXT NULL, `results_count` INTEGER NOT NULL DEFAULT `0`, `created_at` TEXT NULL.  \n   - Indexes: `idx_search_queries_store_id`, `idx_search_queries_store_created`, `idx_search_queries_store_query`.  \n\n**Enums summary (checked via `CHECK` constraints on each table):**\n\n- Store-related: `StoreStatus` (`active`, `suspended`), `StoreDomainType` (`storefront`, `admin`, `api`), `StoreDomainTlsMode` (`managed`, `bring_your_own`), `StoreUserRole` (`owner`, `admin`, `staff`, `support`).  \n- Product/catalog: `ProductStatus` (`draft`, `active`, `archived`), `VariantStatus` (`active`, `archived`), `CollectionType` (`manual`, `automated`), `CollectionStatus` (`draft`, `active`, `archived`), `MediaType` (`image`, `video`), `MediaStatus` (`processing`, `ready`, `failed`), `InventoryPolicy` (`deny`, `continue`).  \n- Themes/navigation: `ThemeStatus` (`draft`, `published`), `PageStatus` (`draft`, `published`, `archived`), `NavigationItemType` (`link`, `page`, `collection`, `product`).  \n- Cart/checkout/etc: `CartStatus` (`active`, `converted`, `abandoned`), `CheckoutStatus` plus `PaymentMethod` etc noted above, `ShippingRateType` (`flat`, `weight`, `price`, `carrier`), `TaxMode` (`manual`, `provider`), `TaxProvider` (`stripe_tax`, `none`), `DiscountType` (`code`, `automatic`), `DiscountValueType` (`fixed`, `percent`, `free_shipping`).  \n- Orders: `OrderStatus`, `FinancialStatus`, `FulfillmentStatus`, `PaymentStatus`, `RefundStatus`, `FulfillmentStatus`, `WebhookStatus`, etc enumerations specified on each table.  \n\nThis blueprint gives the precise column definitions, constraints, indexes, foreign-key references, enumerations, and the sequential order in which the migrations should be created."}}


Output: {"status":{"completed":"Solution: The requested matrix is broken into four sections (Web routes, Storefront REST API, Admin REST API, and OAuth/App endpoints) with each entry linking the path/method to the middleware/auth requirements, the success-response expectations defined in `specs/02-API-ROUTES.md`, and the Playwright suites/tests from `specs/08-PLAYWRIGHT-E2E-PLAN.md` that exercise the UI surface built on top of those routes so you can trace coverage.\n\n**Web Routes Matrix**\n| Route | Method | Auth / Middleware | Success expectations | Playwright coverage |\n|---|---|---|---|---|\n| `/` | GET | `web`, `store.resolve` | `Storefront\\Home` renders “Acme Fashion”, expected HTTP 200 and no JS errors | Suite 1.1 (Smoke) |\n| `/collections` | GET | `web`, `store.resolve` | `Storefront\\Collections\\Index` shows “Collections”, product tiles | Suite 1.2, 1.9; Suite 7 (Browsing) tests collection navigation |\n| `/collections/{handle}` | GET | `web`, `store.resolve` | `Storefront\\Collections\\Show` shows collection title, filters | Suite 1.2 & 7.4 |\n| `/products/{handle}` | GET | `web`, `store.resolve` | `Storefront\\Products\\Show` shows title, price, variant selectors | Suite 1.3; Suites 7 & 8 add-to-cart flow |\n| `/cart` | GET | `web`, `store.resolve` | `Storefront\\Cart\\Show` with line items, totals and discount controls | Suites 1.4, 8.2–8.12 |\n| `/search` | GET | `web`, `store.resolve` | `Storefront\\Search\\Index` shows query, results, sorts | Suite 1.8; Suite 7 (search) |\n| `/pages/{handle}` | GET | `web`, `store.resolve` | `Storefront\\Pages\\Show` renders page content (e.g., about) | Suites 1.7, 7.6 |\n| `/account/login` | GET/POST | `web` (no auth) | `Storefront\\Account\\Auth\\Login` renders form and throttles `login` tier | Suite 1.5 (page load); Suite 10 covers login validation |\n| `/account/register` | GET | `web` | `Storefront\\Account\\Auth\\Register` | Suite 10 (registration) |\n| `/forgot-password`, `/reset-password/{token}` | GET/POST | `web` | Livewire auth flows | Covered indirectly by Suite 10 mail flows |\n| `/account` | GET | `web`, `store.resolve`, `auth:customer` | `Storefront\\Account\\Dashboard` with orders/address shortcuts | Suite 10 (dashboard) |\n| `/account/orders` | GET | same as above | `Storefront\\Account\\Orders\\Index` shows order list | Suite 10 (history) |\n| `/account/orders/{orderNumber}` | GET | same as above | `Storefront\\Account\\Orders\\Show` | Suite 10 |\n| `/account/addresses` | GET/POST/PUT | `auth:customer` | Address book management view | Suite 10 |\n| `/checkout/{checkoutId}` | GET | `web`, `store.resolve` | Multi-step checkout UI `Storefront\\Checkout\\Show` that orchestrates API calls | Suite 9 (full flow) |\n| `/checkout/{checkoutId}/confirmation` | GET | `web`, `store.resolve` | Confirmation page showing order summary | Suite 9.1, 9.9, 9.10 |\n\n**Admin Web Routes (Livewire)**\n| Route | Method | Auth / Middleware | Expectations | Playwright coverage |\n|---|---|---|---|---|\n| `/admin/login`, `/admin/forgot-password`, `/admin/reset-password` | GET/POST | `web` (no auth) | Livewire auth views, rate-limited to `login` tier | Suite 2.1–2.4 |\n| `/admin` | GET | `web`, `auth`, `verified`, `store.resolve`, `role.check` | `Admin\\Dashboard` with KPIs (HTTP 200, no JS errors) | Suite 2.1 (after login) |\n| `/admin/products` and `/admin/products/*` | GET | same as above | Product index/create/edit forms (`Admin\\Products\\*`) | Suite 3 (Product Management) |\n| `/admin/orders` and `/admin/orders/{order}` | GET | same | `Admin\\Orders\\Index`/`Show` | Suite 4 |\n| `/admin/customers`, `/admin/customers/{customer}` | GET | same | Customer list/detail | Suite 16 |\n| `/admin/discounts` | GET | same | Discounts index/form | Suite 5 |\n| `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes` | GET | same | Settings tabs for shipping/taxes | Suite 6 |\n| `/admin/collections`, `/admin/collections/{collection}` | GET | same | Collection management | Suite 15 |\n| `/admin/pages`, `/admin/pages/{page}` | GET | same | CMS page CRUD | Suite 17 |\n| `/admin/analytics`, `/admin/search/settings`, `/admin/themes`, `/admin/apps`, `/admin/navigations`, `/admin/developers` | GET | same | Various admin dashboards and editors | Suites 6 (settings/analytics), 18 (analytics), 2 (navigation/others via dashboard nav) |\n| `/admin/inventory`, `/admin/shipping`, `/admin/themes/{theme}/editor`, `/admin/apps/{installation}` | GET | same | Inventory/collector & app-specific views | Suite 6 (shipping), internal coverage via Suites 3–18 where applicable |\n\n**Storefront REST API Matrix (`/api/storefront/v1`)**\n| Endpoint | Method | Auth / Middleware | Success response expectation | UI coverage |\n|---|---|---|---|---|\n| `/api/storefront/v1/carts` | POST | `store.resolve`, rate limit `api.storefront`, no auth | `201 Created` cart object (id, lines, totals) | Suite 8 (cart creation when adding to cart) |\n| `/api/storefront/v1/carts/{cartId}` | GET | same | `200 OK` cart snapshot with lines/totals | Suite 8 (cart view) |\n| `/api/storefront/v1/carts/{cartId}/lines` | POST | same | `201` cart with new line; errors `404`, `409`, `422` | Suite 8 (adding/line updates) |\n| `/api/storefront/v1/carts/{cartId}/lines/{lineId}` | PUT/DELETE | same | `200` updated cart; `409` for version conflicts | Suite 8 (quantity adjustments, removals) |\n| `/api/storefront/v1/checkouts` | POST | `checkout` tier, no auth | `201` checkout object (status `started`, lines, totals) | Suite 9 (start checkout) |\n| `/api/storefront/v1/checkouts/{id}` | GET | `checkout` tier | `200` current checkout state; `410` if expired | Suite 9 |\n| `/api/storefront/v1/checkouts/{id}/address` | PUT | same | `200` checkout with address, `status=addressed`, shipping methods populated | Suite 9.1–9.7 |\n| `/api/storefront/v1/checkouts/{id}/shipping-method` | PUT | same | `200`, `status=shipping_selected`, totals + tax snapshot | Suite 9.1–9.3 |\n| `/api/storefront/v1/checkouts/{id}/payment-method` | PUT | same | `200`, `status=payment_selected` | Suite 9 (payment selection) |\n| `/api/storefront/v1/checkouts/{id}/apply-discount` | POST | same | `200` totals with discount data; errors `400/404/410/422` | Suites 8.6–8.12 and 9.4 |\n| `/api/storefront/v1/checkouts/{id}/discount` | DELETE | same | `200` totals recalculated without discount | Suite 9 discount removal path |\n| `/api/storefront/v1/checkouts/{id}/pay` | POST | same | `200` with order info (`order.status`, `payment_method`, optional bank transfer instructions); `422` for declines (`card_declined`, `insufficient_funds`), `409` if wrong state | Suite 9.1 (credit card success), 9.9 (PayPal), 9.10 (bank transfer), 9.11–9.12 (declines) |\n| `/api/storefront/v1/orders/{orderNumber}` | GET | `token` parameter (HMAC) | `200` order summary (status, totals, addresses) | Suite 9 confirmation page; Suite 10 order detail |\n| `/api/storefront/v1/search` | GET | `search` tier | `200` hits + facets + pagination; `422` for malformed filters | Suite 7 (search results) |\n| `/api/storefront/v1/search/suggest` | GET | `search` tier | `200` autocomplete suggestions | Suite 7 (search typing) |\n| `/api/storefront/v1/analytics/events` | POST | `analytics` tier | `202 Accepted` with `accepted`/`rejected` counts | Logged indirectly via JS instrumentation tested in Suite 1 smoke paths |\n\n**Admin REST API Matrix (`/api/admin/v1`)**\n| Endpoint(s) | Method | Auth / Middleware | Success response | Playwright coverage |\n|---|---|---|---|---|\n| `/platform/organizations` | POST | `auth:sanctum`, scope `manage-platform` | `201` organization payload | Not yet covered by UI tests (future integration) |\n| `/platform/stores` | POST | same | `201` store payload | Not yet covered |\n| `/stores/{storeId}/invites` | POST | same | `201` invite details | Not yet covered |\n| `/stores/{storeId}/me` | GET | Sanctum token | `200` membership + permissions list | Used as part of admin session initialization (Suite 2) |\n| `/stores/{storeId}/products[/{productId}]` | GET/POST/PUT/DELETE + `/media/presign-upload` | Sanctum token with `read/write-products`, `rate limit api.admin` | JSON product resources or presigned URL; consistent `data` wrapper | Suite 3 (Product CRUD) |\n| `/stores/{storeId}/collections[/{id}]` | GET/POST/PUT/DELETE | `read/write-collections` | `200`/`201` collection payload | Suite 15 (Collection Management) |\n| `/stores/{storeId}/orders[/{orderId}]` | GET + fulfillment/refund subroutes | `read-orders`/`write-orders` | `200` order + `201` fulfillments/refunds | Suite 4 (Order flow) |\n| `/stores/{storeId}/discounts[/{id}]` | GET/POST/PUT/DELETE | `read/write-discounts` | `200`/`201` discount payload | Suite 5 (Discount Management) |\n| `/stores/{storeId}/shipping/zones[/{zoneId}]` | GET/POST/PUT + `/rates` | `read/write-settings` | `200` zone with rates, `201` rate | Suite 6 (Settings) |\n| `/stores/{storeId}/tax/settings` | GET/PUT | `read/write-settings` | `200` tax config payload | Suite 6 |\n| `/stores/{storeId}/themes`, `/themes/{themeId}/publish`, `/themes/{themeId}/settings` | POST/PUT | `write-themes` | `201` theme metadata, `200` publish/settings updates | Suite 6 (themes part of settings navigation) |\n| `/stores/{storeId}/pages[/{pageId}]` | GET/POST/PUT/DELETE | `read/write-content` | `200`/`201` page payload | Suite 17 (Page Management) |\n| `/stores/{storeId}/search/reindex` | POST | `write-settings` | `202` queued job | Suite 6 touches search settings (manual coverage) |\n| `/stores/{storeId}/search/status` | GET | `read-settings` | `200` status data | Suite 6 |\n| `/stores/{storeId}/analytics/summary` | GET | `read-analytics` | `200` summary with totals/daily/top_products | Suite 18 (Analytics Dashboard) |\n| `/stores/{storeId}/exports/orders` | POST | `read-orders` | `202` export job | Suite 18 export UI path |\n| `/stores/{storeId}/exports/{exportId}` | GET | `read-orders` | `200` download metadata | Suite 18 poll page |\n| `/stores/{storeId}/customers` | GET | `read-customers` | `200` customer list | Suite 16 (Customer Management) |\n\n**OAuth and App API Matrix**\n| Endpoint | Method | Auth / Middleware | Expected behavior | Playwright coverage |\n|---|---|---|---|---|\n| `/oauth/authorize` | GET | `web`, `auth` | Shows OAuth approval prompt; future (501) | Not covered (deferred) |\n| `/oauth/token` | POST | Public | Issues tokens (authorization code or refresh) | Not covered yet |\n| `/api/apps/v1/stores/{storeId}/products` | GET | OAuth token scope `read-products` | Same schema as admin product list | Not covered; reserved for future app integrations |\n| `/api/apps/v1/stores/{storeId}/orders` | GET | Scope `read-orders` | Order list | Future coverage |\n| `/api/apps/v1/stores/{storeId}/customers` | GET | Scope `read-customers` | Customer list | Future coverage |\n\n**Notes:** \n- All frontend endpoints are suffixed with the middleware stack in the spec (e.g., `store.resolve`, `auth:customer`, `auth` + `verified`, `role.check`), so the matrix references those exact requirements.\n- The Storefront API replies include traceable fields (cart lines/totals, checkout states, order details); the Playwright Cart/Checkout suites drive those flows to validate the expected `200/201` responses and business logic errors (discount rejection, payment failure codes).\n- Admin UI routes/rendered APIs are exercised across Suites 2–6 and 15–18, ensuring authentication, CRUD, and analytics dashboards all return the expected Livewire views.\n- Rate limits from Section 7 apply (e.g., `login`, `api.storefront`, `checkout`, `search`, `analytics`, `api.admin`). Error structures (422 validation, 403 auth, 409 conflict, 400 business logic, 500 server) are standardized across APIs.\n\nTests were not run in this turn."}}


Output: {"status":{"completed":"**Admin UI Checklist**\n- Layout shell: Livewire handles `App\\Livewire\\Admin\\Layout\\Sidebar` (collapsible responsive sidebar, route-aware active state, hamburger toggle on mobile), `App\\Livewire\\Admin\\Layout\\TopBar` (store switch dropdown, unread notification badge, profile dropdown, toasts via `toast` event), and the main content area with breadcrumbs plus global toast listener.\n- Dashboard (`App\\Livewire\\Admin\\Dashboard`): date-range dropdown (today/7/30/custom with date inputs), KPI tiles showing formatted values and trend badges, orders-over-time chart (Alpine/Chart.js with spinner), top products table (max 5 rows/empty state), conversion funnel bars, loading/error states, and computed formatted totals.\n- Products list (`App\\Livewire\\Admin\\Products\\Index`): search input (300ms debounce), status/type selects, sortable table with pagination, bulk selection bar (Set Active/Archive/Delete), delete confirmation modal, empty/filtered states, row badges, and Livewire actions for selection toggles, sorting, and bulk operations.\n- Product form (`App\\Livewire\\Admin\\Products\\Form`): 2-column layout with primary fields, controls for status/vendor/type/collections/options/variants (tabs/accordion), file upload slots, sticky save bar (Discard/Save with loading), toast on save, delete modal for destructive actions, and validation states on Flux fields. (Further form pages follow the patterns in subsection 19.)\n- Other admin pages referenced in the summary (collections, inventory, orders, customers, discounts, settings, themes, pages, navigation, apps, developers, analytics, search settings) all follow the list/form conventions: headings with breadcrumbs, card patterns, Flux components (heading, button, select, modal, badge), modals for destructive actions, loading states, toasts, and consistent responsive behavior as spelled out in section 19.\n\n**Frontstore Checklist & Livewire Interactions**\n- Master layout (`storefront-layout`): skip link, announcement bar (dismiss persisted in localStorage), header with desktop nav, search/cart/account buttons, optional sticky behavior, responsive mobile drawer (hamburger/close, focus trap, ARIA labels), footer columns/social/payment icons, cart drawer (`CartDrawer`) opened via `open-cart-drawer` event, and dark-mode handling (system/toggle/forced).\n- Home page sections: Hero banner, featured collections grid (clickable cards with alt/etc.), featured products grid backed by `FeaturedProducts` Livewire (lazy-loaded, skeletons), newsletter signup (`NewsletterSignup` with success/error states), and rich text blocks. Each section maps to theme-configured settings for ordering/visibility.\n- Product listing contexts (collection, search, featured grids): `CollectionPage`/`SearchResults` Livewire components with toolbar (filter toggle, product count, sort dropdown), filter sidebar/drawer (availability checkbox, price min/max, vendor/type checklists, clear-all link, active pills), responsive grid using `storefront-product-card`, loading overlays, and pagination via `storefront-pagination`.\n- Product detail (`ProductPage` Livewire): hero media gallery with hover swap, variant selectors (radio/fieldsets with live stock/price updates), quantity selector (`storefront-quantity-selector`), add-to-cart actions dispatching `cart-updated`, toast on success/error, and contextual compute for badges/price display.\n- Global cart and checkout flows: `CartDrawer` (dialog with focus trap, line editing, quantity updates, remove, discount input, close events), `CartPage` for full cart view, `CheckoutPage` multi-step flow (contact/shipping/payment with Livewire validation, totals in live regions, address forms via `storefront-address-form`, order summary via `storefront-order-summary`), and post-order confirmation views.\n- Customer account: Login/Register Livewire forms with validation, `AddressBook` for CRUD (modals sharing address form, default badge), orders list/detail with summary tables, and consistent accessibility hooks (labels, live regions).\n- Search (`SearchModal`/`SearchResults`): modal triggered by header search icon, Livewire debounce (300ms) with grouped result cards, keyboard navigation, loading skeletons, “view all” link, and fallback message; full search page reuses collection-layout conventions.\n- Component library references (price formatting, badges, breadcrumbs, pagination) ensure consistent rendering; global Livewire events (`cart-updated`, `open/close-cart-drawer`, `open/close-search-modal`, `variant-changed`) coordinate between header, drawers, and product/detail components.\n- Accessibility/responsiveness: skip links, WCAG contrast/focus states, keyboard traps for modals/drawers, live regions for dynamic updates (filters, cart totals, checkout validations), and touch-friendly behavior documented in sections 14–15.\n\nNext steps you might take include mapping each Livewire component to its blade view for implementation order, or deriving task tickets for the listed interactions."}}


**Spawning explorer for pending specs**


Read /Users/fabianwesner/Herd/shop/specs/05-BUSINESS-LOGIC.md, /Users/fabianwesner/Herd/shop/specs/06-AUTH-AND-SECURITY.md, /Users/fabianwesner/Herd/shop/specs/07-SEEDERS-AND-TEST-DATA.md and /Users/fabianwesner/Herd/shop/specs/09-IMPLEMENTATION-ROADMAP.md. Produce exact checklist of services/jobs/events/policies/auth providers/rate limits/seeders and required failure-path tests. No file edits.


**Planning progress tracking**


Output: {"status":{"019c5b5a-72ef-7480-a0ad-5e6aadd8202e":{"completed":"**Services**\n1. `App\\Services\\ProductService` – product CRUD, nested variants/options, state transitions, status-change events (`ProductStatusChanged`).\n2. `App\\Services\\VariantMatrixService` – recomputes the option-value cartesian product, reuses existing variants, archives or deletes unused ones.\n3. `App\\Services\\InventoryService` – reserve/release/commit/restock inventory with policy checks and transaction wrapping plus `InsufficientInventoryException`.\n4. `App\\Services\\CartService` – cart lifecycle, guest-to-customer merge, versioning, optimistic concurrency validation.\n5. `App\\Services\\PricingEngine` – deterministic pipeline (lines → subtotal → discount → shipping → tax → total) with `PricingResult` snapshots per checkout.\n6. `App\\Services\\DiscountService` – discount validation, amount/allocation logic, minimum order and usage-limit enforcement.\n7. `App\\Services\\ShippingCalculator` – zone matching, rate calculation, shipping method validation for checkout.\n8. `App\\Services\\TaxCalculator` (and `App\\Services\\Tax\\ManualTaxProvider`/`StripeTaxProvider`) – tax orchestration with providers returning `TaxCalculationResult`.\n9. `App\\Services\\PaymentService` – coordinates payment method selection with `App\\Services\\Payment\\MockPaymentProvider` (credit card, PayPal, bank transfer magic numbers).\n10. `App\\Services\\RefundService` – refund creation, provider interaction, inventory restock.\n11. `App\\Services\\FulfillmentService` – fulfillment lifecycle, status transitions, notifications, auto-fulfill for digital products.\n12. `App\\Services\\CustomerService` – customer registration, store-scoped account management, password resets.\n13. `App\\Services\\NavigationService` – builds menu trees, resolves page/collection/product URLs, caches per store.\n14. `App\\Services\\SearchService` – manages SQLite FTS5 index, sanitizes queries, runs relevance-ranked searches, logs queries.\n15. `App\\Services\\AnalyticsService` – ingests events, feeds daily aggregates.\n16. `App\\Services\\WebhookService` (plus `App\\Services\\WebhookDispatcher`) – delivers signed outbound webhooks, manages circuit breaker state.\n17. `App\\Services\\HandleGenerator` – unique slug/handle generation per store with collision suffixing.\n18. `App\\Services\\AuditLogger` – writes structured JSON audit entries to the `audit` channel with IP/user-agent metadata.\n19. `App\\Services\\ThemeSettingsService` (singleton) – loads and caches active theme settings for the resolved store.\n\n**Jobs**\n1. `App\\Jobs\\ProcessMediaUpload` – resizes uploads (thumbnail → large, optional WebP), updates `ProductMedia` status, retries up to 3 times, leaves original on failure.\n2. `App\\Jobs\\DeliverWebhook` – serializes payload, signs with HMAC-SHA256, posts to subscriber, records deliveries, enforces retry/backoff (1 min → 5 min → 30 min → 2 h → 12 h).\n3. `App\\Jobs\\AggregateAnalytics` – daily at 01:00 UTC rolls raw `analytics_events` into `analytics_daily` with visits/orders/revenue/aov.\n4. `App\\Jobs\\CleanupAbandonedCarts` – daily, marks carts idle ≥14 days as `abandoned`, releases related inventory.\n5. `App\\Jobs\\ExpireAbandonedCheckouts` – every 15 minutes, expires checkouts idle ≥24 h, releases inventory/reservations.\n6. `App\\Jobs\\CancelUnpaidBankTransferOrders` – daily cancellation for unpaid bank-transfer orders after configurable days (default 7).\n\n**Events**\n1. `OrderCreated` – triggers confirmation email, webhooks (`order.created`), analytics event.\n2. `OrderPaid` – commits inventory (especially bank transfers), auto-fulfills digital-only orders, dispatches notifications.\n3. `OrderFulfilled` – updates fulfillment status and downstream notifications/webhooks.\n4. `OrderCancelled` – releases inventory, logs cancellation, notifies customer.\n5. `OrderRefunded` – processes refunds, restocks inventory if flagged, dispatches webhook `order.refunded`.\n6. `CheckoutCompleted` – final state, creates order, dispatches `checkout.completed` webhook.\n7. `CheckoutAddressed` – saves address, recalculates pricing.\n8. `CheckoutShippingSelected` – saves shipping method, recalculates pricing.\n9. `CheckoutExpired` – releases reserved inventory for timed-out checkouts.\n10. `ProductStatusChanged` – keeps FTS5 in sync when a product toggles between draft/active/archived.\n11. `ProductCreated`, `ProductUpdated`, `ProductDeleted` – keep search index and webhooks current.\n12. `CartUpdated` – emitted on any cart mutation.\n13. `FulfillmentCreated`, `FulfillmentShipped`, `FulfillmentDelivered` – drive notifications, webhook `order.fulfilled`.\n14. `RefundProcessed` (implicit via `OrderRefunded`) – ensures audit/logging.\n15. `WebhookDeliveryAttempt` (via job) – stores delivery metadata for audit/circuit breaker tracking.\n\n**Policies**\n1. `ProductPolicy`\n2. `OrderPolicy`\n3. `CollectionPolicy`\n4. `DiscountPolicy`\n5. `CustomerPolicy`\n6. `StorePolicy`\n7. `PagePolicy`\n8. `ThemePolicy`\n9. `FulfillmentPolicy`\n10. `RefundPolicy`\nEach policy uses `ChecksStoreRole` trait (helper helpers `hasRole`, `isOwnerOrAdmin`, `isOwnerAdminOrStaff`, `isAnyRole`) and derives the target store_id via either the model or container-bound `current_store`. Permission matrix enforces Owner/Admin/Staff/Support visibility per resource.\n\n**Auth Providers & Guards**\n1. Guard `web` → provider `users` (model `App\\Models\\User`) → sessions for admin auth (password stored in `password_hash`, `getAuthPassword()` overrides, login routes under `/admin`, rate limited via `login` limiter, password broker `users` with 60-minute tokens).\n2. Guard `customer` → provider `customers` via custom `CustomerUserProvider` → store-scoped customer auth (Email+store_id, password hashed in `password_hash` column, `customers` password broker with dedicated reset table `customer_password_reset_tokens`).\n3. Guard `sanctum` → provider `users` for admin API tokens → personal access tokens prefixed with `shop_`, hashed with SHA-256, scoped abilities (read/write per resource, `manage-platform` for super-admins), `Authorization: Bearer shop_xxx`.\n4. Password security – minimum 8 chars, hashed via bcrypt, `User`/`Customer` hide password and remember token, `remember_me` cookie handled for admin.\n5. CSRF – enforced on all web routes (`@csrf`, Livewire auto), API routes exempt (token-based).\n6. Session config – file driver, 120-minute lifetime, HTTPS-only cookie `shop_session`, encrypted storage.\n\n**Rate Limiters**\n1. `login` – 5 attempts/minute per IP – protects both admin and customer Livewire login forms.\n2. `api.admin` – 60/minute per authenticated user (fallback to IP) – guards `/api/admin/v1/*`.\n3. `api.storefront` – 120/minute per IP – protects public storefront API (carts, search, analytics ingestion, etc.).\n4. `checkout` – 10/minute per session – throttle around address/shipping/payment transitions.\n5. `search` – 30/minute per IP – throttles search/autocomplete queries.\n6. `analytics` – 60/minute per IP – ingestion endpoint `/api/storefront/v1/analytics/events`.\n7. `webhooks` – 100/minute per IP – outbound webhook dispatch (fan-out throttle plus circuit breaker).\n\n**Seeders (execution order)**\n1. `OrganizationSeeder`\n2. `StoreSeeder` (creates Acme Fashion/Electronics with handles/timezones, active/suspended state).\n3. `StoreDomainSeeder`\n4. `UserSeeder` (admin/staff/support credentials: `admin@acme.test`, etc.).\n5. `StoreUserSeeder` (pivot linking users to stores with roles, owner uniqueness constraint).\n6. `StoreSettingsSeeder`\n7. `TaxSettingsSeeder`\n8. `ShippingSeeder` (zones/rates per store).\n9. `CollectionSeeder` (New Arrivals, T-Shirts, etc., plus automated vs. manual types).\n10. `ProductSeeder` (products, options, variants, inventory, media, collection links, statuses, special cases like digital/backorder).\n11. `DiscountSeeder` (codes `WELCOME10`, `FLAT5`, `FREESHIP`, expired/limit states).\n12. `CustomerSeeder` (store-scoped customers with addresses, marketing opt-in).\n13. `OrderSeeder` (sample orders #1001–#1015 for Fashion, #5001–#5003 for Electronics, covering credit cards, PayPal, bank transfer, refunds, partial/fulfilled states).\n14. `ThemeSeeder` (default themes per store + theme_settings entries with colors, hero content).\n15. `PageSeeder` (About, FAQ, Shipping & Returns, Privacy, Terms with sanitized HTML).\n16. `NavigationSeeder` (main/footer menus linking to pages/collections, position ordering).\n17. `AnalyticsSeeder` (30 days of `analytics_daily`, 200+ `analytics_events` with session grouping, distribution of event types).\n18. `SearchSettingsSeeder` (synonyms/stop words per store).\n*Each seeder must be idempotent via `firstOrCreate`/`updateOrCreate`, wrap work in a transaction, and honor seeds defined in `specs/07` (handles, marketing copy, discounts, orders).*\n\n**Required Failure-Path Tests**\n1. **Discount validation** – `DiscountService` and `DiscountCalculatorTest` cover: expired codes, not-yet-active, usage limit reached, unknown codes, minimum purchase not met, case-insensitive matching, and minimum threshold pass.\n2. **Cart versioning** – sending `expected_version` mismatch throws `CartVersionMismatchException` (409) to prevent race conditions.\n3. **Tenant resolution** – unknown hostname returns 404; suspended store returns 503 (storefront) or 403 for admin mutations.\n4. **Authentication rate limits/guards** – admin and customer login flows reject invalid credentials, never reveal which field failed, and enforce 5/minute per IP throttle; duplicate customer registration per store is rejected with validation error.\n5. **API auth** – `AdminProductApiTest` ensures a request with an invalid Sanctum token returns 401 and tokens must have required abilities (`write-products` vs. read-only).\n6. **Product lifecycle** – state machine rejects `draft → active` without priced variants, forbids `active → draft`/deletion when order lines exist, enforces store-scoped SKU uniqueness.\n7. **Inventory controls** – `InventoryService` throws `InsufficientInventoryException` when reserving beyond available quantity under `deny` policy.\n8. **Media upload** – rejects non-image files (e.g., `.txt`).\n9. **Cart constraints** – adding to cart fails if product/variant inactive or insufficient inventory; missing cart ID returns 404; version mismatch returns 409; storefront rate limiting enforced (429 after 120 requests).\n10. **Checkout state machine** – rejects empty carts, invalid addresses (missing required fields), shipping rates from wrong zones, invalid transitions (e.g., started → completed), expired discounts, and prevents invalid shipping/payment selections.\n11. **Order/refund guards** – refund requests exceeding payment amount fail; fulfilling more than ordered quantity or when financial status is `pending` (bank transfer) is blocked with `FulfillmentGuardException`.\n12. **Payment confirmation** – payment confirmation cannot occur for non-bank-transfer orders or already-paid orders; declined cards via mock PSP return 422 with a decline message.\n13. **Discount uniqueness** – discount code uniqueness enforced per store; duplicating `SAVE10` triggers validation error.\n14. **Webhook security** – tampered payloads or signatures signed with the wrong secret are rejected (HMAC verification returns false).\n15. **Cart/checkout validation suite** – addition with nonexistent variant, non-positive quantity, missing required fields in checkout address updates, empties, and invalid shipping rates provide validation errors (422) ensuring failure coverage for APIs.\n\nIf you need these items translated into tasks or prefer a checked-off template, let me know and I can format accordingly."}},"timed_out":false}