**Findings**

- **Critical:** Admin Livewire mutation actions bypass the role matrix. The admin route only requires any store role ([routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:119), [CheckStoreRole.php](/Users/fabianwesner/Herd/shop/app/Http/Middleware/CheckStoreRole.php:27)), but order actions like refunds and fulfillments do not call policies ([Show.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:79), [Show.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:108)) even though `OrderPolicy` restricts refunds to owner/admin ([OrderPolicy.php](/Users/fabianwesner/Herd/shop/app/Policies/OrderPolicy.php:38)). Same pattern exists in product/collection/customer mutation surfaces.

- **High:** Admin API session-user authorization is membership-only and ignores route abilities/role permissions. `AuthenticateAdminApi` lets any store member through before checking abilities ([AuthenticateAdminApi.php](/Users/fabianwesner/Herd/shop/app/Http/Middleware/AuthenticateAdminApi.php:26)), and controllers repeat only `stores()->whereKey()` checks ([ProductController.php](/Users/fabianwesner/Herd/shop/app/Http/Controllers/Api/Admin/V1/ProductController.php:218), [OrderRefundController.php](/Users/fabianwesner/Herd/shop/app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:43)). A support/staff session path can reach destructive endpoints if session auth is used for API tests/clients.

- **Critical:** A single cart can produce multiple checkouts and therefore multiple orders/reservations. `createFromCart()` always inserts a checkout for an active non-empty cart ([CheckoutService.php](/Users/fabianwesner/Herd/shop/app/Services/CheckoutService.php:28)); the migration only indexes `cart_id`, it does not constrain active checkout uniqueness ([2026_05_03_225409_create_checkouts_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_03_225409_create_checkouts_table.php:31)). Order idempotency only checks `checkout_id` ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:52)), so two checkout rows for the same cart can both reserve inventory and complete.

- **High:** Product/collection Livewire forms can persist cross-store pivot links through tampered public state. `Collections\Form::addProduct()` accepts any product id ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Collections/Form.php:54)) and `save()` syncs those ids directly ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Collections/Form.php:103)); product editing similarly syncs raw `collectionIds` ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Products/Form.php:223)). The pivot table has only independent FKs, no same-store constraint ([2026_05_03_213903_create_collection_products_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_03_213903_create_collection_products_table.php:14)).

- **High:** Order numbers and discount usage limits are race-prone. `nextOrderNumber()` reads max existing numbers without a store/order sequence lock ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:227)) while the DB has a unique `(store_id, order_number)` constraint ([2026_05_04_000408_create_orders_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_04_000408_create_orders_table.php:36)). Discount usage is checked before completion without locking ([DiscountService.php](/Users/fabianwesner/Herd/shop/app/Services/DiscountService.php:77)) and incremented later ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:401)), so concurrent checkouts can exceed limits.

No files were edited. I did not run the test suite; this was a static review using the specs, schema, routes, and implementation.


Output: {"status":{"019df43c-0ee0-7980-a903-5273dd8ab788":{"completed":"**Findings**\n\n- **Critical:** Admin Livewire mutation actions bypass the role matrix. The admin route only requires any store role ([routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:119), [CheckStoreRole.php](/Users/fabianwesner/Herd/shop/app/Http/Middleware/CheckStoreRole.php:27)), but order actions like refunds and fulfillments do not call policies ([Show.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:79), [Show.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:108)) even though `OrderPolicy` restricts refunds to owner/admin ([OrderPolicy.php](/Users/fabianwesner/Herd/shop/app/Policies/OrderPolicy.php:38)). Same pattern exists in product/collection/customer mutation surfaces.\n\n- **High:** Admin API session-user authorization is membership-only and ignores route abilities/role permissions. `AuthenticateAdminApi` lets any store member through before checking abilities ([AuthenticateAdminApi.php](/Users/fabianwesner/Herd/shop/app/Http/Middleware/AuthenticateAdminApi.php:26)), and controllers repeat only `stores()->whereKey()` checks ([ProductController.php](/Users/fabianwesner/Herd/shop/app/Http/Controllers/Api/Admin/V1/ProductController.php:218), [OrderRefundController.php](/Users/fabianwesner/Herd/shop/app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:43)). A support/staff session path can reach destructive endpoints if session auth is used for API tests/clients.\n\n- **Critical:** A single cart can produce multiple checkouts and therefore multiple orders/reservations. `createFromCart()` always inserts a checkout for an active non-empty cart ([CheckoutService.php](/Users/fabianwesner/Herd/shop/app/Services/CheckoutService.php:28)); the migration only indexes `cart_id`, it does not constrain active checkout uniqueness ([2026_05_03_225409_create_checkouts_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_03_225409_create_checkouts_table.php:31)). Order idempotency only checks `checkout_id` ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:52)), so two checkout rows for the same cart can both reserve inventory and complete.\n\n- **High:** Product/collection Livewire forms can persist cross-store pivot links through tampered public state. `Collections\\Form::addProduct()` accepts any product id ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Collections/Form.php:54)) and `save()` syncs those ids directly ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Collections/Form.php:103)); product editing similarly syncs raw `collectionIds` ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Products/Form.php:223)). The pivot table has only independent FKs, no same-store constraint ([2026_05_03_213903_create_collection_products_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_03_213903_create_collection_products_table.php:14)).\n\n- **High:** Order numbers and discount usage limits are race-prone. `nextOrderNumber()` reads max existing numbers without a store/order sequence lock ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:227)) while the DB has a unique `(store_id, order_number)` constraint ([2026_05_04_000408_create_orders_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_04_000408_create_orders_table.php:36)). Discount usage is checked before completion without locking ([DiscountService.php](/Users/fabianwesner/Herd/shop/app/Services/DiscountService.php:77)) and incremented later ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:401)), so concurrent checkouts can exceed limits.\n\nNo files were edited. I did not run the test suite; this was a static review using the specs, schema, routes, and implementation."}},"timed_out":false}


<subagent_notification>
{"agent_path":"019df43c-0ee0-7980-a903-5273dd8ab788","status":{"completed":"**Findings**\n\n- **Critical:** Admin Livewire mutation actions bypass the role matrix. The admin route only requires any store role ([routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:119), [CheckStoreRole.php](/Users/fabianwesner/Herd/shop/app/Http/Middleware/CheckStoreRole.php:27)), but order actions like refunds and fulfillments do not call policies ([Show.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:79), [Show.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Orders/Show.php:108)) even though `OrderPolicy` restricts refunds to owner/admin ([OrderPolicy.php](/Users/fabianwesner/Herd/shop/app/Policies/OrderPolicy.php:38)). Same pattern exists in product/collection/customer mutation surfaces.\n\n- **High:** Admin API session-user authorization is membership-only and ignores route abilities/role permissions. `AuthenticateAdminApi` lets any store member through before checking abilities ([AuthenticateAdminApi.php](/Users/fabianwesner/Herd/shop/app/Http/Middleware/AuthenticateAdminApi.php:26)), and controllers repeat only `stores()->whereKey()` checks ([ProductController.php](/Users/fabianwesner/Herd/shop/app/Http/Controllers/Api/Admin/V1/ProductController.php:218), [OrderRefundController.php](/Users/fabianwesner/Herd/shop/app/Http/Controllers/Api/Admin/V1/OrderRefundController.php:43)). A support/staff session path can reach destructive endpoints if session auth is used for API tests/clients.\n\n- **Critical:** A single cart can produce multiple checkouts and therefore multiple orders/reservations. `createFromCart()` always inserts a checkout for an active non-empty cart ([CheckoutService.php](/Users/fabianwesner/Herd/shop/app/Services/CheckoutService.php:28)); the migration only indexes `cart_id`, it does not constrain active checkout uniqueness ([2026_05_03_225409_create_checkouts_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_03_225409_create_checkouts_table.php:31)). Order idempotency only checks `checkout_id` ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:52)), so two checkout rows for the same cart can both reserve inventory and complete.\n\n- **High:** Product/collection Livewire forms can persist cross-store pivot links through tampered public state. `Collections\\Form::addProduct()` accepts any product id ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Collections/Form.php:54)) and `save()` syncs those ids directly ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Collections/Form.php:103)); product editing similarly syncs raw `collectionIds` ([Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Products/Form.php:223)). The pivot table has only independent FKs, no same-store constraint ([2026_05_03_213903_create_collection_products_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_03_213903_create_collection_products_table.php:14)).\n\n- **High:** Order numbers and discount usage limits are race-prone. `nextOrderNumber()` reads max existing numbers without a store/order sequence lock ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:227)) while the DB has a unique `(store_id, order_number)` constraint ([2026_05_04_000408_create_orders_table.php](/Users/fabianwesner/Herd/shop/database/migrations/2026_05_04_000408_create_orders_table.php:36)). Discount usage is checked before completion without locking ([DiscountService.php](/Users/fabianwesner/Herd/shop/app/Services/DiscountService.php:77)) and incremented later ([OrderService.php](/Users/fabianwesner/Herd/shop/app/Services/OrderService.php:401)), so concurrent checkouts can exceed limits.\n\nNo files were edited. I did not run the test suite; this was a static review using the specs, schema, routes, and implementation."}}
</subagent_notification>


The independent code review also found critical/high issues around authorization, checkout idempotency, tenant-safe pivots, and concurrency. I’m going to address the concrete blockers rather than arguing from previous test coverage; first I’m checking the relevant docs and current code paths so the fixes fit the app’s Laravel/Livewire conventions.


sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/livewire-development/SKILL.md


sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/developing-with-fortify/SKILL.md


custom authentication guard viaRequest middleware auth guard

authorization policies authorize livewire actions

database transactions lockForUpdate unique constraint validation

form request validation custom rule after


sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/developing-with-fortify/SKILL.md

---
name: developing-with-fortify
description: Laravel Fortify headless authentication backend development. Activate when implementing authentication features including login, registration, password reset, email verification, two-factor authentication (2FA/TOTP), profile updates, headless auth, authentication scaffolding, or auth guards in Laravel applications.
---

# Laravel Fortify Development

Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications.

## Documentation

Use `search-docs` for detailed Laravel Fortify patterns and documentation.

## Usage

- **Routes**: Use `list-routes` with `only_vendor: true` and `action: "Fortify"` to see all registered endpoints
- **Actions**: Check `app/Actions/Fortify/` for customizable business logic (user creation, password validation, etc.)
- **Config**: See `config/fortify.php` for all options including features, guards, rate limiters, and username field
- **Contracts**: Look in `Laravel\Fortify\Contracts\` for overridable response classes (`LoginResponse`, `LogoutResponse`, etc.)
- **Views**: All view callbacks are set in `FortifyServiceProvider::boot()` using `Fortify::loginView()`, `Fortify::registerView()`, etc.

## Available Features

Enable in `config/fortify.php` features array:

- `Features::registration()` - User registration
- `Features::resetPasswords()` - Password reset via email
- `Features::emailVerification()` - Requires User to implement `MustVerifyEmail`
- `Features::updateProfileInformation()` - Profile updates
- `Features::updatePasswords()` - Password changes
- `Features::twoFactorAuthentication()` - 2FA with QR codes and recovery codes

> Use `search-docs` for feature configuration options and customization patterns.

## Setup Workflows

### Two-Factor Authentication Setup

```
- [ ] Add TwoFactorAuthenticatable trait to User model
- [ ] Enable feature in config/fortify.php
- [ ] Run migrations for 2FA columns
- [ ] Set up view callbacks in FortifyServiceProvider
- [ ] Create 2FA management UI
- [ ] Test QR code and recovery codes
```

> Use `search-docs` for TOTP implementation and recovery code handling patterns.

### Email Verification Setup

```
- [ ] Enable emailVerification feature in config
- [ ] Implement MustVerifyEmail interface on User model
- [ ] Set up verifyEmailView callback
- [ ] Add verified middleware to protected routes
- [ ] Test verification email flow
```

> Use `search-docs` for MustVerifyEmail implementation patterns.

### Password Reset Setup

```
- [ ] Enable resetPasswords feature in config
- [ ] Set up requestPasswordResetLinkView callback
- [ ] Set up resetPasswordView callback
- [ ] Define password.reset named route (if views disabled)
- [ ] Test reset email and link flow
```

> Use `search-docs` for custom password reset flow patterns.

### SPA Authentication Setup

```
- [ ] Set 'views' => false in config/fortify.php
- [ ] Install and configure Laravel Sanctum
- [ ] Use 'web' guard in fortify config
- [ ] Set up CSRF token handling
- [ ] Test XHR authentication flows
```

> Use `search-docs` for integration and SPA authentication patterns.

## Best Practices

### Custom Authentication Logic

Override authentication behavior using `Fortify::authenticateUsing()` for custom user retrieval or `Fortify::authenticateThrough()` to customize the authentication pipeline. Override response contracts in `AppServiceProvider` for custom redirects.

### Registration Customization

Modify `app/Actions/Fortify/CreateNewUser.php` to customize user creation logic, validation rules, and additional fields.

### Rate Limiting

Configure via `fortify.limiters.login` in config. Default configuration throttles by username + IP combination.

## Key Endpoints

| Feature                | Method   | Endpoint                                    |
|------------------------|----------|---------------------------------------------|
| Login                  | POST     | `/login`                                    |
| Logout                 | POST     | `/logout`                                   |
| Register               | POST     | `/register`                                 |
| Password Reset Request | POST     | `/forgot-password`                          |
| Password Reset         | POST     | `/reset-password`                           |
| Email Verify Notice    | GET      | `/email/verify`                             |
| Resend Verification    | POST     | `/email/verification-notification`          |
| Password Confirm       | POST     | `/user/confirm-password`                    |
| Enable 2FA             | POST     | `/user/two-factor-authentication`           |
| Confirm 2FA            | POST     | `/user/confirmed-two-factor-authentication` |
| 2FA Challenge          | POST     | `/two-factor-challenge`                     |
| Get QR Code            | GET      | `/user/two-factor-qr-code`                  |
| Recovery Codes         | GET/POST | `/user/two-factor-recovery-codes`           |


sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/livewire-development/SKILL.md

---
name: livewire-development
description: "Use for any task or question involving Livewire. Activate if user mentions Livewire, wire: directives, or Livewire-specific concepts like wire:model, wire:click, wire:sort, or islands, invoke this skill. Covers building new components, debugging reactivity issues, real-time form validation, drag-and-drop, loading states, migrating from Livewire 3 to 4, converting component formats (SFC/MFC/class-based), and performance optimization. Do not use for non-Livewire reactive UI (React, Vue, Alpine-only, Inertia.js) or standard Laravel forms without Livewire."
license: MIT
metadata:
  author: laravel
---

# Livewire Development

## Documentation

Use `search-docs` for detailed Livewire 4 patterns and documentation.

## Basic Usage

### Creating Components

```bash

# Single-file component (SFC - default in v4)

# Creates: resources/views/components/⚡create-post.blade.php

php artisan make:livewire create-post

# Page component (SFC - Full Page in v4)

# Creates: resources/views/pages/⚡create-post.blade.php

php artisan make:livewire pages::create-post

# Multi-file component (MFC)

# Creates: resources/views/components/⚡create-post/create-post.php

#          resources/views/components/⚡create-post/create-post.blade.php

php artisan make:livewire create-post --mfc

# Class-based component (v3 style)

# Creates: app/Livewire/CreatePost.php AND resources/views/livewire/create-post.blade.php

php artisan make:livewire create-post --class

# With namespace

php artisan make:livewire Posts/CreatePost
```

### Converting Between Formats

Use `php artisan livewire:convert create-post` to convert between single-file, multi-file, and class-based formats.

### Choosing a Component Format

> **Always follow the project's existing conventions first.** Before creating any component, inspect the project's existing Livewire components to determine the established format (SFC, MFC, or class-based) and directory structure. Check `app/Livewire/`, `resources/views/components/`, and `resources/views/livewire/` for existing components. If the project already uses a consistent format, **use that same format** — even if it differs from the Livewire v4 defaults below. Only fall back to the v4 defaults (SFC in `resources/views/components/`) when no existing convention is established.

Also check `config/livewire.php` for `make_command.type`, `make_command.emoji`, `component_locations`, and `component_namespaces` overrides, which change the default format and where files are stored.

### Component Format Reference

| Format | Flag | Class Path | View Path |
|--------|------|------------|-----------|
| Single-file (SFC) | default | — | `resources/views/components/⚡create-post.blade.php` (PHP + Blade in one file) |
| Full Page SFC | `pages::name` | — | `resources/views/pages/⚡create-post.blade.php` |
| Multi-file (MFC) | `--mfc` | `resources/views/components/⚡create-post/create-post.php` | `resources/views/components/⚡create-post/create-post.blade.php` |
| Class-based | `--class` | `app/Livewire/CreatePost.php` | `resources/views/livewire/create-post.blade.php` |
| View-based | default (Blade-only) | — | `resources/views/components/⚡create-post.blade.php` (Blade-only with functional state) |

> **Important:** The ⚡ prefix shown above is the **default** behavior in Livewire v4 — it is **configurable**. Check `config/livewire.php` for the `make_command.emoji` setting. When `true` (default), always include the ⚡ prefix in filenames you create. When `false`, omit the ⚡ prefix from all paths above.

Namespaced components map to subdirectories: `make:livewire Posts/CreatePost` creates `resources/views/components/posts/⚡create-post.blade.php` (single-file by default). Use `make:livewire Posts/CreatePost --mfc` for multi-file output at `resources/views/components/posts/⚡create-post/create-post.php` and `resources/views/components/posts/⚡create-post/create-post.blade.php`.

### Single-File Component Example

<!-- Single-File Component Example -->
```php
<?php
use Livewire\Component;

new class extends Component {
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
};
?>

<div>
    <button wire:click="increment">Count: @{{ $count }}</button>
</div>
```

## Livewire 4 Specifics

### Key Changes From Livewire 3

These things changed in Livewire 4, but may not have been updated in this application. Verify this application's setup to ensure you follow existing conventions.

- Use `Route::livewire()` for full-page components (e.g., `Route::livewire('/posts/create', CreatePost::class)`); config keys renamed: `layout` → `component_layout`, `lazy_placeholder` → `component_placeholder`.
- `wire:model` now ignores child events by default (use `wire:model.deep` for old behavior); `wire:scroll` renamed to `wire:navigate:scroll`.
- Component tags must be properly closed; `wire:transition` now uses View Transitions API (modifiers removed).
- JavaScript: `$wire.$js('name', fn)` → `$wire.$js.name = fn`; `commit`/`request` hooks → `interceptMessage()`/`interceptRequest()`.

### New Features

- Component formats: single-file (SFC), multi-file (MFC), view-based components.
- Islands (`@island`) for isolated updates; async actions (`wire:click.async`, `#[Async]`) for parallel execution.
- Deferred/bundled loading: `defer`, `lazy.bundle` for optimized component loading.

| Feature | Usage | Purpose |
|---------|-------|---------|
| Islands | `@island(name: 'stats')` | Isolated update regions |
| Async | `wire:click.async` or `#[Async]` | Non-blocking actions |
| Deferred | `defer` attribute | Load after page render |
| Bundled | `lazy.bundle` | Load multiple together |

### New Directives

- `wire:sort`, `wire:intersect`, `wire:ref`, `.renderless`, `.preserve-scroll` are available for use.
- `data-loading` attribute automatically added to elements triggering network requests.

| Directive | Purpose |
|-----------|---------|
| `wire:sort` | Drag-and-drop sorting |
| `wire:intersect` | Viewport intersection detection |
| `wire:ref` | Element references for JS |
| `.renderless` | Component without rendering |
| `.preserve-scroll` | Preserve scroll position |

## Best Practices

- Always use `wire:key` in loops
- Use `wire:loading` for loading states
- Use `wire:model.live` for instant updates (default is debounced)
- Validate and authorize in actions (treat like HTTP requests)

## Configuration

- `smart_wire_keys` defaults to `true`; new configs: `component_locations`, `component_namespaces`, `make_command`, `csp_safe`.

## Alpine & JavaScript

- `wire:transition` uses browser View Transitions API; `$errors` and `$intercept` magic properties available.
- Non-blocking `wire:poll` and parallel `wire:model.live` updates improve performance.

For interceptors and hooks, see [reference/javascript-hooks.md](reference/javascript-hooks.md).

## Testing

<!-- Testing Example -->
```php
Livewire::test(Counter::class)
    ->assertSet('count', 0)
    ->call('increment')
    ->assertSet('count', 1);
```

## Verification

1. Browser console: Check for JS errors
2. Network tab: Verify Livewire requests return 200
3. Ensure `wire:key` on all `@foreach` loops

## Common Pitfalls

- Missing `wire:key` in loops → unexpected re-rendering
- Expecting `wire:model` real-time → use `wire:model.live`
- Unclosed component tags → syntax errors in v4
- Using deprecated config keys or JS hooks
- Including Alpine.js separately (already bundled in Livewire 4)


Output:
Chunk ID: 435b3e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1888
Output:
---
name: livewire-development
description: "Use for any task or question involving Livewire. Activate if user mentions Livewire, wire: directives, or Livewire-specific concepts like wire:model, wire:click, wire:sort, or islands, invoke this skill. Covers building new components, debugging reactivity issues, real-time form validation, drag-and-drop, loading states, migrating from Livewire 3 to 4, converting component formats (SFC/MFC/class-based), and performance optimization. Do not use for non-Livewire reactive UI (React, Vue, Alpine-only, Inertia.js) or standard Laravel forms without Livewire."
license: MIT
metadata:
  author: laravel
---

# Livewire Development

## Documentation

Use `search-docs` for detailed Livewire 4 patterns and documentation.

## Basic Usage

### Creating Components

```bash

# Single-file component (SFC - default in v4)

# Creates: resources/views/components/⚡create-post.blade.php

php artisan make:livewire create-post

# Page component (SFC - Full Page in v4)

# Creates: resources/views/pages/⚡create-post.blade.php

php artisan make:livewire pages::create-post

# Multi-file component (MFC)

# Creates: resources/views/components/⚡create-post/create-post.php

#          resources/views/components/⚡create-post/create-post.blade.php

php artisan make:livewire create-post --mfc

# Class-based component (v3 style)

# Creates: app/Livewire/CreatePost.php AND resources/views/livewire/create-post.blade.php

php artisan make:livewire create-post --class

# With namespace

php artisan make:livewire Posts/CreatePost
```

### Converting Between Formats

Use `php artisan livewire:convert create-post` to convert between single-file, multi-file, and class-based formats.

### Choosing a Component Format

> **Always follow the project's existing conventions first.** Before creating any component, inspect the project's existing Livewire components to determine the established format (SFC, MFC, or class-based) and directory structure. Check `app/Livewire/`, `resources/views/components/`, and `resources/views/livewire/` for existing components. If the project already uses a consistent format, **use that same format** — even if it differs from the Livewire v4 defaults below. Only fall back to the v4 defaults (SFC in `resources/views/components/`) when no existing convention is established.

Also check `config/livewire.php` for `make_command.type`, `make_command.emoji`, `component_locations`, and `component_namespaces` overrides, which change the default format and where files are stored.

### Component Format Reference

| Format | Flag | Class Path | View Path |
|--------|------|------------|-----------|
| Single-file (SFC) | default | — | `resources/views/components/⚡create-post.blade.php` (PHP + Blade in one file) |
| Full Page SFC | `pages::name` | — | `resources/views/pages/⚡create-post.blade.php` |
| Multi-file (MFC) | `--mfc` | `resources/views/components/⚡create-post/create-post.php` | `resources/views/components/⚡create-post/create-post.blade.php` |
| Class-based | `--class` | `app/Livewire/CreatePost.php` | `resources/views/livewire/create-post.blade.php` |
| View-based | default (Blade-only) | — | `resources/views/components/⚡create-post.blade.php` (Blade-only with functional state) |

> **Important:** The ⚡ prefix shown above is the **default** behavior in Livewire v4 — it is **configurable**. Check `config/livewire.php` for the `make_command.emoji` setting. When `true` (default), always include the ⚡ prefix in filenames you create. When `false`, omit the ⚡ prefix from all paths above.

Namespaced components map to subdirectories: `make:livewire Posts/CreatePost` creates `resources/views/components/posts/⚡create-post.blade.php` (single-file by default). Use `make:livewire Posts/CreatePost --mfc` for multi-file output at `resources/views/components/posts/⚡create-post/create-post.php` and `resources/views/components/posts/⚡create-post/create-post.blade.php`.

### Single-File Component Example

<!-- Single-File Component Example -->
```php
<?php
use Livewire\Component;

new class extends Component {
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
};
?>

<div>
    <button wire:click="increment">Count: @{{ $count }}</button>
</div>
```

## Livewire 4 Specifics

### Key Changes From Livewire 3

These things changed in Livewire 4, but may not have been updated in this application. Verify this application's setup to ensure you follow existing conventions.

- Use `Route::livewire()` for full-page components (e.g., `Route::livewire('/posts/create', CreatePost::class)`); config keys renamed: `layout` → `component_layout`, `lazy_placeholder` → `component_placeholder`.
- `wire:model` now ignores child events by default (use `wire:model.deep` for old behavior); `wire:scroll` renamed to `wire:navigate:scroll`.
- Component tags must be properly closed; `wire:transition` now uses View Transitions API (modifiers removed).
- JavaScript: `$wire.$js('name', fn)` → `$wire.$js.name = fn`; `commit`/`request` hooks → `interceptMessage()`/`interceptRequest()`.

### New Features

- Component formats: single-file (SFC), multi-file (MFC), view-based components.
- Islands (`@island`) for isolated updates; async actions (`wire:click.async`, `#[Async]`) for parallel execution.
- Deferred/bundled loading: `defer`, `lazy.bundle` for optimized component loading.

| Feature | Usage | Purpose |
|---------|-------|---------|
| Islands | `@island(name: 'stats')` | Isolated update regions |
| Async | `wire:click.async` or `#[Async]` | Non-blocking actions |
| Deferred | `defer` attribute | Load after page render |
| Bundled | `lazy.bundle` | Load multiple together |

### New Directives

- `wire:sort`, `wire:intersect`, `wire:ref`, `.renderless`, `.preserve-scroll` are available for use.
- `data-loading` attribute automatically added to elements triggering network requests.

| Directive | Purpose |
|-----------|---------|
| `wire:sort` | Drag-and-drop sorting |
| `wire:intersect` | Viewport intersection detection |
| `wire:ref` | Element references for JS |
| `.renderless` | Component without rendering |
| `.preserve-scroll` | Preserve scroll position |

## Best Practices

- Always use `wire:key` in loops
- Use `wire:loading` for loading states
- Use `wire:model.live` for instant updates (default is debounced)
- Validate and authorize in actions (treat like HTTP requests)

## Configuration

- `smart_wire_keys` defaults to `true`; new configs: `component_locations`, `component_namespaces`, `make_command`, `csp_safe`.

## Alpine & JavaScript

- `wire:transition` uses browser View Transitions API; `$errors` and `$intercept` magic properties available.
- Non-blocking `wire:poll` and parallel `wire:model.live` updates improve performance.

For interceptors and hooks, see [reference/javascript-hooks.md](reference/javascript-hooks.md).

## Testing

<!-- Testing Example -->
```php
Livewire::test(Counter::class)
    ->assertSet('count', 0)
    ->call('increment')
    ->assertSet('count', 1);
```

## Verification

1. Browser console: Check for JS errors
2. Network tab: Verify Livewire requests return 200
3. Ensure `wire:key` on all `@foreach` loops

## Common Pitfalls

- Missing `wire:key` in loops → unexpected re-rendering
- Expecting `wire:model` real-time → use `wire:model.live`
- Unclosed component tags → syntax errors in v4
- Using deprecated config keys or JS hooks
- Including Alpine.js separately (already bundled in Livewire 4)


Output:
Chunk ID: 55325c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1256
Output:
---
name: developing-with-fortify
description: Laravel Fortify headless authentication backend development. Activate when implementing authentication features including login, registration, password reset, email verification, two-factor authentication (2FA/TOTP), profile updates, headless auth, authentication scaffolding, or auth guards in Laravel applications.
---

# Laravel Fortify Development

Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications.

## Documentation

Use `search-docs` for detailed Laravel Fortify patterns and documentation.

## Usage

- **Routes**: Use `list-routes` with `only_vendor: true` and `action: "Fortify"` to see all registered endpoints
- **Actions**: Check `app/Actions/Fortify/` for customizable business logic (user creation, password validation, etc.)
- **Config**: See `config/fortify.php` for all options including features, guards, rate limiters, and username field
- **Contracts**: Look in `Laravel\Fortify\Contracts\` for overridable response classes (`LoginResponse`, `LogoutResponse`, etc.)
- **Views**: All view callbacks are set in `FortifyServiceProvider::boot()` using `Fortify::loginView()`, `Fortify::registerView()`, etc.

## Available Features

Enable in `config/fortify.php` features array:

- `Features::registration()` - User registration
- `Features::resetPasswords()` - Password reset via email
- `Features::emailVerification()` - Requires User to implement `MustVerifyEmail`
- `Features::updateProfileInformation()` - Profile updates
- `Features::updatePasswords()` - Password changes
- `Features::twoFactorAuthentication()` - 2FA with QR codes and recovery codes

> Use `search-docs` for feature configuration options and customization patterns.

## Setup Workflows

### Two-Factor Authentication Setup

```
- [ ] Add TwoFactorAuthenticatable trait to User model
- [ ] Enable feature in config/fortify.php
- [ ] Run migrations for 2FA columns
- [ ] Set up view callbacks in FortifyServiceProvider
- [ ] Create 2FA management UI
- [ ] Test QR code and recovery codes
```

> Use `search-docs` for TOTP implementation and recovery code handling patterns.

### Email Verification Setup

```
- [ ] Enable emailVerification feature in config
- [ ] Implement MustVerifyEmail interface on User model
- [ ] Set up verifyEmailView callback
- [ ] Add verified middleware to protected routes
- [ ] Test verification email flow
```

> Use `search-docs` for MustVerifyEmail implementation patterns.

### Password Reset Setup

```
- [ ] Enable resetPasswords feature in config
- [ ] Set up requestPasswordResetLinkView callback
- [ ] Set up resetPasswordView callback
- [ ] Define password.reset named route (if views disabled)
- [ ] Test reset email and link flow
```

> Use `search-docs` for custom password reset flow patterns.

### SPA Authentication Setup

```
- [ ] Set 'views' => false in config/fortify.php
- [ ] Install and configure Laravel Sanctum
- [ ] Use 'web' guard in fortify config
- [ ] Set up CSRF token handling
- [ ] Test XHR authentication flows
```

> Use `search-docs` for integration and SPA authentication patterns.

## Best Practices

### Custom Authentication Logic

Override authentication behavior using `Fortify::authenticateUsing()` for custom user retrieval or `Fortify::authenticateThrough()` to customize the authentication pipeline. Override response contracts in `AppServiceProvider` for custom redirects.

### Registration Customization

Modify `app/Actions/Fortify/CreateNewUser.php` to customize user creation logic, validation rules, and additional fields.

### Rate Limiting

Configure via `fortify.limiters.login` in config. Default configuration throttles by username + IP combination.

## Key Endpoints

| Feature                | Method   | Endpoint                                    |
|------------------------|----------|---------------------------------------------|
| Login                  | POST     | `/login`                                    |
| Logout                 | POST     | `/logout`                                   |
| Register               | POST     | `/register`                                 |
| Password Reset Request | POST     | `/forgot-password`                          |
| Password Reset         | POST     | `/reset-password`                           |
| Email Verify Notice    | GET      | `/email/verify`                             |
| Resend Verification    | POST     | `/email/verification-notification`          |
| Password Confirm       | POST     | `/user/confirm-password`                    |
| Enable 2FA             | POST     | `/user/two-factor-authentication`           |
| Confirm 2FA            | POST     | `/user/confirmed-two-factor-authentication` |
| 2FA Challenge          | POST     | `/two-factor-challenge`                     |
| Get QR Code            | GET      | `/user/two-factor-qr-code`                  |
| Recovery Codes         | GET/POST | `/user/two-factor-recovery-codes`           |


# Search Results

## Authorization Authorizing Actions Using Policies
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

## Authorizing Actions Using Policies

---

## Authentication Adding Custom Guards Closure Request Guards
Authentication | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authentication.md

### Closure Request Guards

The simplest way to implement a custom, HTTP request based authentication system is by using the `Auth::viaRequest` method. This method allows you to quickly define your authentication process using a single closure.

To get started, call the `Auth::viaRequest` method within the `boot` method of your application's `AppServiceProvider`. The `viaRequest` method accepts an authentication driver name as its first argument. This name can be any string that describes your custom guard. The second argument passed to the method should be a closure that receives the incoming HTTP request and returns a user instance or, if authentication fails, `null`:

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Auth::viaRequest('custom-token', function (Request $request) {
        return User::where('token', (string) $request->token)->first();
    });
}
```

Once your custom authentication driver has been defined, you may configure it as a driver within the `guards` configuration of your `auth.php` configuration file:

```php
'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],
```

Finally, you may reference the guard when assigning the authentication middleware to a route:

```php
Route::middleware('auth:api')->group(function () {
    // ...
});
```

---

## Precognition Customizing Validation Rules
Precognition | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/precognition.md

## Customizing Validation Rules

It is possible to customize the validation rules executed during a precognitive request by using the request's `isPrecognitive` method.

For example, on a user creation form, we may want to validate that a password is "uncompromised" only on the final form submission. For precognitive validation requests, we will simply validate that the password is required and has a minimum of 8 characters. Using the `isPrecognitive` method, we can customize the rules defined by our form request:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class StoreUserRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    protected function rules()
    {
        return [
            'password' => [
                'required',
                $this->isPrecognitive()
                    ? Password::min(8)
                    : Password::min(8)->uncompromised(),
            ],
            // ...
        ];
    }
}
```

---

## Verification Introduction Database Preparation
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

### Database Preparation

Next, your `users` table must contain an `email_verified_at` column to store the date and time that the user's email address was verified. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` database migration.

---

## Validation Custom Validation Rules
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

## Custom Validation Rules

---

## Passport Protecting Routes Via Middleware
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

### Via Middleware

Passport includes an [authentication guard](/docs/{{version}}/authentication#adding-custom-guards) that will validate access tokens on incoming requests. Once you have configured the `api` guard to use the `passport` driver, you only need to specify the `auth:api` middleware on any routes that should require a valid access token:

```php
Route::get('/user', function () {
    // Only API authenticated users may access this route...
})->middleware('auth:api');
```

> [!WARNING]
> If you are using the [client credentials grant](#client-credentials-grant), you should use [the `Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner` middleware](#client-credentials-grant) to protect your routes instead of the `auth:api` middleware.

---

## Authentication Authentication Quickstart Protecting Routes Specifying a Guard
Authentication | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authentication.md

#### Specifying a Guard

When attaching the `auth` middleware to a route, you may also specify which "guard" should be used to authenticate the user. The guard specified should correspond to one of the keys in the `guards` array of your `auth.php` configuration file:

```php
Route::get('/flights', function () {
    // Only authenticated users may access this route...
})->middleware('auth:admin');
```

---

## Authorization Authorizing Actions Using Policies Supplying Additional Context
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

### Supplying Additional Context

When authorizing actions using policies, you may pass an array as the second argument to the various authorization functions and helpers. The first element in the array will be used to determine which policy should be invoked, while the rest of the array elements are passed as parameters to the policy method and can be used for additional context when making authorization decisions. For example, consider the following `PostPolicy` method definition which contains an additional `$category` parameter:

```php
/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post, int $category): bool
{
    return $user->id === $post->user_id &&
           $user->canUpdateCategory($category);
}
```

When attempting to determine if the authenticated user can update a given post, we can invoke this policy method like so:

```php
/**
 * Update the given blog post.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function update(Request $request, Post $post): RedirectResponse
{
    Gate::authorize('update', [$post, $request->category]);

    // The current user can update the blog post...

    return redirect('/posts');
}
```

---

## Upgrade Upgrading To 12.0 From 11.x Database Database Constructor Signature Changes
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Database Constructor Signature Changes

**Likelihood Of Impact: Very Low**

In Laravel 12, several low-level database classes now require an `Illuminate\Database\Connection` instance to be provided via their constructors.

**These changes are primarily applicable to database package maintainers - it is extremely unlikely any of these changes affect normal application development.**

`Illuminate\Database\Schema\Blueprint`

The constructor of the `Illuminate\Database\Schema\Blueprint` class now expects a `Connection` instance as its first argument. This primarily affects applications or packages that manually instantiate `Blueprint` instances.

`Illuminate\Database\Grammar`

The constructor of the `Illuminate\Database\Grammar` class also now requires a `Connection` instance. In previous versions, the connection was assigned after construction using the `setConnection()` method. This method has been removed in Laravel 12:

```php
// Laravel <= 11.x
$grammar = new MySqlGrammar;
$grammar->setConnection($connection);

// Laravel >= 12.x
$grammar = new MySqlGrammar($connection);
````

In addition, the following APIs have been removed or deprecated:

- The `Blueprint::getPrefix()` method is deprecated.
- The `Connection::withTablePrefix()` method has been removed.
- The `Grammar::getTablePrefix()` and `setTablePrefix()` methods are deprecated.
- The `Grammar::setConnection()` method has been removed.

When working with table prefixes, you should now retrieve them directly from the database connection:

```php
$prefix = $connection->getTablePrefix();
```

If you maintain custom database drivers, schema builders, or grammar implementations, you should review their constructors and ensure a `Connection` instance is provided.

---

## Database Database Transactions Handling Deadlocks
Database | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md

#### Handling Deadlocks

The `transaction` method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown:

```php
use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
}, attempts: 5);
```

---

## Authorization
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

# Authorization

- [Introduction](#introduction)
- [Gates](#gates)
    - [Writing Gates](#writing-gates)
    - [Authorizing Actions](#authorizing-actions-via-gates)
    - [Gate Responses](#gate-responses)
    - [Intercepting Gate Checks](#intercepting-gate-checks)
    - [Inline Authorization](#inline-authorization)
- [Creating Policies](#creating-policies)
    - [Generating Policies](#generating-policies)
    - [Registering Policies](#registering-policies)
- [Writing Policies](#writing-policies)
    - [Policy Methods](#policy-methods)
    - [Policy Responses](#policy-responses)
    - [Methods Without Models](#methods-without-models)
    - [Guest Users](#guest-users)
    - [Policy Filters](#policy-filters)
- [Authorizing Actions Using Policies](#authorizing-actions-using-policies)
    - [Via the User Model](#via-the-user-model)
    - [Via the Gate Facade](#via-the-gate-facade)
    - [Via Middleware](#via-middleware)
    - [Via Blade Templates](#via-blade-templates)
    - [Supplying Additional Context](#supplying-additional-context)
- [Authorization & Inertia](#authorization-and-inertia)

---

## Validation Available Validation Rules required_if:_anotherfield_,_value_,...
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### required_if:_anotherfield_,_value_,...

The field under validation must be present and not empty if the _anotherfield_ field is equal to any _value_.

If you would like to construct a more complex condition for the `required_if` rule, you may use the `Rule::requiredIf` method. This method accepts a boolean or a closure. When passed a closure, the closure should return `true` or `false` to indicate if the field under validation is required:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => Rule::requiredIf($request->user()->is_admin),
]);

Validator::make($request->all(), [
    'role_id' => Rule::requiredIf(fn () => $request->user()->is_admin),
]);
```

---

## Passport Protecting Routes Via Middleware Multiple Authentication Guards
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

#### Multiple Authentication Guards

If your application authenticates different types of users that perhaps use entirely different Eloquent models, you will likely need to define a guard configuration for each user provider type in your application. This allows you to protect requests intended for specific user providers. For example, given the following guard configuration the `config/auth.php` configuration file:

```php
'guards' => [
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],

    'api-customers' => [
        'driver' => 'passport',
        'provider' => 'customers',
    ],
],
```

The following route will utilize the `api-customers` guard, which uses the `customers` user provider, to authenticate incoming requests:

```php
Route::get('/customer', function () {
    // ...
})->middleware('auth:api-customers');
```

> [!NOTE]
> For more information on using multiple user providers with Passport, please consult the [personal access tokens documentation](#customizing-the-user-provider-for-pat) and [password grant documentation](#customizing-the-user-provider).

---

## Validation Available Validation Rules after:_date_
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### after:_date_

The field under validation must be a value after a given date. The dates will be passed into the `strtotime` PHP function in order to be converted to a valid `DateTime` instance:

```php
'start_date' => 'required|date|after:tomorrow'
```

Instead of passing a date string to be evaluated by `strtotime`, you may specify another field to compare against the date:

```php
'finish_date' => 'required|date|after:start_date'
```

For convenience, date-based rules may be constructed using the fluent `date` rule builder:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->after(today()->addDays(7)),
],
```

The `afterToday` and `todayOrAfter` methods may be used to fluently express the date and must be after today, or today or after, respectively:

```php
'start_date' => [
    'required',
    Rule::date()->afterToday(),
],
```

---

## Upgrade Upgrading To 12.0 From 11.x Database Multi-Schema Database Inspecting
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Multi-Schema Database Inspecting

**Likelihood Of Impact: Low**

The `Schema::getTables()`, `Schema::getViews()`, and `Schema::getTypes()` methods now include the results from all schemas by default. You may pass the `schema` argument to retrieve the result for the given schema only:

```php
// All tables on all schemas...
$tables = Schema::getTables();

// All tables on the 'main' schema...
$tables = Schema::getTables(schema: 'main');

// All tables on the 'main' and 'blog' schemas...
$tables = Schema::getTables(schema: ['main', 'blog']);
```

The `Schema::getTableListing()` method now returns schema-qualified table names by default. You may pass the `schemaQualified` argument to change the behavior as desired:

```php
$tables = Schema::getTableListing();
// ['main.migrations', 'main.users', 'blog.posts']

$tables = Schema::getTableListing(schema: 'main');
// ['main.migrations', 'main.users']

$tables = Schema::getTableListing(schema: 'main', schemaQualified: false);
// ['migrations', 'users']
```

The `db:table` and `db:show` commands now output the results of all schemas on MySQL, MariaDB, and SQLite, just like PostgreSQL and SQL Server.

---

## Authorization Writing Policies Policy Filters
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

### Policy Filters

For certain users, you may wish to authorize all actions within a given policy. To accomplish this, define a `before` method on the policy. The `before` method will be executed before any other methods on the policy, giving you an opportunity to authorize the action before the intended policy method is actually called. This feature is most commonly used for authorizing application administrators to perform any action:

```php
use App\Models\User;

/**
 * Perform pre-authorization checks.
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true;
    }

    return null;
}
```

If you would like to deny all authorization checks for a particular type of user then you may return `false` from the `before` method. If `null` is returned, the authorization check will fall through to the policy method.

> [!WARNING]
> The `before` method of a policy class will not be called if the class doesn't contain a method with a name matching the name of the ability being checked.

---

## Validation Available Validation Rules prohibited_if:_anotherfield_,_value_,...
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### prohibited_if:_anotherfield_,_value_,...

The field under validation must be missing or empty if the _anotherfield_ field is equal to any _value_. A field is "empty" if it meets one of the following criteria:

- The value is `null`.
- The value is an empty string.
- The value is an empty array or empty `Countable` object.
- The value is an uploaded file with an empty path.

If complex conditional prohibition logic is required, you may utilize the `Rule::prohibitedIf` method. This method accepts a boolean or a closure. When given a closure, the closure should return `true` or `false` to indicate if the field under validation should be prohibited:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => Rule::prohibitedIf($request->user()->is_admin),
]);

Validator::make($request->all(), [
    'role_id' => Rule::prohibitedIf(fn () => $request->user()->is_admin),
]);
```

---

## Upgrade Upgrading To 12.0 From 11.x Authentication Updated `DatabaseTokenRepository` Constructor Signature
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Updated `DatabaseTokenRepository` Constructor Signature

**Likelihood Of Impact: Very Low**

The constructor of the `Illuminate\Auth\Passwords\DatabaseTokenRepository` class now expects the `$expires` parameter to be given in seconds, rather than minutes.

---

## Validation Form Request Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

## Form Request Validation

---

## Database Database Transactions
Database | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md

## Database Transactions

You may use the `transaction` method provided by the `DB` facade to run a set of operations within a database transaction. If an exception is thrown within the transaction closure, the transaction will automatically be rolled back and the exception is re-thrown. If the closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method:

```php
use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
});
```

---

## Authorization Creating Policies
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

## Creating Policies

---

## Authorization Writing Policies Policy Methods
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

### Policy Methods

Once the policy class has been registered, you may add methods for each action it authorizes. For example, let's define an `update` method on our `PostPolicy` which determines if a given `App\Models\User` can update a given `App\Models\Post` instance.

The `update` method will receive a `User` and a `Post` instance as its arguments, and should return `true` or `false` indicating whether the user is authorized to update the given `Post`. So, in this example, we will verify that the user's `id` matches the `user_id` on the post:

```php
<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     */
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}
```

You may continue to define additional methods on the policy as needed for the various actions it authorizes. For example, you might define `view` or `delete` methods to authorize various `Post` related actions, but remember you are free to give your policy methods any name you like.

If you used the `--model` option when generating your policy via the Artisan console, it will already contain methods for the `viewAny`, `view`, `create`, `update`, `delete`, `restore`, and `forceDelete` actions.

> [!NOTE]
> All policies are resolved via the Laravel [service container](/docs/{{version}}/container), allowing you to type-hint any needed dependencies in the policy's constructor to have them automatically injected.

---

## Testing Running Tests Running Tests in Parallel Parallel Testing and Databases
Testing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/testing.md

#### Parallel Testing and Databases

As long as you have configured a primary database connection, Laravel automatically handles creating and migrating a test database for each parallel process that is running your tests. The test databases will be suffixed with a process token which is unique per process. For example, if you have two parallel test processes, Laravel will create and use `your_db_test_1` and `your_db_test_2` test databases.

By default, test databases persist between calls to the `test` Artisan command so that they can be used again by subsequent `test` invocations. However, you may re-create them using the `--recreate-databases` option:

```shell
php artisan test --parallel --recreate-databases
```

---

## Wire Transition Customizing animations with CSS
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

## Customizing animations with CSS

View Transitions are controlled entirely through CSS. You can customize the animation by targeting the view-transition pseudo-elements:

```css
/* Customize the transition for a specific element */
::view-transition-old(sidebar) {
    animation: 300ms ease-out both slide-out;
}

::view-transition-new(sidebar) {
    animation: 300ms ease-in both slide-in;
}

@keyframes slide-out {
    to { transform: translateX(-100%); }
}

@keyframes slide-in {
    from { transform: translateX(100%); }
}
```

The View Transitions API provides three pseudo-elements you can style:
- `::view-transition-old(name)` — The outgoing element's snapshot
- `::view-transition-new(name)` — The incoming element's snapshot
- `::view-transition-group(name)` — Container for both snapshots

---

## Authorization Introduction
Authorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md

## Introduction

In addition to providing built-in [authentication](/docs/{{version}}/authentication) services, Laravel also provides a simple way to authorize user actions against a given resource. For example, even though a user is authenticated, they may not be authorized to update or delete certain Eloquent models or database records managed by your application. Laravel's authorization features provide an easy, organized way of managing these types of authorization checks.

Laravel provides two primary ways of authorizing actions: [gates](#gates) and [policies](#creating-policies). Think of gates and policies like routes and controllers. Gates provide a simple, closure-based approach to authorization while policies, like controllers, group logic around a particular model or resource. In this documentation, we'll explore gates first and then examine policies.

You do not need to choose between exclusively using gates or exclusively using policies when building an application. Most applications will most likely contain some mixture of gates and policies, and that is perfectly fine! Gates are most applicable to actions that are not related to any model or resource, such as viewing an administrator dashboard. In contrast, policies should be used when you wish to authorize an action for a particular model or resource.

_[Truncated]_


Output:
Wall time: 4.5702 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Authorization Authorizing Actions Using Policies\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n\n## Authorizing Actions Using Policies\n\n---\n\n## Authentication Adding Custom Guards Closure Request Guards\nAuthentication | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authentication.md\n\n\n### Closure Request Guards\n\nThe simplest way to implement a custom, HTTP request based authentication system is by using the `Auth::viaRequest` method. This method allows you to quickly define your authentication process using a single closure.\n\nTo get started, call the `Auth::viaRequest` method within the `boot` method of your application's `AppServiceProvider`. The `viaRequest` method accepts an authentication driver name as its first argument. This name can be any string that describes your custom guard. The second argument passed to the method should be a closure that receives the incoming HTTP request and returns a user instance or, if authentication fails, `null`:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Auth::viaRequest('custom-token', function (Request $request) {\n        return User::where('token', (string) $request->token)->first();\n    });\n}\n```\n\nOnce your custom authentication driver has been defined, you may configure it as a driver within the `guards` configuration of your `auth.php` configuration file:\n\n```php\n'guards' => [\n    'api' => [\n        'driver' => 'custom-token',\n    ],\n],\n```\n\nFinally, you may reference the guard when assigning the authentication middleware to a route:\n\n```php\nRoute::middleware('auth:api')->group(function () {\n    // ...\n});\n```\n\n---\n\n## Precognition Customizing Validation Rules\nPrecognition | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/precognition.md\n\n\n## Customizing Validation Rules\n\nIt is possible to customize the validation rules executed during a precognitive request by using the request's `isPrecognitive` method.\n\nFor example, on a user creation form, we may want to validate that a password is \"uncompromised\" only on the final form submission. For precognitive validation requests, we will simply validate that the password is required and has a minimum of 8 characters. Using the `isPrecognitive` method, we can customize the rules defined by our form request:\n\n```php\n<?php\n\nnamespace App\\Http\\Requests;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rules\\Password;\n\nclass StoreUserRequest extends FormRequest\n{\n    /**\n     * Get the validation rules that apply to the request.\n     *\n     * @return array\n     */\n    protected function rules()\n    {\n        return [\n            'password' => [\n                'required',\n                $this->isPrecognitive()\n                    ? Password::min(8)\n                    : Password::min(8)->uncompromised(),\n            ],\n            // ...\n        ];\n    }\n}\n```\n\n---\n\n## Verification Introduction Database Preparation\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### Database Preparation\n\nNext, your `users` table must contain an `email_verified_at` column to store the date and time that the user's email address was verified. Typically, this is included in Laravel's default `0001_01_01_000000_create_users_table.php` database migration.\n\n---\n\n## Validation Custom Validation Rules\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Custom Validation Rules\n\n---\n\n## Passport Protecting Routes Via Middleware\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n### Via Middleware\n\nPassport includes an [authentication guard](/docs/{{version}}/authentication#adding-custom-guards) that will validate access tokens on incoming requests. Once you have configured the `api` guard to use the `passport` driver, you only need to specify the `auth:api` middleware on any routes that should require a valid access token:\n\n```php\nRoute::get('/user', function () {\n    // Only API authenticated users may access this route...\n})->middleware('auth:api');\n```\n\n> [!WARNING]\n> If you are using the [client credentials grant](#client-credentials-grant), you should use [the `Laravel\\Passport\\Http\\Middleware\\EnsureClientIsResourceOwner` middleware](#client-credentials-grant) to protect your routes instead of the `auth:api` middleware.\n\n---\n\n## Authentication Authentication Quickstart Protecting Routes Specifying a Guard\nAuthentication | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authentication.md\n\n\n#### Specifying a Guard\n\nWhen attaching the `auth` middleware to a route, you may also specify which \"guard\" should be used to authenticate the user. The guard specified should correspond to one of the keys in the `guards` array of your `auth.php` configuration file:\n\n```php\nRoute::get('/flights', function () {\n    // Only authenticated users may access this route...\n})->middleware('auth:admin');\n```\n\n---\n\n## Authorization Authorizing Actions Using Policies Supplying Additional Context\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n\n### Supplying Additional Context\n\nWhen authorizing actions using policies, you may pass an array as the second argument to the various authorization functions and helpers. The first element in the array will be used to determine which policy should be invoked, while the rest of the array elements are passed as parameters to the policy method and can be used for additional context when making authorization decisions. For example, consider the following `PostPolicy` method definition which contains an additional `$category` parameter:\n\n```php\n/**\n * Determine if the given post can be updated by the user.\n */\npublic function update(User $user, Post $post, int $category): bool\n{\n    return $user->id === $post->user_id &&\n           $user->canUpdateCategory($category);\n}\n```\n\nWhen attempting to determine if the authenticated user can update a given post, we can invoke this policy method like so:\n\n```php\n/**\n * Update the given blog post.\n *\n * @throws \\Illuminate\\Auth\\Access\\AuthorizationException\n */\npublic function update(Request $request, Post $post): RedirectResponse\n{\n    Gate::authorize('update', [$post, $request->category]);\n\n    // The current user can update the blog post...\n\n    return redirect('/posts');\n}\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Database Database Constructor Signature Changes\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Database Constructor Signature Changes\n\n**Likelihood Of Impact: Very Low**\n\nIn Laravel 12, several low-level database classes now require an `Illuminate\\Database\\Connection` instance to be provided via their constructors.\n\n**These changes are primarily applicable to database package maintainers - it is extremely unlikely any of these changes affect normal application development.**\n\n`Illuminate\\Database\\Schema\\Blueprint`\n\nThe constructor of the `Illuminate\\Database\\Schema\\Blueprint` class now expects a `Connection` instance as its first argument. This primarily affects applications or packages that manually instantiate `Blueprint` instances.\n\n`Illuminate\\Database\\Grammar`\n\nThe constructor of the `Illuminate\\Database\\Grammar` class also now requires a `Connection` instance. In previous versions, the connection was assigned after construction using the `setConnection()` method. This method has been removed in Laravel 12:\n\n```php\n// Laravel <= 11.x\n$grammar = new MySqlGrammar;\n$grammar->setConnection($connection);\n\n// Laravel >= 12.x\n$grammar = new MySqlGrammar($connection);\n````\n\nIn addition, the following APIs have been removed or deprecated:\n\n
\n\n- The `Blueprint::getPrefix()` method is deprecated.\n- The `Connection::withTablePrefix()` method has been removed.\n- The `Grammar::getTablePrefix()` and `setTablePrefix()` methods are deprecated.\n- The `Grammar::setConnection()` method has been removed.\n\n
\n\nWhen working with table prefixes, you should now retrieve them directly from the database connection:\n\n```php\n$prefix = $connection->getTablePrefix();\n```\n\nIf you maintain custom database drivers, schema builders, or grammar implementations, you should review their constructors and ensure a `Connection` instance is provided.\n\n---\n\n## Database Database Transactions Handling Deadlocks\nDatabase | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md\n\n\n#### Handling Deadlocks\n\nThe `transaction` method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown:\n\n```php\nuse Illuminate\\Support\\Facades\\DB;\n\nDB::transaction(function () {\n    DB::update('update users set votes = 1');\n\n    DB::delete('delete from posts');\n}, attempts: 5);\n```\n\n---\n\n## Authorization\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n# Authorization\n\n- [Introduction](#introduction)\n- [Gates](#gates)\n    - [Writing Gates](#writing-gates)\n    - [Authorizing Actions](#authorizing-actions-via-gates)\n    - [Gate Responses](#gate-responses)\n    - [Intercepting Gate Checks](#intercepting-gate-checks)\n    - [Inline Authorization](#inline-authorization)\n- [Creating Policies](#creating-policies)\n    - [Generating Policies](#generating-policies)\n    - [Registering Policies](#registering-policies)\n- [Writing Policies](#writing-policies)\n    - [Policy Methods](#policy-methods)\n    - [Policy Responses](#policy-responses)\n    - [Methods Without Models](#methods-without-models)\n    - [Guest Users](#guest-users)\n    - [Policy Filters](#policy-filters)\n- [Authorizing Actions Using Policies](#authorizing-actions-using-policies)\n    - [Via the User Model](#via-the-user-model)\n    - [Via the Gate Facade](#via-the-gate-facade)\n    - [Via Middleware](#via-middleware)\n    - [Via Blade Templates](#via-blade-templates)\n    - [Supplying Additional Context](#supplying-additional-context)\n- [Authorization & Inertia](#authorization-and-inertia)\n\n---\n\n## Validation Available Validation Rules required_if:_anotherfield_,_value_,...\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### required_if:_anotherfield_,_value_,...\n\nThe field under validation must be present and not empty if the _anotherfield_ field is equal to any _value_.\n\nIf you would like to construct a more complex condition for the `required_if` rule, you may use the `Rule::requiredIf` method. This method accepts a boolean or a closure. When passed a closure, the closure should return `true` or `false` to indicate if the field under validation is required:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rule;\n\nValidator::make($request->all(), [\n    'role_id' => Rule::requiredIf($request->user()->is_admin),\n]);\n\nValidator::make($request->all(), [\n    'role_id' => Rule::requiredIf(fn () => $request->user()->is_admin),\n]);\n```\n\n---\n\n## Passport Protecting Routes Via Middleware Multiple Authentication Guards\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n#### Multiple Authentication Guards\n\nIf your application authenticates different types of users that perhaps use entirely different Eloquent models, you will likely need to define a guard configuration for each user provider type in your application. This allows you to protect requests intended for specific user providers. For example, given the following guard configuration the `config/auth.php` configuration file:\n\n```php\n'guards' => [\n    'api' => [\n        'driver' => 'passport',\n        'provider' => 'users',\n    ],\n\n    'api-customers' => [\n        'driver' => 'passport',\n        'provider' => 'customers',\n    ],\n],\n```\n\nThe following route will utilize the `api-customers` guard, which uses the `customers` user provider, to authenticate incoming requests:\n\n```php\nRoute::get('/customer', function () {\n    // ...\n})->middleware('auth:api-customers');\n```\n\n> [!NOTE]\n> For more information on using multiple user providers with Passport, please consult the [personal access tokens documentation](#customizing-the-user-provider-for-pat) and [password grant documentation](#customizing-the-user-provider).\n\n---\n\n## Validation Available Validation Rules after:_date_\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### after:_date_\n\nThe field under validation must be a value after a given date. The dates will be passed into the `strtotime` PHP function in order to be converted to a valid `DateTime` instance:\n\n```php\n'start_date' => 'required|date|after:tomorrow'\n```\n\nInstead of passing a date string to be evaluated by `strtotime`, you may specify another field to compare against the date:\n\n```php\n'finish_date' => 'required|date|after:start_date'\n```\n\nFor convenience, date-based rules may be constructed using the fluent `date` rule builder:\n\n```php\nuse Illuminate\\Validation\\Rule;\n\n'start_date' => [\n    'required',\n    Rule::date()->after(today()->addDays(7)),\n],\n```\n\nThe `afterToday` and `todayOrAfter` methods may be used to fluently express the date and must be after today, or today or after, respectively:\n\n```php\n'start_date' => [\n    'required',\n    Rule::date()->afterToday(),\n],\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Database Multi-Schema Database Inspecting\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Multi-Schema Database Inspecting\n\n**Likelihood Of Impact: Low**\n\nThe `Schema::getTables()`, `Schema::getViews()`, and `Schema::getTypes()` methods now include the results from all schemas by default. You may pass the `schema` argument to retrieve the result for the given schema only:\n\n```php\n// All tables on all schemas...\n$tables = Schema::getTables();\n\n// All tables on the 'main' schema...\n$tables = Schema::getTables(schema: 'main');\n\n// All tables on the 'main' and 'blog' schemas...\n$tables = Schema::getTables(schema: ['main', 'blog']);\n```\n\nThe `Schema::getTableListing()` method now returns schema-qualified table names by default. You may pass the `schemaQualified` argument to change the behavior as desired:\n\n```php\n$tables = Schema::getTableListing();\n// ['main.migrations', 'main.users', 'blog.posts']\n\n$tables = Schema::getTableListing(schema: 'main');\n// ['main.migrations', 'main.users']\n\n$tables = Schema::getTableListing(schema: 'main', schemaQualified: false);\n// ['migrations', 'users']\n```\n\nThe `db:table` and `db:show` commands now output the results of all schemas on MySQL, MariaDB, and SQLite, just like PostgreSQL and SQL Server.\n\n---\n\n## Authorization Writing Policies Policy Filters\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n\n### Policy Filters\n\nFor certain users, you may wish to authorize all actions within a given policy. To accomplish this, define a `before` method on the policy. The `before` method will be executed before any other methods on the policy, giving you an opportunity to authorize the action before the intended policy method is actually called. This feature is most commonly used for authorizing application administrators to perform any action:\n\n```php\nuse App\\Models\\User;\n\n/**\n * Perform pre-authorization checks.\n */\npublic function before(User $user, string $ability): bool|null\n{\n    if ($user->isAdministrator()) {\n        return true;\n    }\n\n    return null;\n}\n```\n\nIf you would like to deny all authorization checks for a particular type of user then you may return `false` from the `before` method. If `null` is returned, the authorization check will fall through to the policy method.\n\n> [!WARNING]\n> The `before` method of a policy class will not be called if the class doesn't contain a method with a name matching the name of the ability being checked.\n\n---\n\n## Validation Available Validation Rules prohibited_if:_anotherfield_,_value_,...\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### prohibited_if:_anotherfield_,_value_,...\n\nThe field under validation must be missing or empty if the _anotherfield_ field is equal to any _value_. A field is \"empty\" if it meets one of the following criteria:\n\n
\n\n- The value is `null`.\n- The value is an empty string.\n- The value is an empty array or empty `Countable` object.\n- The value is an uploaded file with an empty path.\n\n
\n\nIf complex conditional prohibition logic is required, you may utilize the `Rule::prohibitedIf` method. This method accepts a boolean or a closure. When given a closure, the closure should return `true` or `false` to indicate if the field under validation should be prohibited:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rule;\n\nValidator::make($request->all(), [\n    'role_id' => Rule::prohibitedIf($request->user()->is_admin),\n]);\n\nValidator::make($request->all(), [\n    'role_id' => Rule::prohibitedIf(fn () => $request->user()->is_admin),\n]);\n```\n\n---\n\n## Upgrade Upgrading To 12.0 From 11.x Authentication Updated `DatabaseTokenRepository` Constructor Signature\nUpgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md\n\n\n#### Updated `DatabaseTokenRepository` Constructor Signature\n\n**Likelihood Of Impact: Very Low**\n\nThe constructor of the `Illuminate\\Auth\\Passwords\\DatabaseTokenRepository` class now expects the `$expires` parameter to be given in seconds, rather than minutes.\n\n---\n\n## Validation Form Request Validation\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Form Request Validation\n\n---\n\n## Database Database Transactions\nDatabase | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/database.md\n\n\n## Database Transactions\n\nYou may use the `transaction` method provided by the `DB` facade to run a set of operations within a database transaction. If an exception is thrown within the transaction closure, the transaction will automatically be rolled back and the exception is re-thrown. If the closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method:\n\n```php\nuse Illuminate\\Support\\Facades\\DB;\n\nDB::transaction(function () {\n    DB::update('update users set votes = 1');\n\n    DB::delete('delete from posts');\n});\n```\n\n---\n\n## Authorization Creating Policies\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n\n## Creating Policies\n\n---\n\n## Authorization Writing Policies Policy Methods\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n\n### Policy Methods\n\nOnce the policy class has been registered, you may add methods for each action it authorizes. For example, let's define an `update` method on our `PostPolicy` which determines if a given `App\\Models\\User` can update a given `App\\Models\\Post` instance.\n\nThe `update` method will receive a `User` and a `Post` instance as its arguments, and should return `true` or `false` indicating whether the user is authorized to update the given `Post`. So, in this example, we will verify that the user's `id` matches the `user_id` on the post:\n\n```php\n<?php\n\nnamespace App\\Policies;\n\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\nclass PostPolicy\n{\n    /**\n     * Determine if the given post can be updated by the user.\n     */\n    public function update(User $user, Post $post): bool\n    {\n        return $user->id === $post->user_id;\n    }\n}\n```\n\nYou may continue to define additional methods on the policy as needed for the various actions it authorizes. For example, you might define `view` or `delete` methods to authorize various `Post` related actions, but remember you are free to give your policy methods any name you like.\n\nIf you used the `--model` option when generating your policy via the Artisan console, it will already contain methods for the `viewAny`, `view`, `create`, `update`, `delete`, `restore`, and `forceDelete` actions.\n\n> [!NOTE]\n> All policies are resolved via the Laravel [service container](/docs/{{version}}/container), allowing you to type-hint any needed dependencies in the policy's constructor to have them automatically injected.\n\n---\n\n## Testing Running Tests Running Tests in Parallel Parallel Testing and Databases\nTesting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/testing.md\n\n\n#### Parallel Testing and Databases\n\nAs long as you have configured a primary database connection, Laravel automatically handles creating and migrating a test database for each parallel process that is running your tests. The test databases will be suffixed with a process token which is unique per process. For example, if you have two parallel test processes, Laravel will create and use `your_db_test_1` and `your_db_test_2` test databases.\n\nBy default, test databases persist between calls to the `test` Artisan command so that they can be used again by subsequent `test` invocations. However, you may re-create them using the `--recreate-databases` option:\n\n```shell\nphp artisan test --parallel --recreate-databases\n```\n\n---\n\n## Wire Transition Customizing animations with CSS\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## Customizing animations with CSS\n\nView Transitions are controlled entirely through CSS. You can customize the animation by targeting the view-transition pseudo-elements:\n\n```css\n/* Customize the transition for a specific element */\n::view-transition-old(sidebar) {\n    animation: 300ms ease-out both slide-out;\n}\n\n::view-transition-new(sidebar) {\n    animation: 300ms ease-in both slide-in;\n}\n\n@keyframes slide-out {\n    to { transform: translateX(-100%); }\n}\n\n@keyframes slide-in {\n    from { transform: translateX(100%); }\n}\n```\n\nThe View Transitions API provides three pseudo-elements you can style:\n- `::view-transition-old(name)` — The outgoing element's snapshot\n- `::view-transition-new(name)` — The incoming element's snapshot\n- `::view-transition-group(name)` — Container for both snapshots\n\n---\n\n## Authorization Introduction\nAuthorization | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/authorization.md\n\n\n## Introduction\n\nIn addition to providing built-in [authentication](/docs/{{version}}/authentication) services, Laravel also provides a simple way to authorize user actions against a given resource. For example, even though a user is authenticated, they may not be authorized to update or delete certain Eloquent models or database records managed by your application. Laravel's authorization features provide an easy, organized way of managing these types of authorization checks.\n\nLaravel provides two primary ways of authorizing actions: [gates](#gates) and [policies](#creating-policies). Think of gates and policies like routes and controllers. Gates provide a simple, closure-based approach to authorization while policies, like controllers, group logic around a particular model or resource. In this documentation, we'll explore gates first and then examine policies.\n\nYou do not need to choose between exclusively using gates or exclusively using policies when building an application. Most applications will most likely contain some mixture of gates and policies, and that is perfectly fine! Gates are most applicable to actions that are not related to any model or resource, such as viewing an administrator dashboard. In contrast, policies should be used when you wish to authorize an action for a particular model or resource.\n\n_[Truncated]_"}]


No critical/high blocker found in the current Herd browser pass.

**Flows Checked**
- `PASS` Storefront browse/content: `/`, `/collections`, `/collections/t-shirts`, `/products/classic-cotton-t-shirt`, `/search?q=Cotton`, `/pages/about` all rendered expected seeded content.
- `PASS` Cart/checkout: selected `M`/`Black`, added Classic Cotton T-Shirt, cart showed item, checkout completed with card `4242…`, reached `/checkout/confirmation/19`, created paid order `#1016`.
- `PASS` Customer account: logged in as `customer@acme.test`, reached `/account`, opened `/account/orders/1`, saw order `#1001`.
- `PASS` Admin: logged in as `admin@acme.test`, verified dashboard plus products, orders, customers, discounts, settings, analytics, themes, search settings pages.
- `PASS` Admin order detail: `/admin/orders/1` showed order `#1001`, line items, payments, fulfillment sections.
- `PASS` Admin product write/archive: product create persisted through UI; the two QA-created products were then archived via admin edit UI and DB confirmed `status = archived`.

**Test/Log Evidence**
- Selected existing browser tests passed:
  `php artisan test --compact tests/Browser/SmokeTest.php tests/Browser/Admin/AuthenticationTest.php tests/Browser/Storefront/CheckoutTest.php`
  Result: `33 passed (230 assertions)`.
- Current checkout run did not reproduce the old Flux checkout JS error.
- Boost browser logs still contain older `/checkout` Flux `addEventListener` errors from `2026-05-04 11:04:38`; I’d treat that as historical unless it reappears.
- Non-blocking noise: headless browser saw aborted external Bunny font requests; pages still rendered and flows worked.

No files were edited. The real Herd DB now includes test order `#1016`; QA products created during admin testing were archived.


Output: {"status":{"019df43c-0eae-7ba1-891a-455523c0d83c":{"completed":"No critical/high blocker found in the current Herd browser pass.\n\n**Flows Checked**\n- `PASS` Storefront browse/content: `/`, `/collections`, `/collections/t-shirts`, `/products/classic-cotton-t-shirt`, `/search?q=Cotton`, `/pages/about` all rendered expected seeded content.\n- `PASS` Cart/checkout: selected `M`/`Black`, added Classic Cotton T-Shirt, cart showed item, checkout completed with card `4242…`, reached `/checkout/confirmation/19`, created paid order `#1016`.\n- `PASS` Customer account: logged in as `customer@acme.test`, reached `/account`, opened `/account/orders/1`, saw order `#1001`.\n- `PASS` Admin: logged in as `admin@acme.test`, verified dashboard plus products, orders, customers, discounts, settings, analytics, themes, search settings pages.\n- `PASS` Admin order detail: `/admin/orders/1` showed order `#1001`, line items, payments, fulfillment sections.\n- `PASS` Admin product write/archive: product create persisted through UI; the two QA-created products were then archived via admin edit UI and DB confirmed `status = archived`.\n\n**Test/Log Evidence**\n- Selected existing browser tests passed:  \n  `php artisan test --compact tests/Browser/SmokeTest.php tests/Browser/Admin/AuthenticationTest.php tests/Browser/Storefront/CheckoutTest.php`  \n  Result: `33 passed (230 assertions)`.\n- Current checkout run did not reproduce the old Flux checkout JS error.\n- Boost browser logs still contain older `/checkout` Flux `addEventListener` errors from `2026-05-04 11:04:38`; I’d treat that as historical unless it reappears.\n- Non-blocking noise: headless browser saw aborted external Bunny font requests; pages still rendered and flows worked.\n\nNo files were edited. The real Herd DB now includes test order `#1016`; QA products created during admin testing were archived."}},"timed_out":false}


<subagent_notification>
{"agent_path":"019df43c-0eae-7ba1-891a-455523c0d83c","status":{"completed":"No critical/high blocker found in the current Herd browser pass.\n\n**Flows Checked**\n- `PASS` Storefront browse/content: `/`, `/collections`, `/collections/t-shirts`, `/products/classic-cotton-t-shirt`, `/search?q=Cotton`, `/pages/about` all rendered expected seeded content.\n- `PASS` Cart/checkout: selected `M`/`Black`, added Classic Cotton T-Shirt, cart showed item, checkout completed with card `4242…`, reached `/checkout/confirmation/19`, created paid order `#1016`.\n- `PASS` Customer account: logged in as `customer@acme.test`, reached `/account`, opened `/account/orders/1`, saw order `#1001`.\n- `PASS` Admin: logged in as `admin@acme.test`, verified dashboard plus products, orders, customers, discounts, settings, analytics, themes, search settings pages.\n- `PASS` Admin order detail: `/admin/orders/1` showed order `#1001`, line items, payments, fulfillment sections.\n- `PASS` Admin product write/archive: product create persisted through UI; the two QA-created products were then archived via admin edit UI and DB confirmed `status = archived`.\n\n**Test/Log Evidence**\n- Selected existing browser tests passed:  \n  `php artisan test --compact tests/Browser/SmokeTest.php tests/Browser/Admin/AuthenticationTest.php tests/Browser/Storefront/CheckoutTest.php`  \n  Result: `33 passed (230 assertions)`.\n- Current checkout run did not reproduce the old Flux checkout JS error.\n- Boost browser logs still contain older `/checkout` Flux `addEventListener` errors from `2026-05-04 11:04:38`; I’d treat that as historical unless it reappears.\n- Non-blocking noise: headless browser saw aborted external Bunny font requests; pages still rendered and flows worked.\n\nNo files were edited. The real Herd DB now includes test order `#1016`; QA products created during admin testing were archived."}}
</subagent_notification>


The browser QA is clean, but the static QA/code review blockers are actionable. I’m narrowing the fixes into four buckets: admin API/auth + Livewire authorization, checkout idempotency/route contract, HTML sanitization, and webhook event wiring/tenant-safe pivots/concurrency.


Validate QA findings against specs and existing implementation

Implement security/auth/idempotency/event fixes with tests

Run focused verification and full affected suites

Update progress evidence, commit fixes, and rerun final completion audit

Explanation: Fresh independent QA found critical/high issues, so completion is blocked until they are fixed or conclusively disproven with evidence.


Output: Plan updated


sed -n '1160,1245p' specs/02-API-ROUTES.md


sed -n '190,230p' specs/06-AUTH-AND-SECURITY.md && sed -n '800,840p' specs/06-AUTH-AND-SECURITY.md


sed -n '570,610p' specs/05-BUSINESS-LOGIC.md && sed -n '1580,1615p' specs/05-BUSINESS-LOGIC.md


sed -n '170,205p' specs/02-API-ROUTES.md && sed -n '930,950p' specs/04-STOREFRONT-UI.md


sed -n '1,260p' app/Http/Middleware/AuthenticateAdminApi.php && sed -n '1,220p' app/Policies/OrderPolicy.php && sed -n '1,160p' app/Http/Middleware/CheckStoreRole.php


sed -n '1,460p' app/Services/CheckoutService.php && sed -n '1,470p' app/Services/OrderService.php


sed -n '1160,1245p' specs/02-API-ROUTES.md

        "url": "/products/classic-t-shirt",
        "referrer": "https://google.com",
        "user_agent": "Mozilla/5.0..."
      },
      "occurred_at": "2026-02-11T10:00:00Z"
    },
    {
      "type": "add_to_cart",
      "session_id": "sess_abc123",
      "client_event_id": "evt_002",
      "properties": {
        "product_id": 10,
        "variant_id": 101,
        "quantity": 1,
        "price_amount": 2500,
        "currency": "EUR"
      },
      "occurred_at": "2026-02-11T10:01:00Z"
    }
  ]
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `events` | array | Yes | Min: 1, max: 50 events per batch. |
| `events[].type` | string | Yes | One of: `page_view`, `add_to_cart`, `remove_from_cart`, `checkout_started`, `checkout_completed`, `search`. |
| `events[].session_id` | string | Yes | Max: 100 characters. |
| `events[].client_event_id` | string | Yes | Max: 100 characters. If a `client_event_id` has already been recorded for the current store, the duplicate event is silently dropped (unique on `(store_id, client_event_id)`). |
| `events[].properties` | object | No | Freeform JSON. Max depth: 3 levels. |
| `events[].occurred_at` | string (ISO 8601) | Yes | Must not be more than 1 hour in the past or in the future. |

**Success response:** `202 Accepted`

```json
{
  "accepted": 2,
  "rejected": 0
}
```

**Error responses:**
- `422` - Validation errors (invalid event types, too many events, etc.)

---

## 3. Admin REST API

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

These endpoints provide programmatic access for integrations, CLI tools, and third-party apps. The admin web UI uses Livewire actions directly and does not call these endpoints.

All endpoints require a valid Sanctum API token with appropriate scopes. Responses follow Laravel's API Resource conventions.

---

### 3.1 Platform Management (Super-Admin Only)

These endpoints are restricted to users with the `owner` (platform) role.

#### POST /api/admin/v1/platform/organizations

Create a new organization.

- **Authentication:** Sanctum token with `manage-platform` scope
- **Rate limit:** `api.admin` (60/min per token)

**Request body:**

```json
{
  "name": "Acme Corp",
  "billing_email": "billing@acme.com"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `name` | string | Yes | Max: 255 |
| `billing_email` | string | Yes | Valid email, max: 255 |

**Success response:** `201 Created`

```json


sed -n '190,230p' specs/06-AUTH-AND-SECURITY.md && sed -n '800,840p' specs/06-AUTH-AND-SECURITY.md

- 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)
```

**Authorization in Form Requests:**

Each Form Request's `authorize()` method must check the relevant policy or gate. For example, `StoreProductRequest::authorize()` checks whether the user can create a Product. If authorization fails, the request is rejected with 403 before validation even runs.

---

### 4.5 XSS Prevention

| Context | Strategy |
|---------|----------|
| Standard Blade output | Double-curly-brace syntax auto-escapes all output (Laravel default) |
| Rich HTML output | Unescaped syntax used only for trusted HTML fields: product `description_html`, page `body_html` |
| HTML input sanitization | Use HTMLPurifier (or equivalent) on save, before persisting to database |
| Livewire properties | Auto-escaped by default when rendered in Blade |
| JSON API responses | Laravel escapes JSON output by default |

#### HTML Sanitization Requirements

An `SanitizeHtml` action class must be created at `app/Actions/SanitizeHtml.php`.

**Allowed HTML elements and attributes:**

| Element | Allowed Attributes |
|---------|-------------------|
| `p`, `br`, `strong`, `em`, `u` | none |
| `ol`, `ul`, `li` | none |
| `a` | `href` |
| `img` | `src`, `alt` |
| `h1` through `h6` | none |
| `blockquote` | none |
| `table`, `thead`, `tbody`, `tr`, `th`, `td` | none |
| `div`, `span` | none |

All other elements and attributes must be stripped. Empty elements should be removed.

**Where sanitization is applied:** Before persisting product `description_html` and page `body_html` fields. The sanitization runs during the create and update operations for these models.

---

### 4.6 SQL Injection Prevention