text:
Script completed
Wall time 0.0 seconds
Output:

text:
Warning: truncated output (original token count: 18603)
Total output lines: 1

[{"previous_status":{"completed":"## Audit verdict\n\nThe repo at `d3efee4e` does not satisfy `specs/03-ADMIN-UI.md` or `specs/04-STOREFRONT-UI.md`. It contains a functional commerce slice, but most specified UI components, interactions, theme behavior, and accessibility requirements are incomplete.\n\nI inspected both specifications in full, current routes/classes/views, Tailwind markup, and a read-only HTTP render. No files were edited. Playwright could not be reattached because its existing browser session was locked, so runtime-only responsive assertions remain unverified.\n\n## Admin UI gaps\n\n### Layout shell\n\n- Required `Admin\\Layout\\Sidebar` and `Admin\\Layout\\TopBar` classes/views are missing.\n- The shell is hard-coded inline in [`resources/views/layouts/admin.blade.php:4`](/Users/wesner/Herd/shop/resources/views/layouts/admin.blade.php:4).\n- No collapsed state, `toggle()`, mobile drawer, backdrop, hamburger, store selector, notifications, profile dropdown, or `switchStore()`.\n- No Flux components are used in the target admin views.\n- No breadcrumbs on admin pages.\n- No global toast listener or `toast` event system.\n- No admin dark-mode classes, system preference handling, `localStorage` persistence, or flash-prevention script.\n\n### Dashboard\n\n[`app/Livewire/Admin/Dashboard.php:9`](/Users/wesner/Herd/shop/app/Livewire/Admin/Dashboard.php:9) and [`resources/views/livewire/admin/dashboard.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/dashboard.blade.php:1):\n\n- Missing date-range state and custom date inputs.\n- Missing visitors, period comparisons, chart data, top-products data, and conversion-funnel data.\n- No Chart.js chart, loading overlays, empty/error states, or trend badges.\n- Displays product count instead of the required visitors KPI.\n\n### Products list and form\n\n[`app/Livewire/Admin/Products/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Products/Index.php), [`resources/views/livewire/admin/products/index.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/products/index.blade.php:1), [`app/Livewire/Admin/Products/Form.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Products/Form.php), and [`resources/views/livewire/admin/products/form.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/products/form.blade.php:1):\n\n- Missing type filter, selected IDs, select-all, sortable columns, bulk archive/delete/activate actions, and confirmation modal.\n- Missing image, inventory, type, vendor, updated columns and required status badges.\n- Form lacks handle, tags, publish date, collections, options, variant matrix, media uploads/reordering/alt text, SEO section, sticky save bar, discard/delete actions, and loading states.\n- Uses a single default price instead of editable variants.\n- No Flux field/input/select/modal mapping required by the spec.\n\n### Collections\n\n[`app/Livewire/Admin/Collections/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Collections/Index.php), [`Create.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Collections/Create.php), [`Edit.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Collections/Edit.php), and [`resources/views/livewire/admin/collections/`](/Users/wesner/Herd/shop/resources/views/livewire/admin/collections/):\n\n- `Index`, `Create`, and `Edit` only inherit the generic placeholder [`app/Livewire/Admin/Section.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Section.php).\n- Required `Admin\\Collections\\Form` class/view is absent.\n- No search/filter, pagination, deletion modal, product assignment, product search, ordering, or save behavior.\n\n### Inventory\n\n[`app/Livewire/Admin/Inventory/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Inventory/Index.php) and [`resources/views/livewire/admin/inventory/index.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/inventory/index.blade.php:1):\n\n- Placeholder only.\n- No search, stock filter, pagination, inline quantity editing, reserved quantity, policy badge, or actions.\n\n### Orders\n\n[`app/Livewire/Admin/Orders/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Orders/Index.php), [`resources/views/livewire/admin/orders/index.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/orders/index.blade.php:1), [`app/Livewire/Admin/Orders/Show.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Orders/Show.php), and [`resources/views/livewire/admin/orders/show.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/orders/show.blade.php:1):\n\n- List lacks search, required status tabs, correct status taxonomy, sorting, fulfillment status, and responsive table behavior.\n- Detail lacks timeline, addresses, payment details, fulfillment tracking fields, fulfillment/refund modals, line selection, partial quantities, shipped/delivered actions, and required callouts.\n- Required action names such as `createFulfillment()`, `markAsShipped()`, `markAsDelivered()`, and `createRefund()` are absent.\n- Current fulfillment action fulfills every order line at once.\n\n### Customers\n\n[`app/Livewire/Admin/Customers/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Customers/Index.php), [`resources/views/livewire/admin/customers/index.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/customers/index.blade.php:1), [`app/Livewire/Admin/Customers/Show.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Customers/Show.php), and [`resources/views/livewire/admin/customers/show.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/customers/show.blade.php:1):\n\n- Missing total spent aggregate and required table columns.\n- Detail lacks customer information card, marketing status, paginated order history, address CRUD, address modal, default-address action, edit/delete controls, and responsive layout.\n\n### Discounts\n\n[`app/Livewire/Admin/Discounts/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Discounts/Index.php), [`resources/views/livewire/admin/discounts/index.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/discounts/index.blade.php:1), [`app/Livewire/Admin/Discounts/Form.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php), and [`resources/views/livewire/admin/discounts/form.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/discounts/form.blade.php:1):\n\n- List lacks search, status filter, pagination, type, usage, and date columns.\n- Form lacks automatic/code selection, generate-code action, minimum purchase, product/collection eligibility, usage limits, one-per-customer, dates, active toggle, and search selectors.\n- No sticky save bar, discard action, or confirmation behavior.\n\n### Settings\n\n[`app/Livewire/Admin/Settings/General.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Settings/General.php), [`Shipping.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Settings/Shipping.php), [`Taxes.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Settings/Taxes.php), and their views:\n\n- General settings only edit store name; currency, locale, timezone, and immutable handle are absent.\n- Required `Admin\\Settings\\Domains` class/view is missing.\n- Required General/Domains/Shipping/Taxes/Checkout/Notifications tab UI is missing.\n- Shipping lacks zone/rate CRUD, country checklist, dynamic rate configuration, active toggles, modals, deletion, and test-address tool.\n- Taxes lacks manual/provider modes, provider/API fields, manual-rate rows, and tax-inclusive toggle.\n\n### Themes, pages, navigation, apps, developers, analytics, search\n\nThese routes point to placeholder components:\n\n[`app/Livewire/Admin/Section.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Section.php), [`resources/views/livewire/admin/section.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/admin/section.blade.php:1), and the following classes/views:\n\n- Themes: [`app/Livewire/Admin/Themes/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Themes/Index.php), [`Editor.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Themes/Editor.php)\n- Pages: [`app/Livewire/Admin/Pages/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Pages/Index.php), [`Create.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Pages/Create.php), [`Edit.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Pages/Edit.php)\n- Navigation: [`app/Livewire/Admin/Navigation/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Navigation/Index.php)\n- Apps: [`app/Livewire/Admin/Apps/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Apps/Index.php), [`Show.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Apps/Show.php)\n- Developers: [`app/Livewire/Admin/Developers/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Developers/Index.php)\n- Analytics: [`app/Livewire/Admin/Analytics/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Analytics/Index.php)\n- Search settings: [`app/Livewire/Admin/Search/Settings.php`](/Users/wesner/Herd/shop/app/Livewire/Admin/Search/Settings.php)\n\nAll lack the specified properties, actions, forms, tables, modals, loading states, and data interactions.\n\n### Admin route mismatches\n\n[`routes/web.php:117-131`](/Users/wesner/Herd/shop/routes/web.php:117):\n\n- Spec names such as `admin.collections.index`, `admin.inventory.index`, `admin.themes.index`, `admin.pages.index`, `admin.navigation.index`, `admin.apps.index`, `admin.analytics.index`, and `admin.developers.index` are not used.\n- Current names are `admin.collections`, `admin.inventory`, `admin.themes`, `admin.pages`, `admin.navigation`, `admin.apps`, `admin.analytics`, and `admin.developers`.\n\n## Storefront UI gaps\n\n### Architecture, theme, and currency\n\n[`resources/views/layouts/storefront.blade.php`](/Users/wesner/Herd/shop/resources/views/layouts/storefront.blade.php), [`app/Livewire/Storefront/Home.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Home.php), and [`resources/views/livewire/storefront/home.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/home.blade.php):\n\n- No active-theme settings service, cache, section schema, or configurable section order/visibility.\n- Announcement, header, footer, palette, typography, sticky-header, dark-mode, and social settings are hard-coded.\n- No shared currency/price helper. Prices are hard-coded as `€` with `number_format()` in multiple views instead of locale-aware `24.99 EUR` formatting.\n- Only [`resources/views/components/storefront/product-card.blade.php`](/Users/wesner/Herd/shop/resources/views/components/storefront/product-card.blade.php) exists from the required reusable component library.\n\n### Base layout\n\n[`resources/views/layouts/storefront.blade.php:1-41`](/Users/wesner/Herd/shop/resources/views/layouts/storefront.blade.php):\n\n- No page-specific meta description, app-name title suffix, Open Graph tags, JSON-LD, or smooth-scroll behavior.\n- `lang=\"en\"` is hard-coded.\n- Skip link says “Skip to content” rather than the specified “Skip to main content.”\n- Announcement bar is always visible, hard-coded, has no configured link, dismiss control, or `localStorage` persistence.\n- Desktop navigation is hard-coded and has no theme-driven menu or dropdown submenus.\n- Mobile navigation has no hamburger, drawer, backdrop, close behavior, accordion submenus, focus trap, or account placement.\n- Cart is a normal link; there is no cart count badge or drawer trigger.\n- Search is a normal link; `Search\\Modal` is not mounted in the layout.\n- Footer lacks configured footer menus, social links, payment icons, address/contact details, and Organization structured data.\n- No dark-mode toggle, persistence, system-preference initialization, or forced theme mode.\n\n### Home page\n\n[`app/Livewire/Storefront/Home.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Home.php) and [`resources/views/livewire/storefront/home.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/home.blade.php):\n\n- Sections are fixed inline rather than theme-configurable.\n- No `FeaturedProducts` lazy-loading component or skeleton state.\n- No `NewsletterSignup` Livewire component; the form posts to `#` and has no success/error/loading behavior.\n- Hero has no configured background image/gradient/overlay and does not implement the specified responsive heights.\n- Collection cards lack images, 3:4 aspect ratio, overlays, and configured card content.\n- Rich-text section is missing.\n- No theme-driven sanitized rich content.\n\n### Collections and product cards\n\n[`app/Livewire/Storefront/Collections/Show.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Collections/Show.php), [`resources/views/livewire/storefront/collections/show.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/collections/show.blade.php), and [`resources/views/components/storefront/product-card.blade.php`](/Users/wesner/Herd/shop/resources/views/components/storefront/product-card.blade.php):\n\n- Collection filters only support sort and in-stock; price, product type, vendor, filter pills, clear-all logic, and filter drawer/sidebar are missing.\n- No best-selling sort or pagination.\n- Collection description is plain text rather than configured sanitized prose.\n- Product cards lack secondary hover images, quick add/choose-options behavior, proper fallback icon, heading-level prop, shared price/badge components, and complete accessibility labels.\n- Sale/compare-at handling is not fully spec-compliant.\n\n### Product page\n\n[`app/Livewire/Storefront/Products/Show.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Products/Show.php) and [`resources/views/livewire/storefront/products/show.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/products/show.blade.php):\n\n- Missing breadcrumbs, sticky image column, image zoom, thumbnail selection, variant-specific media, mobile gallery scrolling, dots, and labeled image region.\n- Thumbnails are buttons but do not change the selected image or expose current state.\n- Variant matching uses `str_contains()` against variant titles rather than an option matrix.\n- No selected/unavailable option states, color swatches, large-option select fallback, or live variant mapping.\n- Missing compare-at price, sale badge, in-stock/low-stock messaging, live price/stock regions, and tags.\n- Quantity selector lacks increment/decrement buttons, max logic, and policy-aware limits.\n- Add-to-cart lacks loading text/spinner and only dispatches `cart-updated` without the specified payload.\n- Product description is not rendered with the required prose/sanitization behavior.\n\n### Cart drawer and cart page\n\n[`app/Livewire/Storefront/Cart/Show.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Cart/Show.php), [`resources/views/livewire/storefront/cart/show.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/cart/show.blade.php), and the layout:\n\n- Required `CartDrawer` component is absent.\n- No global drawer, backdrop, slide animation, scroll lock, focus trap, Escape handling, focus restoration, or browser-event listeners.\n- Full cart lacks product thumbnails, proper desktop table headers, discount totals, discount removal, loading states, and responsive card/table variants.\n- Quantity buttons are only 32px (`h-8 w-8`), below the 44px touch-target requirement.\n- Cart totals display subtotal as total and do not render the required discount breakdown.\n\n### Checkout\n\n[`app/Livewire/Storefront/Checkout/Show.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Checkout/Show.php) and [`resources/views/livewire/storefront/checkout/show.blade.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Checkout/Show.php):\n\n- Contact and address are combined; email is not rendered as a checkout field.\n- No collapsible step state, completed/future states, edit links, or focus movement.\n- Address lacks line 2, phone, billing address, saved addresses, country-dependent regions, labels, required attributes, and blur validation.\n- Shipping options lack delivery estimates, accessible fieldset/legend, no-method warning, and full summary updates.\n- Payment UI only supports a card number; cardholder, expiry, CVC, PayPal content, and bank-transfer content are missing.\n- No payment-specific button text, formatting, validation, processing state, or inline decline callout.\n- Order summary lacks discount input, subtotal/discount/shipping/tax rows, mobile collapse, and sticky desktop behavior.\n- Most inputs use placeholders without associated labels.\n\n### Confirmation\n\n[`app/Livewire/Storefront/Checkout/Confirmation.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Checkout/Confirmation.php) and [`resources/views/livewire/storefront/checkout/confirmation.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/checkout/confirmation.blade.php):\n\n- Only order number, total, payment status, and continue-shopping link are shown.\n- Missing email notice, line-item summary, thumbnails, shipping/billing address, payment method details, view-order link, and bank-transfer instructions.\n\n### Customer accounts\n\nCurrent routes exist in [`routes/web.php:71-88`](/Users/wesner/Herd/shop/routes/web.php:71), but the implementations are partial:\n\n- Login lacks autocomplete attributes and a general error callout.\n- Registration lacks marketing opt-in, labels, and complete inline validation.\n- Dashboard lacks quick-link descriptions, recent orders, status badges, and order actions.\n- Order history is unpaginated, lacks mobile cards, action links, status badges, and table scopes.\n- Order detail lacks breadcrumbs, placed date, addresses, payment information, totals breakdown, and fulfillment tracking.\n- Address book only creates addresses; edit, delete, default selection, modal behavior, full fields, and reusable address form are missing.\n\nRelevant files include [`resources/views/livewire/storefront/account/auth/login.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/account/auth/login.blade.php), [`register.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/account/auth/register.blade.php), [`dashboard.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/account/dashboard.blade.php), [`orders/index.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/account/orders/index.blade.php), [`orders/show.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/account/orders/show.blade.php), and [`addresses/index.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/account/addresses/index.blade.php).\n\n### Search\n\n[`app/Livewire/Storefront/Search/Modal.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Search/Modal.php), [`resources/views/livewire/storefront/search/modal.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/search/modal.blade.php), and [`app/Livewire/Storefront/Search/Index.php`](/Users/wesner/Herd/shop/app/Livewire/Storefront/Search/Index.php):\n\n- Modal is not included in the global layout.\n- Results contain products only; collections, grouping, max-five limits, “view all,” skeletons, no-results text, listbox semantics, arrow-key navigation, Enter selection, Escape behavior, and focus trap are missing.\n- Search results page lacks collection-page filters, sort controls, active filter pills, and the required empty-state wording.\n\n### Content and errors\n\n- [`resources/views/livewire/storefront/pages/show.blade.php:1`](/Users/wesner/Herd/shop/resources/views/livewire/storefront/pages/show.blade.php:1) renders HTML with `{!! !!}` and has no breadcrumbs, dynamic metadata, or verified sanitized `body_html` contract.\n- No `resources/views/errors/404.blade.php`.\n- No `resources/views/errors/503.blade.php`.\n- [`resources/views/livewire/storefront/home-fallback.blade.php`](/Users/wesner/Herd/shop/resources/views/livewire/s…8603 tokens truncated…eme keyed by `theme_id` with `settings_json` ([create_shop_schema.php:199](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:199)).\n  - `navigation_menus` uses `name`, while the spec requires `title`; `navigation_items.menu_id` is only a nullable alias without the required foreign key ([create_shop_schema.php:220](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:220), [add_remaining_schema_contract_columns.php:46](/Users/wesner/Herd/shop/database/migrations/2026_08_20_223045_add_remaining_schema_contract_columns.php:46)).\n  - `search_queries` lacks required `filters_json` ([create_shop_schema.php:250](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:250)).\n  - `customers` uses required `first_name`/`last_name` and lacks `name` and `marketing_opt_in` ([create_shop_schema.php:313](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:313)).\n  - `order_lines.title_snapshot` and `total_amount` remain nullable, contrary to the required non-null snapshots ([create_shop_schema.php:414](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:414), [add_remaining_schema_contract_columns.php:59](/Users/wesner/Herd/shop/database/migrations/2026_08_20_223045_add_remaining_schema_contract_columns.php:59)).\n  - `refunds.payment_id` is nullable with `SET NULL`; the spec requires non-null with cascade deletion ([create_shop_schema.php:447](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:447)).\n  - OAuth tables use different relationships and column names: `oauth_clients` is store-scoped instead of app-scoped, and `oauth_tokens` lacks the specified installation/access-token/refresh-token fields ([create_shop_schema.php:528](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:528)).\n  - Webhook compatibility columns are nullable aliases without foreign keys; the signing-secret and delivery contract does not match the spec ([create_shop_schema.php:549](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:549), [add_remaining_schema_contract_columns.php:81](/Users/wesner/Herd/shop/database/migrations/2026_08_20_223045_add_remaining_schema_contract_columns.php:81)).\n\n### P1 API gaps\n\n- **Large portions of the specified API are absent.**  \n  Actual API routes contain only catalog, collections, orders/customers/discount reads, cart, checkout, pay, and one analytics endpoint ([routes/api.php:10](/Users/wesner/Herd/shop/routes/api.php:10), [routes/api.php:26](/Users/wesner/Herd/shop/routes/api.php:26)).\n\n  Missing or incomplete areas include:\n\n  - Storefront order lookup with signed token.\n  - Storefront search and autocomplete endpoints.\n  - Batch analytics ingestion.\n  - Checkout discount removal.\n  - Admin platform organizations/stores/invites/me.\n  - Product media upload.\n  - Order fulfillment and refund mutations.\n  - Discount create/update/delete.\n  - Shipping zones/rates.\n  - Tax settings.\n  - Themes.\n  - Pages.\n  - Search reindex/status.\n  - Analytics summary and exports.\n  - OAuth and app endpoints.\n\n- **Storefront API validation and response contracts diverge.**  \n  Analytics accepts one event rather than the required batch payload ([StorefrontAnalyticsController.php:14](/Users/wesner/Herd/shop/app/Http/Controllers/Api/StorefrontAnalyticsController.php:14)). Payment card fields are nullable and `card_holder` is not validated ([StorefrontCheckoutController.php:83](/Users/wesner/Herd/shop/app/Http/Controllers/Api/StorefrontCheckoutController.php:83)). Business errors use `code`, while the spec requires `error_code` and specific status mappings ([StorefrontCartController.php:45](/Users/wesner/Herd/shop/app/Http/Controllers/Api/StorefrontCartController.php:45)).\n\n- **Rate limiting is incomplete.**  \n  Only `login`, `api.storefront`, `api.admin`, and `checkout` are defined ([AppServiceProvider.php:48](/Users/wesner/Herd/shop/app/Providers/AppServiceProvider.php:48)). Required `search`, `analytics`, and `webhooks` limiters are missing. Analytics incorrectly uses `api.storefront` ([routes/api.php:23](/Users/wesner/Herd/shop/routes/api.php:23)).\n\n- **Admin route authorization is too broad.**  \n  The parent admin group allows every role ([routes/web.php:102](/Users/wesner/Herd/shop/routes/web.php:102)); themes, apps, navigation, and developers have no stricter route middleware. `ThemePolicy` and `PagePolicy` also permit all roles for read operations, contrary to the matrix ([ThemePolicy.php:14](/Users/wesner/Herd/shop/app/Policies/ThemePolicy.php:14), [PagePolicy.php:14](/Users/wesner/Herd/shop/app/Policies/PagePolicy.php:14)). `NavigationMenuPolicy` is absent.\n\n- **Secondary admin routes are largely placeholder implementations.**  \n  Inventory, collections, themes, pages, navigation, apps, developers, analytics, and search components simply extend the generic `Section` component ([Inventory/Index.php:5](/Users/wesner/Herd/shop/app/Livewire/Admin/Inventory/Index.php:5), [Section.php:16](/Users/wesner/Herd/shop/app/Livewire/Admin/Section.php:16)). They expose summary rows, not the specified management workflows.\n\n### P1 authentication/security gaps\n\n- No dedicated `CustomerAuthenticate` middleware exists; account routes use Laravel’s generic `auth:customer` ([routes/web.php:83](/Users/wesner/Herd/shop/routes/web.php:83)). The required intended-URL redirect behavior is not implemented.\n- Admin/customer login messages are “These credentials do not match our records,” not the specified generic “Invalid credentials” ([Admin Login.php:20](/Users/wesner/Herd/shop/app/Livewire/Admin/Auth/Login.php:20)).\n- Login attempts do not explicitly reject disabled users.\n- The `login` limiter is redefined by Fortify as email+IP rather than IP-only ([FortifyServiceProvider.php:66](/Users/wesner/Herd/shop/app/Providers/FortifyServiceProvider.php:66)).\n- No Form Request classes exist. Controllers and Livewire components use inline validation; this fails the required Form Request inventory and authorization-before-validation design.\n- Sanitization is implemented in `HtmlSanitizer`, but it is not the specified action and its allowed tag list omits several required tags such as `img`, tables, `div`, and `span` ([HtmlSanitizer.php:13](/Users/wesner/Herd/shop/app/Support/HtmlSanitizer.php:13)).\n- Webhook signatures are incorrect: the implementation signs only the JSON body, while the spec requires signing `timestamp.json_body` ([WebhookService.php:26](/Users/wesner/Herd/shop/app/Services/WebhookService.php:26), [DeliverWebhook.php:33](/Users/wesner/Herd/shop/app/Jobs/DeliverWebhook.php:33)).\n- Required audit logging is absent: no `audit` channel, `AuditLogger`, auth listeners, or resource observers ([config/logging.php:53](/Users/wesner/Herd/shop/config/logging.php:53)).\n- Session settings diverge: encryption is disabled by default and the cookie defaults to `laravel-session`, not `shop_session` ([config/session.php:35](/Users/wesner/Herd/shop/config/session.php:35), [config/session.php:50](/Users/wesner/Herd/shop/config/session.php:50), [config/session.php:130](/Users/wesner/Herd/shop/config/session.php:130)).\n- `config/cors.php` is missing, so the required CORS policy and exposed rate-limit headers are not explicitly configured.\n\n### Schema/index completeness\n\nThe live SQLite schema also lacks several indexes explicitly required by the spec, including standalone indexes for product options, variants, collections, media, pages, navigation menus, payments, refunds, fulfillments, app installations, OAuth tokens, and webhook delivery retry timestamps. The migration generally uses plain `foreignId()` without adding the required indexes ([create_shop_schema.php:88](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:88), [create_shop_schema.php:414](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:414), [create_shop_schema.php:561](/Users/wesner/Herd/shop/database/migrations/2026_08_20_000000_create_shop_schema.php:561)).\n\nSQLite connection settings are mostly present—foreign keys, WAL, busy timeout, synchronous mode, and deferred transactions ([config/database.php:34](/Users/wesner/Herd/shop/config/database.php:34))—but the required `cache_size=-20000` pragma is not configured.\n\n### Test evidence\n\nThe suite contains tests for the basic commerce flow, tenancy, customer login, and only two admin API behaviors ([AdminApiTest.php:17](/Users/wesner/Herd/shop/tests/Feature/AdminApiTest.php:17)). There are no tests for Sanctum tokens/abilities, most specified endpoints, CORS, audit logging, role matrix coverage, OAuth, reset-token tenant isolation, or webhook timestamp signing.\n\nDuring this audit, `php artisan test --compact` did not produce a green baseline; repeated runs emitted `Cannot redeclare App\\Models\\User::stores()`, and an isolated Fortify login test failed with an unresolvable `LoginViewResponse` binding."}},{"previous_status":{"completed":"# Audit result\n\nThe implementation is a partial happy-path build, not yet compliant with specs 05, 07, or 08. The largest risks are incorrect payment/fulfillment state transitions, incomplete seed fixtures, absent browser tests, and unwired event-driven behavior.\n\n## Critical gaps\n\n### Business logic — payment, order, fulfillment\n\n- Creating a pending fulfillment can immediately mark an order as fulfilled because fulfillment lines are counted regardless of shipment status. This violates the requirement that orders become fulfilled only after shipment.  \n  Evidence: [FulfillmentService.php:18](/Users/wesner/Herd/shop/app/Services/FulfillmentService.php:18), [FulfillmentService.php:82](/Users/wesner/Herd/shop/app/Services/FulfillmentService.php:82)\n\n- `markAsDelivered()` unconditionally sets the order status to `fulfilled`, even when other order lines remain unfulfilled. It also does not update `fulfillment_status`.  \n  Evidence: [FulfillmentService.php:61](/Users/wesner/Herd/shop/app/Services/FulfillmentService.php:61)\n\n- Bank-transfer cancellation only changes the order status and releases inventory. It does not set financial status to `voided` or payment status to `failed`, as required.  \n  Evidence: [OrderService.php:110](/Users/wesner/Herd/shop/app/Services/OrderService.php:110), [CancelUnpaidBankTransferOrders.php:17](/Users/wesner/Herd/shop/app/Jobs/CancelUnpaidBankTransferOrders.php:17)\n\n- Confirming a bank transfer commits inventory but does not auto-fulfill all-digital orders.  \n  Evidence: [OrderService.php:129](/Users/wesner/Herd/shop/app/Services/OrderService.php:129)\n\n- Refunds do not update the payment record to `refunded`; line-level refund quantities are not tracked across previous refunds, allowing repeated refunds/restocks for the same line quantity.  \n  Evidence: [RefundService.php:21](/Users/wesner/Herd/shop/app/Services/RefundService.php:21), [RefundService.php:61](/Users/wesner/Herd/shop/app/Services/RefundService.php:61)\n\n- Order line tax snapshots contain the entire checkout tax-line array for every line instead of line-specific tax allocations.  \n  Evidence: [OrderService.php:57](/Users/wesner/Herd/shop/app/Services/OrderService.php:57)\n\n- Order numbering always uses `#` and ignores the configurable prefix requirement.  \n  Evidence: [OrderService.php:103](/Users/wesner/Herd/shop/app/Services/OrderService.php:103)\n\n### Event/listener architecture is largely absent\n\nThe spec requires listeners for checkout events, order events, product events, fulfillment events, webhooks, emails, analytics, and inventory side effects. There is no `app/Listeners` directory, and the existing events are dispatched directly without registered side effects.\n\nExamples:\n\n- Checkout address/shipping/completion/expiration events are not dispatched from [CheckoutService.php:29](/Users/wesner/Herd/shop/app/Services/CheckoutService.php:29).\n- Webhooks are not automatically triggered by domain events; they require manual calls to [WebhookService.php:11](/Users/wesner/Herd/shop/app/Services/WebhookService.php:11).\n- Webhook delivery uses the encrypted secret value directly and omits required headers such as `Content-Type`.  \n  Evidence: [DeliverWebhook.php:28](/Users/wesner/Herd/shop/app/Jobs/DeliverWebhook.php:28)\n\n### Analytics API contract is incorrect\n\nThe spec requires batches of events with `type`, `session_id`, `client_event_id`, `occurred_at`, and `properties`. The controller accepts one event only and does not validate or store the required client session/timestamp fields.\n\n- Evidence: [StorefrontAnalyticsController.php:14](/Users/wesner/Herd/shop/app/Http/Controllers/Api/StorefrontAnalyticsController.php:14)\n- The configured storefront limit is 120 requests/minute, not the required 60.  \n  Evidence: [AppServiceProvider.php:48](/Users/wesner/Herd/shop/app/Providers/AppServiceProvider.php:48)\n- Aggregation counts page views rather than distinct sessions and derives orders/revenue from the orders table rather than `checkout_completed` event properties.  \n  Evidence: [AggregateAnalytics.php:21](/Users/wesner/Herd/shop/app/Jobs/AggregateAnalytics.php:21)\n\n### Media processing is a stub\n\n`ProcessMediaUpload` only changes the media status to `ready`; it does not resize images, generate WebP variants, populate dimensions/byte size, preserve originals, or enforce the required retry behavior.\n\nEvidence: [ProcessMediaUpload.php:18](/Users/wesner/Herd/shop/app/Jobs/ProcessMediaUpload.php:18)\n\n## High-priority business logic gaps\n\n- Product creation creates variants but does not automatically create inventory records.  \n  Evidence: [ProductService.php:19](/Users/wesner/Herd/shop/app/Services/ProductService.php:19), [ProductVariant.php:11](/Users/wesner/Herd/shop/app/Models/ProductVariant.php:11)\n\n- Variant matrix rebuild leaves orphaned variants with order history untouched instead of archiving them.  \n  Evidence: [VariantMatrixService.php:38](/Users/wesner/Herd/shop/app/Services/VariantMatrixService.php:38)\n\n- Cart cleanup marks carts abandoned but does not release reservations from active checkouts.  \n  Evidence: [CleanupAbandonedCarts.php:15](/Users/wesner/Herd/shop/app/Jobs/CleanupAbandonedCarts.php:15)\n\n- Guest-cart merge clears only the store-specific session key, not the required plain `cart_id` key.  \n  Evidence: [CartService.php:154](/Users/wesner/Herd/shop/app/Services/CartService.php:154)\n\n- Shipping-zone matching does not implement specificity or lowest-ID tie-breaking. Its `OR` conditions can return rates from unrelated zones.  \n  Evidence: [ShippingCalculator.php:13](/Users/wesner/Herd/shop/app/Services/ShippingCalculator.php:13)\n\n- Unmatched weight/price ranges fall back to the rate base price instead of making the rate unavailable.  \n  Evidence: [ShippingCalculator.php:43](/Users/wesner/Herd/shop/app/Services/ShippingCalculator.php:43)\n\n- Discount usage increment is case-sensitive even though validation is case-insensitive.  \n  Evidence: [PaymentService.php:48](/Users/wesner/Herd/shop/app/Services/PaymentService.php:48)\n\n- No `TaxProvider` contract, manual provider, or Stripe Tax stub exists; tax is implemented directly in `TaxCalculator`.  \n  Evidence: [TaxCalculator.php:9](/Users/wesner/Herd/shop/app/Services/TaxCalculator.php:9)\n\n## Seeders and test data\n\nThe required fixture dataset is not present.\n\n- `DatabaseSeeder` calls only `ShopSeeder`; the other required seeders are empty.  \n  Evidence: [DatabaseSeeder.php:12](/Users/wesner/Herd/shop/database/seeders/DatabaseSeeder.php:12), [OrganizationSeeder.php:12](/Users/wesner/Herd/shop/database/seeders/OrganizationSeeder.php:12)\n\n- `ShopSeeder` creates approximately:\n  - 1 store instead of 2\n  - 5 products instead of 20\n  - 1 customer instead of 10\n  - 1 order instead of 15\n  - 1 page instead of 5\n  - 1 shipping zone instead of domestic/international coverage\n  - no analytics events/daily aggregates\n  - no search settings\n  - no electronics tenant\n\n  Evidence: [ShopSeeder.php:35](/Users/wesner/Herd/shop/database/seeders/ShopSeeder.php:35), [ShopSeeder.php:57](/Users/wesner/Herd/shop/database/seeders/ShopSeeder.php:57), [ShopSeeder.php:75](/Users/wesner/Herd/shop/database/seeders/ShopSeeder.php:75)\n\n- Seed values contradict the specification:\n  - Customer is “Jamie Customer” instead of John Doe.\n  - `WELCOME10` starts with usage count 0 instead of 3.\n  - `MAXED` uses limit/count 1/1 instead of 5/5.\n  - `FREESHIP` usage count is 0 instead of 1.\n  - Seeded order `#1001` totals 2998 with zero tax instead of 5497 with tax.\n  \n  Evidence: [ShopSeeder.php:53](/Users/wesner/Herd/shop/database/seeders/ShopSeeder.php:53), [ShopSeeder.php:65](/Users/wesner/Herd/shop/database/seeders/ShopSeeder.php:72), [ShopSeeder.php:75](/Users/wesner/Herd/shop/database/seeders/ShopSeeder.php:75)\n\n- Required factories are missing for cart lines, checkouts, order lines, payments, refunds, fulfillments, fulfillment lines, addresses, shipping zones/rates, themes, pages, navigation, and analytics events.\n\n- Existing factories lack most required states. For example, `ProductFactory` only has `draft`, and `ProductVariantFactory` has no digital, sale, archived, or default states.  \n  Evidence: [ProductFactory.php:13](/Users/wesner/Herd/shop/database/factories/ProductFactory.php:13), [ProductVariantFactory.php:12](/Users/wesner/Herd/shop/database/factories/ProductVariantFactory.php:12)\n\n## Playwright/Pest E2E plan\n\nThe required browser suite is not implemented.\n\n- `tests/Browser` does not exist.\n- `phpunit.xml` registers only Unit and Feature suites.  \n  Evidence: [phpunit.xml:7](/Users/wesner/Herd/shop/phpunit.xml:7)\n- There is no `.env.testing` with the browser configuration described by the spec.\n- None of the 18 suites / 143 browser tests are present.\n\nThe browser-facing routes exist for many smoke pages, but the implementation cannot satisfy the planned interactions:\n\n- Admin order UI has buttons for confirm payment, create fulfillment, and refund, but no “Mark as shipped” or “Mark as delivered” actions.  \n  Evidence: [Show.php:24](/Users/wesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:24), [show.blade.php:1](/Users/wesner/Herd/shop/resources/views/livewire/admin/orders/show.blade.php:1)\n\n- Collections, pages, themes, navigation, analytics, apps, developers, and search settings mostly render a generic read-only `Section` placeholder.  \n  Evidence: [Section.php:16](/Users/wesner/Herd/shop/app/Livewire/Admin/Section.php:16), [Index.php:7](/Users/wesner/Herd/shop/app/Livewire/Admin/Collections/Index.php:7)\n\n- Product option selection chooses the first variant whose title contains a value, rather than selecting a valid cartesian option combination.  \n  Evidence: [products/show.blade.php:3](/Users/wesner/Herd/shop/resources/views/livewire/storefront/products/show.blade.php:3)\n\n- The storefront does not render compare-at pricing, a cart drawer, theme-driven content, or the complete checkout/payment form required by the plan.  \n  Evidence: [products/show.blade.php:3](/Users/wesner/Herd/shop/resources/views/livewire/storefront/products/show.blade.php:3), [checkout/show.blade.php:1](/Users/wesner/Herd/shop/resources/views/livewire/storefront/checkout/show.blade.php:1)\n\n## Verification note\n\nI did not edit files. `git status` remained limited to the pre-existing `boost.json` and `.playwright-mcp/` entries.\n\nA full Pest run was not a reliable source verdict because the generated Composer autoloader points to `/private/tmp/shop-merge.XZmomj` rather than this checkout. It loaded a different source snapshot and produced redeclaration errors. Independently, the repository still has zero browser test files, which is an unambiguous spec gap."}}]


exec

status: completed

input:
const tasks = [
  {
    message: "In the shop repo, implement the specified storefront checkout/cart completion from specs/04-STOREFRONT-UI.md within this disjoint write scope only: app/Livewire/Storefront/Checkout/**, app/Livewire/Storefront/Cart/Show.php, resources/views/livewire/storefront/checkout/**, resources/views/livewire/storefront/cart/show.blade.php. Improve the existing Livewire checkout flow with accessible labeled contact/address/payment fields, discount breakdown/removal UI, shipping/payment states, processing validation, and a useful order confirmation summary. Do not edit layouts, services, routes, tests outside this scope, or dependencies. Follow the project skills and conventions. Run focused existing tests if possible and report changed files.",
  },
  {
    message: "In the shop repo, implement specs/07-SEEDERS-AND-TEST-DATA.md in this disjoint write scope only: database/seeders/**, database/factories/**, and tests/Feature/SeedDataTest.php (create it with Artisan if needed). Build deterministic multi-tenant seed data matching the requested counts/fixtures as far as the current schema allows, fill meaningful factories/states for the listed commerce entities, and add focused Pest coverage. Do not edit application services, routes, views, migrations, or other tests. Follow project skills, use Artisan make commands for new files, and report changed files/tests.",
  },
  {
    message: "In the shop repo, implement missing admin management UX in this disjoint write scope only: app/Livewire/Admin/Orders/**, app/Livewire/Admin/Customers/**, app/Livewire/Admin/Discounts/**, app/Livewire/Admin/Settings/** and their matching resources/views/livewire/admin/{orders,customers,discounts,settings}/** views. Replace placeholders with functional searchable/paginated tables, detail/edit actions, forms, authorization, and accessible Flux/Tailwind UI based on specs/03-ADMIN-UI.md and existing services. Do not edit routes, services, layouts, APIs, tests outside your scope, or dependencies. Run focused tests if possible and report changed files.",
  }
];
const out = await Promise.all(tasks.map(message => tools.multi_agent_v1__spawn_agent({message, fork_context:true})));
text(out);


text:
Script failed
Wall time 0.0 seconds
Output:

text:
Script error:
failed to parse function arguments: invalid type: map, expected a string at line 1 column 11


status: completed

input:
const tasks = [
  "In the shop repo, implement the specified storefront checkout/cart completion from specs/04-STOREFRONT-UI.md within this disjoint write scope only: app/Livewire/Storefront/Checkout/**, app/Livewire/Storefront/Cart/Show.php, resources/views/livewire/storefront/checkout/**, resources/views/livewire/storefront/cart/show.blade.php. Improve the existing Livewire checkout flow with accessible labeled contact/address/payment fields, discount breakdown/removal UI, shipping/payment states, processing validation, and a useful order confirmation summary. Do not edit layouts, services, routes, tests outside this scope, or dependencies. Follow the project skills and conventions. Run focused existing tests if possible and report changed files.",
  "In the shop repo, implement specs/07-SEEDERS-AND-TEST-DATA.md in this disjoint write scope only: database/seeders/**, database/factories/**, and tests/Feature/SeedDataTest.php (create it with Artisan if needed). Build deterministic multi-tenant seed data matching the requested counts/fixtures as far as the current schema allows, fill meaningful factories/states for the listed commerce entities, and add focused Pest coverage. Do not edit application services, routes, views, migrations, or other tests. Follow project skills, use Artisan make commands for new files, and report changed files/tests.",
  "In the shop repo, implement missing admin management UX in this disjoint write scope only: app/Livewire/Admin/Orders/**, app/Livewire/Admin/Customers/**, app/Livewire/Admin/Discounts/**, app/Livewire/Admin/Settings/** and their matching resources/views/livewire/admin/{orders,customers,discounts,settings}/** views. Replace placeholders with functional searchable/paginated tables, detail/edit actions, forms, authorization, and accessible Flux/Tailwind UI based on specs/03-ADMIN-UI.md and existing services. Do not edit routes, services, layouts, APIs, tests outside your scope, or dependencies. Run focused tests if possible and report changed files."
];
const out = await Promise.all(tasks.map(message => tools.multi_agent_v1__spawn_agent({message, fork_context:true})));
text(out);


type: CollabAgentToolCall

status: completed

agent_nickname: Singer

prompt: In the shop repo, implement the specified storefront checkout/cart completion from specs/04-STOREFRONT-UI.md within this disjoint write scope only: app/Livewire/Storefront/Checkout/**, app/Livewire/Storefront/Cart/Show.php, resources/views/livewire/storefront/checkout/**, resources/views/livewire/storefront/cart/show.blade.php. Improve the existing Livewire checkout flow with accessible labeled contact/address/payment fields, discount breakdown/removal UI, shipping/payment states, processing validation, and a useful order confirmation summary. Do not edit layouts, services, routes, tests outside this scope, or dependencies. Follow the project skills and conventions. Run focused existing tests if possible and report changed files.

reasoning_effort: xhigh


type: CollabAgentToolCall

status: completed

agent_nickname: Banach

prompt: In the shop repo, implement specs/07-SEEDERS-AND-TEST-DATA.md in this disjoint write scope only: database/seeders/**, database/factories/**, and tests/Feature/SeedDataTest.php (create it with Artisan if needed). Build deterministic multi-tenant seed data matching the requested counts/fixtures as far as the current schema allows, fill meaningful factories/states for the listed commerce entities, and add focused Pest coverage. Do not edit application services, routes, views, migrations, or other tests. Follow project skills, use Artisan make commands for new files, and report changed files/tests.

reasoning_effort: xhigh


type: CollabAgentToolCall

status: completed

agent_nickname: Copernicus

prompt: In the shop repo, implement missing admin management UX in this disjoint write scope only: app/Livewire/Admin/Orders/**, app/Livewire/Admin/Customers/**, app/Livewire/Admin/Discounts/**, app/Livewire/Admin/Settings/** and their matching resources/views/livewire/admin/{orders,customers,discounts,settings}/** views. Replace placeholders with functional searchable/paginated tables, detail/edit actions, forms, authorization, and accessible Flux/Tailwind UI based on specs/03-ADMIN-UI.md and existing services. Do not edit routes, services, layouts, APIs, tests outside your scope, or dependencies. Run focused tests if possible and report changed files.

reasoning_effort: xhigh


text:
Script completed
Wall time 0.4 seconds
Output:


status: completed

input:
const r = await tools.update_plan({explanation:"Baseline is green and the first implementation tranche is underway; inventory audits are complete.",plan:[
  {step:"Inventory current implementation against specs 01–09 and identify unimplemented requirements",status:"completed"},
  {step:"Implement missing core API/auth, admin CRUD, storefront, business-rule behavior",status:"in_progress"},
  {step:"Add/expand Pest coverage",status:"pending"},
  {step:"Run full migration/tests/build/Playwright review",status:"pending"},
  {step:"Independent audit/fix/progress/commit",status:"pending"}
]});
text(r);


text:
Script completed
Wall time 0.0 seconds
Output:


status: completed

input:
const r = await tools.exec_command({cmd:"sed -n '1,260p' specs/02-API-ROUTES.md && sed -n '1,260p' specs/06-AUTH-AND-SECURITY.md","workdir":"/Users/wesner/Herd/shop","yield_time_ms":1000,"max_output_tokens":20000});
text(r.output);


type: CommandExecution

command: sed -n '1,260p' specs/02-API-ROUTES.md && sed -n '1,260p' specs/06-AUTH-AND-SECURITY.md

type: read

cmd: sed -n '1,260p' specs/02-API-ROUTES.md

status: completed

stdout:
# API and Routing Specification

This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.

All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.

#### Dashboard

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |

#### Products

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |

#### Inventory

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |

#### Collections

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |

#### Orders

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |

#### Customers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |

#### Discounts

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |

#### Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |

#### Themes and Online Store

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |

#### Pages

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |

#### Navigation

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |

#### Apps

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.

#### Analytics

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |

#### Search Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |

---

### 1.3 Storefront Routes

**Middleware:** `web`, `store.resolve`

These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.

| Method | Path | Description | Component / View |
|--------|------|-------------|------------------|
| GET | `/` | Home page | `Storefront\Home` |
| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |

---

## 2. Storefront REST API

**Prefix:** `/api/storefront/v1`
**Middleware:** `store.resolve`, `throttle:api.storefront`
**Content-Type:** `application/json`

These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).

---

### 2.1 Cart Endpoints

#### POST /api/storefront/v1/carts

Create a new cart.

- **Authentication:** None (guest) or session-based (logged-in customer)
- **Rate limit:** `api.storefront` (120/min per IP)

**Request body:**

```json
{
  "currency": "EUR"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |

**Success response:** `201 Created`

```json
{
  "id": 42,
  "store_id": 1,
  "customer_id": null,
  "currency": "EUR",
  "cart_version": 1,
  "status": "active",
  "lines": [],
  "totals": {
    "subtotal": 0,
    "discount": 0,
    "total": 0,
    "currency": "EUR"
  },
  "created_at": "2026-02-11T10:00:00Z",
  "updated_at": "2026-02-11T10:00:00Z"
}
```

**Error responses:**
- `422` - Validation error (invalid currency)

---

#### GET /api/storefront/v1/carts/{cartId}

Retrieve a cart with all lines and computed totals.

- **Authentication:** None (cart ID acts as token)
- **Rate limit:** `api.storefront` (120/min per IP)
# 06 - Authentication and Security Specification

> E-commerce platform - Auth, authorization, and security controls for Laravel 12 + Livewire v4 + SQLite

---

## 1. Authentication

The platform supports three distinct authentication mechanisms: session-based admin auth, session-based customer auth (store-scoped), and token-based API auth via Sanctum.

### 1.1 Admin Authentication

| Setting | Value |
|---------|-------|
| Guard name | `web` (default Laravel guard) |
| Provider name | `users` (Eloquent driver) |
| Model | `App\Models\User` |
| Driver | Session (cookie-based) |
| Login route | `GET /admin/login` |
| Login form | Livewire component `Admin\Auth\Login` |

#### Admin Login Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant LW as Livewire Component
    participant Auth as Auth Guard (web)
    participant DB as Database
    participant S as Session

    B->>LW: Submit email + password
    LW->>LW: Validate input (email required, password required)
    LW->>LW: Check rate limiter (5/min per IP)
    alt Rate limit exceeded
        LW-->>B: Flash "Too many attempts. Try again in X seconds."
    end
    LW->>Auth: attempt(credentials, remember)
    Auth->>DB: SELECT from users WHERE email = ?
    Auth->>Auth: Verify bcrypt hash
    alt Credentials valid
        Auth->>S: Regenerate session ID (prevent fixation)
        Auth-->>LW: Success
        LW-->>B: Redirect to /admin (dashboard)
    else Credentials invalid
        Auth-->>LW: Failure
        LW-->>B: Flash "Invalid credentials" (generic, never reveal which field)
    end
```

**Requirements:**

- The login form collects `email` and `password`.
- On successful login, the session MUST be regenerated to prevent session fixation attacks.
- On failed login, only the generic message "Invalid credentials" is shown. The system must not indicate whether the email or password was wrong.
- Rate limiting: maximum 5 attempts per minute, keyed by IP address. Uses the `login` rate limiter.
- After login: redirect to `/admin` (the dashboard).

#### Remember Me

- An optional "Remember me" checkbox on the login form.
- When enabled, Laravel sets a long-lived remember token cookie.
- The `remember_token` column on the `users` table stores the hashed token.
- Cookie name follows Laravel default convention: `remember_web_{hash}`.

#### Admin Password Reset Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant App as Application
    participant DB as Database
    participant Mail as Mail Service

    B->>App: GET /admin/forgot-password
    App-->>B: Render forgot-password form
    B->>App: POST /admin/forgot-password (email)
    App->>DB: Look up user by email
    alt User exists
        App->>DB: Store reset token in password_reset_tokens table
        App->>Mail: Send reset link email
    end
    App-->>B: "If that email exists, we sent a reset link." (generic)
    B->>App: GET /admin/reset-password/{token}
    App-->>B: Render reset-password form
    B->>App: POST /admin/reset-password (token, email, password, password_confirmation)
    App->>DB: Validate token (exists, not expired, matches email)
    alt Valid token
        App->>DB: Update user password (bcrypt hash)
        App->>DB: Delete used token
        App-->>B: Redirect to /admin/login with success flash
    else Invalid/expired token
        App-->>B: Error message
    end
```

**Requirements:**

- Uses Laravel's built-in password broker (`users` broker).
- Reset tokens stored in the `password_reset_tokens` table.
- Token expiration: 60 minutes (configurable in `config/auth.php` under `passwords.users.expire`).
- Throttle: one reset email per 60 seconds per email address.
- The "forgot password" response must be generic (do not reveal whether email exists).

#### Admin Logout

- Route: `POST /admin/logout` (via Livewire action or standard route).
- Steps: invalidate the entire session, regenerate the CSRF token, redirect to `/admin/login`.
- The response must not cache.

#### Session Lifetime

| Setting | Value |
|---------|-------|
| Lifetime | 120 minutes (configurable via `SESSION_LIFETIME` env var) |
| Expire on close | No (session persists until lifetime expires) |

---

### 1.2 Customer Authentication

| Setting | Value |
|---------|-------|
| Guard name | `customer` (custom guard) |
| Provider name | `customers` (Eloquent driver) |
| Model | `App\Models\Customer` |
| Driver | Session (cookie-based) |
| Login route | `GET /account/login` |
| Registration route | `GET /account/register` |

#### Customer Login Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant LW as Livewire Component
    participant Auth as Auth Guard (customer)
    participant Store as ResolveStore Middleware
    participant DB as Database

    Note over Store: Store already resolved from hostname
    B->>LW: Submit email + password
    LW->>LW: Validate input
    LW->>LW: Check rate limiter (5/min per IP)
    LW->>Auth: attempt(credentials + store_id, remember)
    Auth->>DB: SELECT from customers WHERE email = ? AND store_id = ?
    Auth->>Auth: Verify bcrypt hash
    alt Valid
        Auth-->>LW: Success (regenerate session)
        LW-->>B: Redirect to /account (or intended URL)
    else Invalid
        Auth-->>LW: Failure
        LW-->>B: Flash "Invalid credentials"
    end
```

**Requirements:**

- Customer login is always scoped to the current store. The store is resolved from the request hostname by the `ResolveStore` middleware before the login attempt.
- The custom `CustomerUserProvider` must inject `store_id` into the credential check so the query filters by both `email` and `store_id`.
- The same email address can exist in different stores (multi-tenant isolation).
- Rate limiting: maximum 5 attempts per minute per IP, using the same `login` rate limiter.
- After login: redirect to `/account` or the intended URL (if the customer was redirected from a protected page).

#### Customer Registration Flow

```mermaid
flowchart TD
    A[Customer visits /account/register] --> B[Fill form: name, email, password, password_confirmation, marketing_opt_in]
    B --> C{Validate via RegisterCustomerRequest}
    C -- Invalid --> D[Show validation errors]
    C -- Valid --> E[Create Customer record scoped to current store]
    E --> F[Auto-login via customer guard]
    F --> G[Redirect to /account]
```

**Registration fields:**

| Field | Type | Required | Validation |
|-------|------|:--------:|------------|
| name | string | Y | max:255 |
| email | string | Y | valid email, unique per store (composite unique on email + store_id) |
| password | string | Y | min:8, confirmed |
| password_confirmation | string | Y | must match password |
| marketing_opt_in | boolean | N | defaults to false |

#### Customer Store Scoping

- The `customers` auth provider requires a custom `CustomerUserProvider` class.
- This provider overrides the default Eloquent provider to always include `store_id` in credential queries.
- The `store_id` is obtained from the container-bound current store (resolved by `ResolveStore` middleware).
- This ensures that `Auth::guard('customer')->attempt()` never crosses store boundaries.

#### Customer Password Reset

- Uses a separate password broker: `customers` broker.
- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
- Token records include `store_id` to scope resets to the correct store.
- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
- Token expiration: 60 minutes.
- Throttle: one reset email per 60 seconds.
- Generic response on the "forgot password" form (do not reveal whether email exists in the store).

---

### 1.3 API Authentication (Sanctum)

| Setting | Value |
|---------|-------|
| Guard name | `sanctum` |
| Provider | `users` (admin users only) |
| Token type | Personal access tokens |
| Token prefix | `shop_` |
| Hashing | SHA-256 |
| Default expiration | 1 year |
| Storage | `personal_access_tokens` table |

#### Token Lifecycle

```mermaid
stateDiagram-v2
    [*] --> Created: Admin creates token in Developers section
    Created --> Active: Plain-text shown once, hash stored
    Active --> InUse: Token sent as Bearer header
    InUse --> Active: Request processed
    Active --> Revoked: Admin revokes token
    Active --> Expired: Expiration date reached
    Revoked --> [*]: Token row deleted
    Expired --> [*]: Token row deleted (cleanup job)
```

**Requirements:**

- Tokens are created in the admin panel, under the Developers section.
- Each token has: a human-readable name, an optional expiration date, and a list of abilities (scopes).
- On creation, the plain-text token (prefixed with `shop_`) is displayed exactly once. Only the SHA-256 hash is persisted.
- Tokens are sent as `Authorization: Bearer shop_xxxxxxxx`.
- Sanctum middleware validates the hash and resolves the associated user.
- Ability checks are performed per-endpoint (e.g., the products list endpoint requires `read-products`).
- Revoking a token deletes its row from the `personal_access_tokens` table.

#### Token Abilities (Scopes)

| Ability | Description |
|---------|-------------|
| `read-products` | List and view products |
| `write-products` | Create, update, delete products |
| `read-orders` | List and view orders |
| `write-orders` | Update orders, create fulfillments |
| `read-customers` | List and view customers |
| `write-customers` | Update customers |
| `read-collections` | List and view collections |
| `write-collections` | Create, update, delete collections |
| `read-discounts` | List and view discounts |
| `write-discounts` | Create, update, delete discounts |
| `read-analytics` | View analytics data |
| `read-settings` | View store settings (general, shipping, taxes, checkout, notifications) |
| `write-settings` | Update store settings (replaces the legacy `manage-store`, `manage-shipping`, `manage-taxes` scopes) |
| `read-themes` | View themes and theme files |
| `write-themes` | Create, update, delete, publish themes |


text:
Script completed
Wall time 0.1 seconds
Output:

text:
# API and Routing Specification

This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.

All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.

#### Dashboard

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |

#### Products

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |

#### Inventory

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |

#### Collections

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |

#### Orders

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |

#### Customers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |

#### Discounts

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |

#### Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |

#### Themes and Online Store

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |

#### Pages

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |

#### Navigation

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |

#### Apps

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.

#### Analytics

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |

#### Search Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |

---

### 1.3 Storefront Routes

**Middleware:** `web`, `store.resolve`

These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.

| Method | Path | Description | Component / View |
|--------|------|-------------|------------------|
| GET | `/` | Home page | `Storefront\Home` |
| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |

---

## 2. Storefront REST API

**Prefix:** `/api/storefront/v1`
**Middleware:** `store.resolve`, `throttle:api.storefront`
**Content-Type:** `application/json`

These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).

---

### 2.1 Cart Endpoints

#### POST /api/storefront/v1/carts

Create a new cart.

- **Authentication:** None (guest) or session-based (logged-in customer)
- **Rate limit:** `api.storefront` (120/min per IP)

**Request body:**

```json
{
  "currency": "EUR"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |

**Success response:** `201 Created`

```json
{
  "id": 42,
  "store_id": 1,
  "customer_id": null,
  "currency": "EUR",
  "cart_version": 1,
  "status": "active",
  "lines": [],
  "totals": {
    "subtotal": 0,
    "discount": 0,
    "total": 0,
    "currency": "EUR"
  },
  "created_at": "2026-02-11T10:00:00Z",
  "updated_at": "2026-02-11T10:00:00Z"
}
```

**Error responses:**
- `422` - Validation error (invalid currency)

---

#### GET /api/storefront/v1/carts/{cartId}

Retrieve a cart with all lines and computed totals.

- **Authentication:** None (cart ID acts as token)
- **Rate limit:** `api.storefront` (120/min per IP)
# 06 - Authentication and Security Specification

> E-commerce platform - Auth, authorization, and security controls for Laravel 12 + Livewire v4 + SQLite

---

## 1. Authentication

The platform supports three distinct authentication mechanisms: session-based admin auth, session-based customer auth (store-scoped), and token-based API auth via Sanctum.

### 1.1 Admin Authentication

| Setting | Value |
|---------|-------|
| Guard name | `web` (default Laravel guard) |
| Provider name | `users` (Eloquent driver) |
| Model | `App\Models\User` |
| Driver | Session (cookie-based) |
| Login route | `GET /admin/login` |
| Login form | Livewire component `Admin\Auth\Login` |

#### Admin Login Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant LW as Livewire Component
    participant Auth as Auth Guard (web)
    participant DB as Database
    participant S as Session

    B->>LW: Submit email + password
    LW->>LW: Validate input (email required, password required)
    LW->>LW: Check rate limiter (5/min per IP)
    alt Rate limit exceeded
        LW-->>B: Flash "Too many attempts. Try again in X seconds."
    end
    LW->>Auth: attempt(credentials, remember)
    Auth->>DB: SELECT from users WHERE email = ?
    Auth->>Auth: Verify bcrypt hash
    alt Credentials valid
        Auth->>S: Regenerate session ID (prevent fixation)
        Auth-->>LW: Success
        LW-->>B: Redirect to /admin (dashboard)
    else Credentials invalid
        Auth-->>LW: Failure
        LW-->>B: Flash "Invalid credentials" (generic, never reveal which field)
    end
```

**Requirements:**

- The login form collects `email` and `password`.
- On successful login, the session MUST be regenerated to prevent session fixation attacks.
- On failed login, only the generic message "Invalid credentials" is shown. The system must not indicate whether the email or password was wrong.
- Rate limiting: maximum 5 attempts per minute, keyed by IP address. Uses the `login` rate limiter.
- After login: redirect to `/admin` (the dashboard).

#### Remember Me

- An optional "Remember me" checkbox on the login form.
- When enabled, Laravel sets a long-lived remember token cookie.
- The `remember_token` column on the `users` table stores the hashed token.
- Cookie name follows Laravel default convention: `remember_web_{hash}`.

#### Admin Password Reset Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant App as Application
    participant DB as Database
    participant Mail as Mail Service

    B->>App: GET /admin/forgot-password
    App-->>B: Render forgot-password form
    B->>App: POST /admin/forgot-password (email)
    App->>DB: Look up user by email
    alt User exists
        App->>DB: Store reset token in password_reset_tokens table
        App->>Mail: Send reset link email
    end
    App-->>B: "If that email exists, we sent a reset link." (generic)
    B->>App: GET /admin/reset-password/{token}
    App-->>B: Render reset-password form
    B->>App: POST /admin/reset-password (token, email, password, password_confirmation)
    App->>DB: Validate token (exists, not expired, matches email)
    alt Valid token
        App->>DB: Update user password (bcrypt hash)
        App->>DB: Delete used token
        App-->>B: Redirect to /admin/login with success flash
    else Invalid/expired token
        App-->>B: Error message
    end
```

**Requirements:**

- Uses Laravel's built-in password broker (`users` broker).
- Reset tokens stored in the `password_reset_tokens` table.
- Token expiration: 60 minutes (configurable in `config/auth.php` under `passwords.users.expire`).
- Throttle: one reset email per 60 seconds per email address.
- The "forgot password" response must be generic (do not reveal whether email exists).

#### Admin Logout

- Route: `POST /admin/logout` (via Livewire action or standard route).
- Steps: invalidate the entire session, regenerate the CSRF token, redirect to `/admin/login`.
- The response must not cache.

#### Session Lifetime

| Setting | Value |
|---------|-------|
| Lifetime | 120 minutes (configurable via `SESSION_LIFETIME` env var) |
| Expire on close | No (session persists until lifetime expires) |

---

### 1.2 Customer Authentication

| Setting | Value |
|---------|-------|
| Guard name | `customer` (custom guard) |
| Provider name | `customers` (Eloquent driver) |
| Model | `App\Models\Customer` |
| Driver | Session (cookie-based) |
| Login route | `GET /account/login` |
| Registration route | `GET /account/register` |

#### Customer Login Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant LW as Livewire Component
    participant Auth as Auth Guard (customer)
    participant Store as ResolveStore Middleware
    participant DB as Database

    Note over Store: Store already resolved from hostname
    B->>LW: Submit email + password
    LW->>LW: Validate input
    LW->>LW: Check rate limiter (5/min per IP)
    LW->>Auth: attempt(credentials + store_id, remember)
    Auth->>DB: SELECT from customers WHERE email = ? AND store_id = ?
    Auth->>Auth: Verify bcrypt hash
    alt Valid
        Auth-->>LW: Success (regenerate session)
        LW-->>B: Redirect to /account (or intended URL)
    else Invalid
        Auth-->>LW: Failure
        LW-->>B: Flash "Invalid credentials"
    end
```

**Requirements:**

- Customer login is always scoped to the current store. The store is resolved from the request hostname by the `ResolveStore` middleware before the login attempt.
- The custom `CustomerUserProvider` must inject `store_id` into the credential check so the query filters by both `email` and `store_id`.
- The same email address can exist in different stores (multi-tenant isolation).
- Rate limiting: maximum 5 attempts per minute per IP, using the same `login` rate limiter.
- After login: redirect to `/account` or the intended URL (if the customer was redirected from a protected page).

#### Customer Registration Flow

```mermaid
flowchart TD
    A[Customer visits /account/register] --> B[Fill form: name, email, password, password_confirmation, marketing_opt_in]
    B --> C{Validate via RegisterCustomerRequest}
    C -- Invalid --> D[Show validation errors]
    C -- Valid --> E[Create Customer record scoped to current store]
    E --> F[Auto-login via customer guard]
    F --> G[Redirect to /account]
```

**Registration fields:**

| Field | Type | Required | Validation |
|-------|------|:--------:|------------|
| name | string | Y | max:255 |
| email | string | Y | valid email, unique per store (composite unique on email + store_id) |
| password | string | Y | min:8, confirmed |
| password_confirmation | string | Y | must match password |
| marketing_opt_in | boolean | N | defaults to false |

#### Customer Store Scoping

- The `customers` auth provider requires a custom `CustomerUserProvider` class.
- This provider overrides the default Eloquent provider to always include `store_id` in credential queries.
- The `store_id` is obtained from the container-bound current store (resolved by `ResolveStore` middleware).
- This ensures that `Auth::guard('customer')->attempt()` never crosses store boundaries.

#### Customer Password Reset

- Uses a separate password broker: `customers` broker.
- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
- Token records include `store_id` to scope resets to the correct store.
- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
- Token expiration: 60 minutes.
- Throttle: one reset email per 60 seconds.
- Generic response on the "forgot password" form (do not reveal whether email exists in the store).

---

### 1.3 API Authentication (Sanctum)

| Setting | Value |
|---------|-------|
| Guard name | `sanctum` |
| Provider | `users` (admin users only) |
| Token type | Personal access tokens |
| Token prefix | `shop_` |
| Hashing | SHA-256 |
| Default expiration | 1 year |
| Storage | `personal_access_tokens` table |

#### Token Lifecycle

```mermaid
stateDiagram-v2
    [*] --> Created: Admin creates token in Developers section
    Created --> Active: Plain-text shown once, hash stored
    Active --> InUse: Token sent as Bearer header
    InUse --> Active: Request processed
    Active --> Revoked: Admin revokes token
    Active --> Expired: Expiration date reached
    Revoked --> [*]: Token row deleted
    Expired --> [*]: Token row deleted (cleanup job)
```

**Requirements:**

- Tokens are created in the admin panel, under the Developers section.
- Each token has: a human-readable name, an optional expiration date, and a list of abilities (scopes).
- On creation, the plain-text token (prefixed with `shop_`) is displayed exactly once. Only the SHA-256 hash is persisted.
- Tokens are sent as `Authorization: Bearer shop_xxxxxxxx`.
- Sanctum middleware validates the hash and resolves the associated user.
- Ability checks are performed per-endpoint (e.g., the products list endpoint requires `read-products`).
- Revoking a token deletes its row from the `personal_access_tokens` table.

#### Token Abilities (Scopes)

| Ability | Description |
|---------|-------------|
| `read-products` | List and view products |
| `write-products` | Create, update, delete products |
| `read-orders` | List and view orders |
| `write-orders` | Update orders, create fulfillments |
| `read-customers` | List and view customers |
| `write-customers` | Update customers |
| `read-collections` | List and view collections |
| `write-collections` | Create, update, delete collections |
| `read-discounts` | List and view discounts |
| `write-discounts` | Create, update, delete discounts |
| `read-analytics` | View analytics data |
| `read-settings` | View store settings (general, shipping, taxes, checkout, notifications) |
| `write-settings` | Update store settings (replaces the legacy `manage-store`, `manage-shipping`, `manage-taxes` scopes) |
| `read-themes` | View themes and theme files |
| `write-themes` | Create, update, delete, publish themes |


status: completed

input:
const r = await tools.exec_command({cmd:"composer show --direct --no-ansi | sed -n '1,80p'; sed -n '260,620p' specs/02-API-ROUTES.md; sed -n '120,340p' specs/06-AUTH-AND-SECURITY.md","workdir":"/Users/wesner/Herd/shop","yield_time_ms":1000,"max_output_tokens":22000});
text(r.output);