Output:
Chunk ID: eb5a5b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 804
Output:
{
    "$schema": "https://getcomposer.org/schema.json",
    "name": "laravel/livewire-starter-kit",
    "type": "project",
    "description": "The official Laravel starter kit for Livewire.",
    "keywords": [
        "laravel",
        "framework"
    ],
    "license": "MIT",
    "require": {
        "php": "^8.2",
        "laravel/fortify": "^1.30",
        "laravel/framework": "^12.0",
        "laravel/tinker": "^2.10.1",
        "livewire/flux": "^2.9.0",
        "livewire/livewire": "^4.0"
    },
    "require-dev": {
        "fakerphp/faker": "^1.23",
        "laravel/boost": "^2.4",
        "laravel/pail": "^1.2.2",
        "laravel/pint": "^1.24",
        "laravel/sail": "^1.41",
        "mockery/mockery": "^1.6",
        "nunomaduro/collision": "^8.6",
        "pestphp/pest": "^4.3",
        "pestphp/pest-plugin-laravel": "^4.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "setup": [
            "composer install",
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
            "@php artisan key:generate",
            "@php artisan migrate --force",
            "npm install",
            "npm run build"
        ],
        "dev": [
            "Composer\\Config::disableProcessTimeout",
            "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
        ],
        "lint": [
            "pint --parallel"
        ],
        "test:lint": [
            "pint --parallel --test"
        ],
        "test": [
            "@php artisan config:clear --ansi",
            "@test:lint",
            "@php artisan test"
        ],
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-update-cmd": [
            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi",
            "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
            "@php artisan migrate --graceful --ansi"
        ],
        "pre-package-uninstall": [
            "Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
        ]
    },
    "extra": {
        "laravel": {
            "dont-discover": []
        }
    },
    "config": {
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true,
        "allow-plugins": {
            "pestphp/pest-plugin": true,
            "php-http/discovery": true
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}


sed -n '1,180p' database/migrations/2026_05_03_211423_create_organizations_table.php


sed -n '1,180p' database/migrations/2026_05_03_211424_create_stores_table.php


sed -n '1,180p' database/migrations/2026_05_03_211425_create_store_users_table.php


sed -n '1,160p' app/Enums/StoreUserRole.php


sed -n '1,180p' database/migrations/2026_05_03_211424_create_stores_table.php

sed: database/migrations/2026_05_03_211424_create_stores_table.php: No such file or directory


sed -n '1,180p' database/migrations/2026_05_03_211423_create_organizations_table.php

sed: database/migrations/2026_05_03_211423_create_organizations_table.php: No such file or directory


sed -n '1,180p' database/migrations/2026_05_03_211425_create_store_users_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('store_users', function (Blueprint $table) {
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->enum('role', ['owner', 'admin', 'staff', 'support'])->default('staff');
            $table->timestamp('created_at')->nullable();

            $table->primary(['store_id', 'user_id']);
            $table->index('user_id');
            $table->index(['store_id', 'role']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('store_users');
    }
};


sed -n '1,160p' app/Enums/StoreUserRole.php

<?php

namespace App\Enums;

enum StoreUserRole: string
{
    case Owner = 'owner';
    case Admin = 'admin';
    case Staff = 'staff';
    case Support = 'support';
}


Output:
Chunk ID: bcf19c
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 26
Output:
sed: database/migrations/2026_05_03_211423_create_organizations_table.php: No such file or directory


Output:
Chunk ID: 8afb18
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 24
Output:
sed: database/migrations/2026_05_03_211424_create_stores_table.php: No such file or directory


Output:
Chunk ID: 3b0736
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 231
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('store_users', function (Blueprint $table) {
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->enum('role', ['owner', 'admin', 'staff', 'support'])->default('staff');
            $table->timestamp('created_at')->nullable();

            $table->primary(['store_id', 'user_id']);
            $table->index('user_id');
            $table->index(['store_id', 'role']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('store_users');
    }
};


Output:
Chunk ID: b61686
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 42
Output:
<?php

namespace App\Enums;

enum StoreUserRole: string
{
    case Owner = 'owner';
    case Admin = 'admin';
    case Staff = 'staff';
    case Support = 'support';
}


rg --files database/migrations | rg 'organization|stores_table|store_domains|store_settings|users'


rg --files database/migrations | rg 'organization|stores_table|store_domains|store_settings|users'

Question: organization|stores_table|store_domains|store_settings|users


Output:
Chunk ID: bf5dc7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 120
Output:
database/migrations/2025_08_14_170933_add_two_factor_columns_to_users_table.php
database/migrations/2026_05_03_211409_create_organizations_table.php
database/migrations/2026_05_03_211419_create_store_settings_table.php
database/migrations/2026_05_03_211413_create_stores_table.php
database/migrations/2026_05_03_211425_create_store_users_table.php
database/migrations/2026_05_03_211416_create_store_domains_table.php
database/migrations/0001_01_01_000000_create_users_table.php


sed -n '1,180p' database/migrations/2026_05_03_211409_create_organizations_table.php


sed -n '1,180p' database/migrations/2026_05_03_211413_create_stores_table.php


sed -n '1,140p' database/migrations/2026_05_03_211416_create_store_domains_table.php


sed -n '1,150p' database/migrations/2026_05_03_211419_create_store_settings_table.php


sed -n '1,180p' database/migrations/2026_05_03_211409_create_organizations_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('organizations', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('billing_email')->index();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('organizations');
    }
};


sed -n '1,180p' database/migrations/2026_05_03_211413_create_stores_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('stores', function (Blueprint $table) {
            $table->id();
            $table->foreignId('organization_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->string('handle')->unique();
            $table->enum('status', ['active', 'suspended'])->default('active')->index();
            $table->string('default_currency', 3)->default('USD');
            $table->string('default_locale')->default('en');
            $table->string('timezone')->default('UTC');
            $table->timestamps();

            $table->index('organization_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('stores');
    }
};


sed -n '1,140p' database/migrations/2026_05_03_211416_create_store_domains_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('store_domains', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('hostname')->unique();
            $table->enum('type', ['storefront', 'admin', 'api'])->default('storefront');
            $table->boolean('is_primary')->default(false);
            $table->enum('tls_mode', ['managed', 'bring_your_own'])->default('managed');
            $table->timestamp('created_at')->nullable();

            $table->index('store_id');
            $table->index(['store_id', 'is_primary']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('store_domains');
    }
};


sed -n '1,150p' database/migrations/2026_05_03_211419_create_store_settings_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('store_settings', function (Blueprint $table) {
            $table->foreignId('store_id')->primary()->constrained()->cascadeOnDelete();
            $table->text('settings_json')->default('{}');
            $table->timestamp('updated_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('store_settings');
    }
};


Output:
Chunk ID: d827ae
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 159
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('organizations', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('billing_email')->index();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('organizations');
    }
};


Output:
Chunk ID: 0b3704
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 255
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('stores', function (Blueprint $table) {
            $table->id();
            $table->foreignId('organization_id')->constrained()->cascadeOnDelete();
            $table->string('name');
            $table->string('handle')->unique();
            $table->enum('status', ['active', 'suspended'])->default('active')->index();
            $table->string('default_currency', 3)->default('USD');
            $table->string('default_locale')->default('en');
            $table->string('timezone')->default('UTC');
            $table->timestamps();

            $table->index('organization_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('stores');
    }
};


Output:
Chunk ID: d2f2df
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 257
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('store_domains', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->string('hostname')->unique();
            $table->enum('type', ['storefront', 'admin', 'api'])->default('storefront');
            $table->boolean('is_primary')->default(false);
            $table->enum('tls_mode', ['managed', 'bring_your_own'])->default('managed');
            $table->timestamp('created_at')->nullable();

            $table->index('store_id');
            $table->index(['store_id', 'is_primary']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('store_domains');
    }
};


Output:
Chunk ID: 9128a2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 172
Output:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('store_settings', function (Blueprint $table) {
            $table->foreignId('store_id')->primary()->constrained()->cascadeOnDelete();
            $table->text('settings_json')->default('{}');
            $table->timestamp('updated_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('store_settings');
    }
};


sed -n '1,140p' database/seeders/StoreUserSeeder.php


sed -n '1,140p' database/seeders/StoreUserSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Store;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class StoreUserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();

        Store::query()->each(function (Store $store) use ($user): void {
            DB::table('store_users')->updateOrInsert(
                [
                    'store_id' => $store->getKey(),
                    'user_id' => $user->getKey(),
                ],
                [
                    'role' => 'owner',
                    'created_at' => now(),
                ],
            );
        });
    }
}


Output:
Chunk ID: 5cb007
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 194
Output:
<?php

namespace Database\Seeders;

use App\Models\Store;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class StoreUserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();

        Store::query()->each(function (Store $store) use ($user): void {
            DB::table('store_users')->updateOrInsert(
                [
                    'store_id' => $store->getKey(),
                    'user_id' => $user->getKey(),
                ],
                [
                    'role' => 'owner',
                    'created_at' => now(),
                ],
            );
        });
    }
}


rg -n "invite|invitation|store_user|Platform Management|manage-platform|personal_access_tokens" specs/01-DATABASE-SCHEMA.md specs/06-AUTH-AND-SECURITY.md specs/07-SEEDERS-AND-TEST-DATA.md -S


rg -n "invite|invitation|store_user|Platform Management|manage-platform|personal_access_tokens" specs/01-DATABASE-SCHEMA.md specs/06-AUTH-AND-SECURITY.md specs/07-SEEDERS-AND-TEST-DATA.md -S

rg -n 'invite|invitation|store_user|Platform Management|manage-platform|personal_access_tokens' specs/01-DATABASE-SCHEMA.md specs/06-AUTH-AND-SECURITY.md specs/07-SEEDERS-AND-TEST-DATA.md -S

Question: invite|invitation|store_user|Platform Management|manage-platform|personal_access_tokens

specs/06-AUTH-AND-SECURITY.md:216:| Storage | `personal_access_tokens` table |
specs/06-AUTH-AND-SECURITY.md:240:- Revoking a token deletes its row from the `personal_access_tokens` table.
specs/06-AUTH-AND-SECURITY.md:263:| `manage-platform` | Platform-level management (super-admin only) |
specs/06-AUTH-AND-SECURITY.md:318:- A User can hold different roles in different stores (via the `store_users` pivot table).
specs/06-AUTH-AND-SECURITY.md:324:The `store_users` pivot table links a `User` to a `Store` with a `role`. A user can have different roles in different stores.
specs/06-AUTH-AND-SECURITY.md:396:| `getStoreRole` | User, store_id (int) | StoreUserRole or null | Looks up the user's role for the given store via the `store_users` relationship. Returns null if the user has no role in that store. |
specs/06-AUTH-AND-SECURITY.md:546:Gates are registered in `AppServiceProvider` (or a dedicated `AuthServiceProvider`). Each gate resolves the current store from the container and checks the user's role via the `store_users` relationship.
specs/06-AUTH-AND-SECURITY.md:563:2. Look up the user's `store_users` record for that store
specs/06-AUTH-AND-SECURITY.md:641:    L --> M{User has store_users record?}
specs/06-AUTH-AND-SECURITY.md:670:    B --> C[Look up store_users record for user + store]
specs/06-AUTH-AND-SECURITY.md:676:    G -- Yes --> I[Attach store_user to request attributes]
specs/06-AUTH-AND-SECURITY.md:684:3. Query the user's `store_users` relationship filtered by `store_id`.
specs/06-AUTH-AND-SECURITY.md:688:7. Store the resolved `StoreUser` pivot record on the request attributes (key: `store_user`) for downstream use.
specs/01-DATABASE-SCHEMA.md:119:### store_users
specs/01-DATABASE-SCHEMA.md:134:- `idx_store_users_user_id` on (`user_id`) - Reverse lookup: find all stores for a user
specs/01-DATABASE-SCHEMA.md:135:- `idx_store_users_role` on (`store_id`, `role`) - Listing users by role within a store
specs/01-DATABASE-SCHEMA.md:1164:### personal_access_tokens
specs/01-DATABASE-SCHEMA.md:1210:    stores ||--o{ store_users : "links"
specs/01-DATABASE-SCHEMA.md:1211:    users ||--o{ store_users : "links"
specs/01-DATABASE-SCHEMA.md:1240:    store_users {
specs/01-DATABASE-SCHEMA.md:1525:        store_users
specs/01-DATABASE-SCHEMA.md:1584:    stores --> store_users
specs/01-DATABASE-SCHEMA.md:1585:    users --> store_users
specs/01-DATABASE-SCHEMA.md:1645:- **Batch 3** (depends on Batch 2): `store_domains`, `store_users`, `store_settings`, `customers`, `themes`, `pages`, `navigation_menus`, `search_settings`, `shipping_zones`, `tax_settings`, `discounts`, `products`, `collections`, `app_installations`, `oauth_clients`
specs/01-DATABASE-SCHEMA.md:1661:4. **Composite primary keys for pivot tables** - Tables like `store_users`, `collection_products`, `variant_option_values`, and `analytics_daily` use composite primary keys to naturally enforce uniqueness and serve as their own covering index for the most common query pattern.
specs/01-DATABASE-SCHEMA.md:1673:| 1 - Foundation and Multi-Store Tenancy | organizations, stores, store_domains, users, store_users, store_settings | 6 |
specs/01-DATABASE-SCHEMA.md:1682:| Laravel framework tables | sessions, cache, cache_locks, jobs, job_batches, failed_jobs, personal_access_tokens, password_reset_tokens, customer_password_reset_tokens | 9 |


Output:
Chunk ID: 12b36a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 837
Output:
specs/06-AUTH-AND-SECURITY.md:216:| Storage | `personal_access_tokens` table |
specs/06-AUTH-AND-SECURITY.md:240:- Revoking a token deletes its row from the `personal_access_tokens` table.
specs/06-AUTH-AND-SECURITY.md:263:| `manage-platform` | Platform-level management (super-admin only) |
specs/06-AUTH-AND-SECURITY.md:318:- A User can hold different roles in different stores (via the `store_users` pivot table).
specs/06-AUTH-AND-SECURITY.md:324:The `store_users` pivot table links a `User` to a `Store` with a `role`. A user can have different roles in different stores.
specs/06-AUTH-AND-SECURITY.md:396:| `getStoreRole` | User, store_id (int) | StoreUserRole or null | Looks up the user's role for the given store via the `store_users` relationship. Returns null if the user has no role in that store. |
specs/06-AUTH-AND-SECURITY.md:546:Gates are registered in `AppServiceProvider` (or a dedicated `AuthServiceProvider`). Each gate resolves the current store from the container and checks the user's role via the `store_users` relationship.
specs/06-AUTH-AND-SECURITY.md:563:2. Look up the user's `store_users` record for that store
specs/06-AUTH-AND-SECURITY.md:641:    L --> M{User has store_users record?}
specs/06-AUTH-AND-SECURITY.md:670:    B --> C[Look up store_users record for user + store]
specs/06-AUTH-AND-SECURITY.md:676:    G -- Yes --> I[Attach store_user to request attributes]
specs/06-AUTH-AND-SECURITY.md:684:3. Query the user's `store_users` relationship filtered by `store_id`.
specs/06-AUTH-AND-SECURITY.md:688:7. Store the resolved `StoreUser` pivot record on the request attributes (key: `store_user`) for downstream use.
specs/01-DATABASE-SCHEMA.md:119:### store_users
specs/01-DATABASE-SCHEMA.md:134:- `idx_store_users_user_id` on (`user_id`) - Reverse lookup: find all stores for a user
specs/01-DATABASE-SCHEMA.md:135:- `idx_store_users_role` on (`store_id`, `role`) - Listing users by role within a store
specs/01-DATABASE-SCHEMA.md:1164:### personal_access_tokens
specs/01-DATABASE-SCHEMA.md:1210:    stores ||--o{ store_users : "links"
specs/01-DATABASE-SCHEMA.md:1211:    users ||--o{ store_users : "links"
specs/01-DATABASE-SCHEMA.md:1240:    store_users {
specs/01-DATABASE-SCHEMA.md:1525:        store_users
specs/01-DATABASE-SCHEMA.md:1584:    stores --> store_users
specs/01-DATABASE-SCHEMA.md:1585:    users --> store_users
specs/01-DATABASE-SCHEMA.md:1645:- **Batch 3** (depends on Batch 2): `store_domains`, `store_users`, `store_settings`, `customers`, `themes`, `pages`, `navigation_menus`, `search_settings`, `shipping_zones`, `tax_settings`, `discounts`, `products`, `collections`, `app_installations`, `oauth_clients`
specs/01-DATABASE-SCHEMA.md:1661:4. **Composite primary keys for pivot tables** - Tables like `store_users`, `collection_products`, `variant_option_values`, and `analytics_daily` use composite primary keys to naturally enforce uniqueness and serve as their own covering index for the most common query pattern.
specs/01-DATABASE-SCHEMA.md:1673:| 1 - Foundation and Multi-Store Tenancy | organizations, stores, store_domains, users, store_users, store_settings | 6 |
specs/01-DATABASE-SCHEMA.md:1682:| Laravel framework tables | sessions, cache, cache_locks, jobs, job_batches, failed_jobs, personal_access_tokens, password_reset_tokens, customer_password_reset_tokens | 9 |


php artisan route:list --json --except-vendor


The route comparison found another concrete Spec 02 gap: the platform/admin identity endpoints (`platform/organizations`, `platform/stores`, store invites, and store `/me`) are not registered. OAuth/app routes are explicitly deferred by the spec, but these platform routes are not, so I’m treating them as the next audit fix.


route middleware form request api resource validation

authenticated api middleware bearer token tests

database transactions validation unique store


sed -n '1,180p' app/Models/OauthToken.php


sed -n '1,180p' app/Models/AppInstallation.php


/bin/zsh
-lc
sed -n '1,220p' bootstrap/app.php

<?php

use App\Http\Middleware\AuthenticateAdminApi;
use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'admin.api' => AuthenticateAdminApi::class,
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();


# Search Results

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

## Form Request Validation

---

## 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.

---

## 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.

---

## Socialite Retrieving User Details Stateless Authentication
Socialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md

#### Stateless Authentication

The `stateless` method may be used to disable session state verification. This is useful when adding social authentication to a stateless API that does not utilize cookie based sessions:

```php
use Laravel\Socialite\Socialite;

return Socialite::driver('google')->stateless()->user();
```

---

## 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.

---

## Vite Working With Blade and Routes Aliases
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

### Aliases

It is common in JavaScript applications to [create aliases](#aliases) to regularly referenced directories. But, you may also create aliases to use in Blade by using the `macro` method on the `Illuminate\Support\Facades\Vite` class. Typically, "macros" should be defined within the `boot` method of a [service provider](/docs/{{version}}/providers):

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}"));
}
```

Once a macro has been defined, it can be invoked within your templates. For example, we can use the `image` macro defined above to reference an asset located at `resources/images/logo.png`:

```blade
<img src="{{ Vite::image('logo.png') }}" alt="Laravel Logo">
```

---

## Http Tests Available Assertions Authentication Assertions
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

### Authentication Assertions

Laravel also provides a variety of authentication related assertions that you may utilize within your application's feature tests. Note that these methods are invoked on the test class itself and not the `Illuminate\Testing\TestResponse` instance returned by methods such as `get` and `post`.

---

## 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');
});
```

---

## 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.

---

## Socialite Authentication Optional Parameters
Socialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md

### Optional Parameters

A number of OAuth providers support other optional parameters on the redirect request. To include any optional parameters in the request, call the `with` method with an associative array:

```php
use Laravel\Socialite\Socialite;

return Socialite::driver('google')
    ->with(['hd' => 'example.com'])
    ->redirect();
```

> [!WARNING]
> When using the `with` method, be careful not to pass any reserved keywords such as `state` or `response_type`.

---

## Vite Working With Blade and Routes Refreshing on Save
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

### Refreshing on Save

When your application is built using traditional server-side rendering with Blade, Vite can improve your development workflow by automatically refreshing the browser when you make changes to view files in your application. To get started, you can simply specify the `refresh` option as `true`.

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: true,
        }),
    ],
});
```

When the `refresh` option is `true`, saving files in the following directories will trigger the browser to perform a full page refresh while you are running `npm run dev`:

- `app/Livewire/**`
- `app/View/Components/**`
- `lang/**`
- `resources/lang/**`
- `resources/views/**`
- `routes/**`

Watching the `routes/**` directory is useful if you are utilizing [Ziggy](https://github.com/tighten/ziggy) to generate route links within your application's frontend.

If these default paths do not suit your needs, you can specify your own list of paths to watch:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: ['resources/views/**'],
        }),
    ],
});
```

Under the hood, the Laravel Vite plugin uses the [vite-plugin-full-reload](https://github.com/ElMassimo/vite-plugin-full-reload) package, which offers some advanced configuration options to fine-tune this feature's behavior. If you need this level of customization, you may provide a `config` definition:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: [{
                paths: ['path/to/watch/**'],
                config: { delay: 300 }
            }],
        }),
    ],
});
```

---

## Socialite Authentication Slack Bot Scopes
Socialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md

### Slack Bot Scopes

Slack's API provides [different types of access tokens](https://api.slack.com/authentication/token-types), each with their own set of [permission scopes](https://api.slack.com/scopes). Socialite is compatible with both of the following Slack access tokens types:

- Bot (prefixed with `xoxb-`)
- User (prefixed with `xoxp-`)

By default, the `slack` driver will generate a `user` token and invoking the driver's `user` method will return the user's details.

Bot tokens are primarily useful if your application will be sending notifications to external Slack workspaces that are owned by your application's users. To generate a bot token, invoke the `asBotUser` method before redirecting the user to Slack for authentication:

```php
return Socialite::driver('slack')
    ->asBotUser()
    ->setScopes(['chat:write', 'chat:write.public', 'chat:write.customize'])
    ->redirect();
```

In addition, you must invoke the `asBotUser` method before invoking the `user` method after Slack redirects the user back to your application after authentication:

```php
$user = Socialite::driver('slack')->asBotUser()->user();
```

When generating a bot token, the `user` method will still return a `Laravel\Socialite\Two\User` instance; however, only the `token` property will be hydrated. This token may be stored in order to [send notifications to the authenticated user's Slack workspaces](/docs/{{version}}/notifications#notifying-external-slack-workspaces).

---

## Vite Working With Blade and Routes Processing Static Assets With Vite
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

### Processing Static Assets With Vite

When referencing assets in your JavaScript or CSS, Vite automatically processes and versions them. In addition, when building Blade based applications, Vite can also process and version static assets that you reference solely in Blade templates.

However, in order to accomplish this, you need to make Vite aware of your assets by importing the static assets into the application's entry point. For example, if you want to process and version all images stored in `resources/images` and all fonts stored in `resources/fonts`, you should add the following in your application's `resources/js/app.js` entry point:

```js
import.meta.glob([
  '../images/**',
  '../fonts/**',
]);
```

These assets will now be processed by Vite when running `npm run build`. You can then reference these assets in Blade templates using the `Vite::asset` method, which will return the versioned URL for a given asset:

```blade
<img src="{{ Vite::asset('resources/images/logo.png') }}">
```

---

## Passport SPA Authentication
Passport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md

## SPA Authentication

When building an API, it can be extremely useful to be able to consume your own API from your JavaScript application. This approach to API development allows your own application to consume the same API that you are sharing with the world. The same API may be consumed by your web application, mobile applications, third-party applications, and any SDKs that you may publish on various package managers.

Typically, if you want to consume your API from your JavaScript application, you would need to manually send an access token to the application and pass it with each request to your application. However, Passport includes a middleware that can handle this for you. All you need to do is append the `CreateFreshApiToken` middleware to the `web` middleware group in your application's `bootstrap/app.php` file:

```php
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        CreateFreshApiToken::class,
    ]);
})
```

> [!WARNING]
> You should ensure that the `CreateFreshApiToken` middleware is the last middleware listed in your middleware stack.

This middleware will attach a `laravel_token` cookie to your outgoing responses. This cookie contains an encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. The JWT has a lifetime equal to your `session.lifetime` configuration value. Now, since the browser will automatically send the cookie with all subsequent requests, you may make requests to your application's API without explicitly passing an access token:

```js
axios.get('/api/user')
    .then(response => {
        console.log(response.data);
    });
```

---

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

---

## 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.

---

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

---

## Vite Working With Blade and Routes
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

## Working With Blade and Routes

---

## Socialite Authentication Access Scopes
Socialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md

### Access Scopes

Before redirecting the user, you may use the `scopes` method to specify the "scopes" that should be included in the authentication request. This method will merge all previously specified scopes with the scopes that you specify:

```php
use Laravel\Socialite\Socialite;

return Socialite::driver('github')
    ->scopes(['read:user', 'public_repo'])
    ->redirect();
```

You can overwrite all existing scopes on the authentication request using the `setScopes` method:

```php
return Socialite::driver('github')
    ->setScopes(['read:user', 'public_repo'])
    ->redirect();
```

---

## Verification Routing Protecting Routes
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

### Protecting Routes

[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\Auth\Middleware\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware:

```php
Route::get('/profile', function () {
    // Only verified users may access this route...
})->middleware(['auth', 'verified']);
```

If an unverified user attempts to access a route that has been assigned this middleware, they will automatically be redirected to the `verification.notice` [named route](/docs/{{version}}/routing#named-routes).

---

## Socialite Authentication Authentication and Storage
Socialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md

### Authentication and Storage

Once the user has been retrieved from the OAuth provider, you may determine if the user exists in your application's database and [authenticate the user](/docs/{{version}}/authentication#authenticate-a-user-instance). If the user does not exist in your application's database, you will typically create a new record in your database to represent the user:

```php
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Socialite;

Route::get('/auth/callback', function () {
    $githubUser = Socialite::driver('github')->user();

    $user = User::updateOrCreate([
        'github_id' => $githubUser->id,
    ], [
        'name' => $githubUser->name,
        'email' => $githubUser->email,
        'github_token' => $githubUser->token,
        'github_refresh_token' => $githubUser->refreshToken,
    ]);

    Auth::login($user);

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

> [!NOTE]
> For more information regarding what user information is available from specific OAuth providers, please consult the documentation on [retrieving user details](#retrieving-user-details).

_[Truncated]_


Output:
Wall time: 3.4173 seconds
Output:
[{"type":"text","text":"# Search Results\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## 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## 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## Socialite Retrieving User Details Stateless Authentication\nSocialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md\n\n\n#### Stateless Authentication\n\nThe `stateless` method may be used to disable session state verification. This is useful when adding social authentication to a stateless API that does not utilize cookie based sessions:\n\n```php\nuse Laravel\\Socialite\\Socialite;\n\nreturn Socialite::driver('google')->stateless()->user();\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## Vite Working With Blade and Routes Aliases\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Aliases\n\nIt is common in JavaScript applications to [create aliases](#aliases) to regularly referenced directories. But, you may also create aliases to use in Blade by using the `macro` method on the `Illuminate\\Support\\Facades\\Vite` class. Typically, \"macros\" should be defined within the `boot` method of a [service provider](/docs/{{version}}/providers):\n\n```php\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Vite::macro('image', fn (string $asset) => $this->asset(\"resources/images/{$asset}\"));\n}\n```\n\nOnce a macro has been defined, it can be invoked within your templates. For example, we can use the `image` macro defined above to reference an asset located at `resources/images/logo.png`:\n\n```blade\n<img src=\"{{ Vite::image('logo.png') }}\" alt=\"Laravel Logo\">\n```\n\n---\n\n## Http Tests Available Assertions Authentication Assertions\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n### Authentication Assertions\n\nLaravel also provides a variety of authentication related assertions that you may utilize within your application's feature tests. Note that these methods are invoked on the test class itself and not the `Illuminate\\Testing\\TestResponse` instance returned by methods such as `get` and `post`.\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## 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## Socialite Authentication Optional Parameters\nSocialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md\n\n\n### Optional Parameters\n\nA number of OAuth providers support other optional parameters on the redirect request. To include any optional parameters in the request, call the `with` method with an associative array:\n\n```php\nuse Laravel\\Socialite\\Socialite;\n\nreturn Socialite::driver('google')\n    ->with(['hd' => 'example.com'])\n    ->redirect();\n```\n\n> [!WARNING]\n> When using the `with` method, be careful not to pass any reserved keywords such as `state` or `response_type`.\n\n---\n\n## Vite Working With Blade and Routes Refreshing on Save\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Refreshing on Save\n\nWhen your application is built using traditional server-side rendering with Blade, Vite can improve your development workflow by automatically refreshing the browser when you make changes to view files in your application. To get started, you can simply specify the `refresh` option as `true`.\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: true,\n        }),\n    ],\n});\n```\n\nWhen the `refresh` option is `true`, saving files in the following directories will trigger the browser to perform a full page refresh while you are running `npm run dev`:\n\n- `app/Livewire/**`\n- `app/View/Components/**`\n- `lang/**`\n- `resources/lang/**`\n- `resources/views/**`\n- `routes/**`\n\nWatching the `routes/**` directory is useful if you are utilizing [Ziggy](https://github.com/tighten/ziggy) to generate route links within your application's frontend.\n\nIf these default paths do not suit your needs, you can specify your own list of paths to watch:\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: ['resources/views/**'],\n        }),\n    ],\n});\n```\n\nUnder the hood, the Laravel Vite plugin uses the [vite-plugin-full-reload](https://github.com/ElMassimo/vite-plugin-full-reload) package, which offers some advanced configuration options to fine-tune this feature's behavior. If you need this level of customization, you may provide a `config` definition:\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: [{\n                paths: ['path/to/watch/**'],\n                config: { delay: 300 }\n            }],\n        }),\n    ],\n});\n```\n\n---\n\n## Socialite Authentication Slack Bot Scopes\nSocialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md\n\n\n### Slack Bot Scopes\n\nSlack's API provides [different types of access tokens](https://api.slack.com/authentication/token-types), each with their own set of [permission scopes](https://api.slack.com/scopes). Socialite is compatible with both of the following Slack access tokens types:\n\n
\n\n- Bot (prefixed with `xoxb-`)\n- User (prefixed with `xoxp-`)\n\n
\n\nBy default, the `slack` driver will generate a `user` token and invoking the driver's `user` method will return the user's details.\n\nBot tokens are primarily useful if your application will be sending notifications to external Slack workspaces that are owned by your application's users. To generate a bot token, invoke the `asBotUser` method before redirecting the user to Slack for authentication:\n\n```php\nreturn Socialite::driver('slack')\n    ->asBotUser()\n    ->setScopes(['chat:write', 'chat:write.public', 'chat:write.customize'])\n    ->redirect();\n```\n\nIn addition, you must invoke the `asBotUser` method before invoking the `user` method after Slack redirects the user back to your application after authentication:\n\n```php\n$user = Socialite::driver('slack')->asBotUser()->user();\n```\n\nWhen generating a bot token, the `user` method will still return a `Laravel\\Socialite\\Two\\User` instance; however, only the `token` property will be hydrated. This token may be stored in order to [send notifications to the authenticated user's Slack workspaces](/docs/{{version}}/notifications#notifying-external-slack-workspaces).\n\n---\n\n## Vite Working With Blade and Routes Processing Static Assets With Vite\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Processing Static Assets With Vite\n\nWhen referencing assets in your JavaScript or CSS, Vite automatically processes and versions them. In addition, when building Blade based applications, Vite can also process and version static assets that you reference solely in Blade templates.\n\nHowever, in order to accomplish this, you need to make Vite aware of your assets by importing the static assets into the application's entry point. For example, if you want to process and version all images stored in `resources/images` and all fonts stored in `resources/fonts`, you should add the following in your application's `resources/js/app.js` entry point:\n\n```js\nimport.meta.glob([\n  '../images/**',\n  '../fonts/**',\n]);\n```\n\nThese assets will now be processed by Vite when running `npm run build`. You can then reference these assets in Blade templates using the `Vite::asset` method, which will return the versioned URL for a given asset:\n\n```blade\n<img src=\"{{ Vite::asset('resources/images/logo.png') }}\">\n```\n\n---\n\n## Passport SPA Authentication\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n## SPA Authentication\n\nWhen building an API, it can be extremely useful to be able to consume your own API from your JavaScript application. This approach to API development allows your own application to consume the same API that you are sharing with the world. The same API may be consumed by your web application, mobile applications, third-party applications, and any SDKs that you may publish on various package managers.\n\nTypically, if you want to consume your API from your JavaScript application, you would need to manually send an access token to the application and pass it with each request to your application. However, Passport includes a middleware that can handle this for you. All you need to do is append the `CreateFreshApiToken` middleware to the `web` middleware group in your application's `bootstrap/app.php` file:\n\n```php\nuse Laravel\\Passport\\Http\\Middleware\\CreateFreshApiToken;\n\n->withMiddleware(function (Middleware $middleware): void {\n    $middleware->web(append: [\n        CreateFreshApiToken::class,\n    ]);\n})\n```\n\n> [!WARNING]\n> You should ensure that the `CreateFreshApiToken` middleware is the last middleware listed in your middleware stack.\n\nThis middleware will attach a `laravel_token` cookie to your outgoing responses. This cookie contains an encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. The JWT has a lifetime equal to your `session.lifetime` configuration value. Now, since the browser will automatically send the cookie with all subsequent requests, you may make requests to your application's API without explicitly passing an access token:\n\n```js\naxios.get('/api/user')\n    .then(response => {\n        console.log(response.data);\n    });\n```\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## 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## 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## Vite Working With Blade and Routes\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n## Working With Blade and Routes\n\n---\n\n## Socialite Authentication Access Scopes\nSocialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md\n\n\n### Access Scopes\n\nBefore redirecting the user, you may use the `scopes` method to specify the \"scopes\" that should be included in the authentication request. This method will merge all previously specified scopes with the scopes that you specify:\n\n```php\nuse Laravel\\Socialite\\Socialite;\n\nreturn Socialite::driver('github')\n    ->scopes(['read:user', 'public_repo'])\n    ->redirect();\n```\n\nYou can overwrite all existing scopes on the authentication request using the `setScopes` method:\n\n```php\nreturn Socialite::driver('github')\n    ->setScopes(['read:user', 'public_repo'])\n    ->redirect();\n```\n\n---\n\n## Verification Routing Protecting Routes\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### Protecting Routes\n\n[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\\Auth\\Middleware\\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware:\n\n```php\nRoute::get('/profile', function () {\n    // Only verified users may access this route...\n})->middleware(['auth', 'verified']);\n```\n\nIf an unverified user attempts to access a route that has been assigned this middleware, they will automatically be redirected to the `verification.notice` [named route](/docs/{{version}}/routing#named-routes).\n\n---\n\n## Socialite Authentication Authentication and Storage\nSocialite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/socialite.md\n\n\n### Authentication and Storage\n\nOnce the user has been retrieved from the OAuth provider, you may determine if the user exists in your application's database and [authenticate the user](/docs/{{version}}/authentication#authenticate-a-user-instance). If the user does not exist in your application's database, you will typically create a new record in your database to represent the user:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Laravel\\Socialite\\Socialite;\n\nRoute::get('/auth/callback', function () {\n    $githubUser = Socialite::driver('github')->user();\n\n    $user = User::updateOrCreate([\n        'github_id' => $githubUser->id,\n    ], [\n        'name' => $githubUser->name,\n        'email' => $githubUser->email,\n        'github_token' => $githubUser->token,\n        'github_refresh_token' => $githubUser->refreshToken,\n    ]);\n\n    Auth::login($user);\n\n    return redirect('/dashboard');\n});\n```\n\n> [!NOTE]\n> For more information regarding what user information is available from specific OAuth providers, please consult the documentation on [retrieving user details](#retrieving-user-details).\n\n_[Truncated]_"}]


Output:
Chunk ID: 1ddaa5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 277
Output:
<?php

use App\Http\Middleware\AuthenticateAdminApi;
use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'admin.api' => AuthenticateAdminApi::class,
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();


sed -n '1,180p' app/Models/AppInstallation.php

<?php

namespace App\Models;

use App\Enums\AppInstallationStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class AppInstallation extends Model
{
    /** @use HasFactory<\Database\Factories\AppInstallationFactory> */
    use BelongsToStore, HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'app_id',
        'scopes_json',
        'status',
        'installed_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'scopes_json' => '[]',
        'status' => 'active',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return BelongsTo<App, $this>
     */
    public function app(): BelongsTo
    {
        return $this->belongsTo(App::class);
    }

    /**
     * @return HasMany<OauthToken, $this>
     */
    public function oauthTokens(): HasMany
    {
        return $this->hasMany(OauthToken::class, 'installation_id');
    }

    /**
     * @return HasMany<WebhookSubscription, $this>
     */
    public function webhookSubscriptions(): HasMany
    {
        return $this->hasMany(WebhookSubscription::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'scopes_json' => 'array',
            'status' => AppInstallationStatus::class,
            'installed_at' => 'datetime',
        ];
    }
}


sed -n '1,180p' app/Models/OauthToken.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class OauthToken extends Model
{
    /** @use HasFactory<\Database\Factories\OauthTokenFactory> */
    use HasFactory;

    public const UPDATED_AT = null;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'installation_id',
        'name',
        'access_token_hash',
        'refresh_token_hash',
        'abilities_json',
        'expires_at',
        'last_used_at',
        'created_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'abilities_json' => '[]',
    ];

    /**
     * @return BelongsTo<AppInstallation, $this>
     */
    public function installation(): BelongsTo
    {
        return $this->belongsTo(AppInstallation::class, 'installation_id');
    }

    public function isExpired(): bool
    {
        return $this->expires_at->isPast();
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'abilities_json' => 'array',
            'expires_at' => 'datetime',
            'last_used_at' => 'datetime',
            'created_at' => 'datetime',
        ];
    }
}


Output:
Chunk ID: 1d53ae
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 331
Output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class OauthToken extends Model
{
    /** @use HasFactory<\Database\Factories\OauthTokenFactory> */
    use HasFactory;

    public const UPDATED_AT = null;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'installation_id',
        'name',
        'access_token_hash',
        'refresh_token_hash',
        'abilities_json',
        'expires_at',
        'last_used_at',
        'created_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'abilities_json' => '[]',
    ];

    /**
     * @return BelongsTo<AppInstallation, $this>
     */
    public function installation(): BelongsTo
    {
        return $this->belongsTo(AppInstallation::class, 'installation_id');
    }

    public function isExpired(): bool
    {
        return $this->expires_at->isPast();
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'abilities_json' => 'array',
            'expires_at' => 'datetime',
            'last_used_at' => 'datetime',
            'created_at' => 'datetime',
        ];
    }
}


Output:
Chunk ID: ea8944
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 440
Output:
<?php

namespace App\Models;

use App\Enums\AppInstallationStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class AppInstallation extends Model
{
    /** @use HasFactory<\Database\Factories\AppInstallationFactory> */
    use BelongsToStore, HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'app_id',
        'scopes_json',
        'status',
        'installed_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'scopes_json' => '[]',
        'status' => 'active',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return BelongsTo<App, $this>
     */
    public function app(): BelongsTo
    {
        return $this->belongsTo(App::class);
    }

    /**
     * @return HasMany<OauthToken, $this>
     */
    public function oauthTokens(): HasMany
    {
        return $this->hasMany(OauthToken::class, 'installation_id');
    }

    /**
     * @return HasMany<WebhookSubscription, $this>
     */
    public function webhookSubscriptions(): HasMany
    {
        return $this->hasMany(WebhookSubscription::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'scopes_json' => 'array',
            'status' => AppInstallationStatus::class,
            'installed_at' => 'datetime',
        ];
    }
}


sed -n '1,160p' app/Models/Organization.php


sed -n '1,160p' app/Models/StoreSettings.php


sed -n '1,180p' database/factories/OrganizationFactory.php


sed -n '1,180p' database/factories/StoreFactory.php


sed -n '1,160p' app/Models/Organization.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Organization extends Model
{
    /** @use HasFactory<\Database\Factories\OrganizationFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'billing_email',
    ];

    /**
     * @return HasMany<Store, $this>
     */
    public function stores(): HasMany
    {
        return $this->hasMany(Store::class);
    }
}


/bin/zsh
-lc
sed -n '1,180p' app/Models/Store.php

<?php

namespace App\Models;

use App\Enums\StoreStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Store extends Model
{
    /** @use HasFactory<\Database\Factories\StoreFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'organization_id',
        'name',
        'handle',
        'status',
        'default_currency',
        'default_locale',
        'timezone',
    ];

    /**
     * @return BelongsTo<Organization, $this>
     */
    public function organization(): BelongsTo
    {
        return $this->belongsTo(Organization::class);
    }

    /**
     * @return HasMany<StoreDomain, $this>
     */
    public function domains(): HasMany
    {
        return $this->hasMany(StoreDomain::class);
    }

    /**
     * @return BelongsToMany<User, $this>
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'store_users')
            ->using(StoreUser::class)
            ->withPivot('role');
    }

    /**
     * @return HasOne<StoreSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(StoreSettings::class);
    }

    /**
     * @return HasMany<Theme, $this>
     */
    public function themes(): HasMany
    {
        return $this->hasMany(Theme::class);
    }

    /**
     * @return HasMany<Page, $this>
     */
    public function pages(): HasMany
    {
        return $this->hasMany(Page::class);
    }

    /**
     * @return HasMany<NavigationMenu, $this>
     */
    public function navigationMenus(): HasMany
    {
        return $this->hasMany(NavigationMenu::class);
    }

    /**
     * @return HasMany<Cart, $this>
     */
    public function carts(): HasMany
    {
        return $this->hasMany(Cart::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return HasMany<ShippingZone, $this>
     */
    public function shippingZones(): HasMany
    {
        return $this->hasMany(ShippingZone::class);
    }

    /**
     * @return HasOne<TaxSettings, $this>
     */
    public function taxSettings(): HasOne
    {
        return $this->hasOne(TaxSettings::class);
    }

    /**
     * @return HasMany<Discount, $this>
     */
    public function discounts(): HasMany
    {
        return $this->hasMany(Discount::class);
    }

    /**
     * @return HasMany<Order, $this>
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    /**
     * @return HasMany<AppInstallation, $this>
     */
    public function appInstallations(): HasMany
    {
        return $this->hasMany(AppInstallation::class);
    }

    /**
     * @return HasMany<WebhookSubscription, $this>
     */
    public function webhookSubscriptions(): HasMany
    {
        return $this->hasMany(WebhookSubscription::class);
    }

    public function isSuspended(): bool
    {
        return $this->status === StoreStatus::Suspended;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => StoreStatus::class,
        ];
    }
}


sed -n '1,160p' app/Models/StoreSettings.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class StoreSettings extends Model
{
    /** @use HasFactory<\Database\Factories\StoreSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'settings_json',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}


sed -n '1,180p' database/factories/OrganizationFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Organization>
 */
class OrganizationFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'billing_email' => fake()->unique()->companyEmail(),
        ];
    }
}


sed -n '1,180p' database/factories/StoreFactory.php

<?php

namespace Database\Factories;

use App\Enums\StoreStatus;
use App\Models\Organization;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Store>
 */
class StoreFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $name = fake()->company().' Store';

        return [
            'organization_id' => Organization::factory(),
            'name' => $name,
            'handle' => Str::slug(fake()->unique()->words(2, true)),
            'status' => StoreStatus::Active,
            'default_currency' => 'EUR',
            'default_locale' => 'en',
            'timezone' => 'Europe/Berlin',
        ];
    }

    public function suspended(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => StoreStatus::Suspended,
        ]);
    }
}


Output:
Chunk ID: a88a13
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 143
Output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Organization extends Model
{
    /** @use HasFactory<\Database\Factories\OrganizationFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'billing_email',
    ];

    /**
     * @return HasMany<Store, $this>
     */
    public function stores(): HasMany
    {
        return $this->hasMany(Store::class);
    }
}


Output:
Chunk ID: ea41fa
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 883
Output:
<?php

namespace App\Models;

use App\Enums\StoreStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Store extends Model
{
    /** @use HasFactory<\Database\Factories\StoreFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'organization_id',
        'name',
        'handle',
        'status',
        'default_currency',
        'default_locale',
        'timezone',
    ];

    /**
     * @return BelongsTo<Organization, $this>
     */
    public function organization(): BelongsTo
    {
        return $this->belongsTo(Organization::class);
    }

    /**
     * @return HasMany<StoreDomain, $this>
     */
    public function domains(): HasMany
    {
        return $this->hasMany(StoreDomain::class);
    }

    /**
     * @return BelongsToMany<User, $this>
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'store_users')
            ->using(StoreUser::class)
            ->withPivot('role');
    }

    /**
     * @return HasOne<StoreSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(StoreSettings::class);
    }

    /**
     * @return HasMany<Theme, $this>
     */
    public function themes(): HasMany
    {
        return $this->hasMany(Theme::class);
    }

    /**
     * @return HasMany<Page, $this>
     */
    public function pages(): HasMany
    {
        return $this->hasMany(Page::class);
    }

    /**
     * @return HasMany<NavigationMenu, $this>
     */
    public function navigationMenus(): HasMany
    {
        return $this->hasMany(NavigationMenu::class);
    }

    /**
     * @return HasMany<Cart, $this>
     */
    public function carts(): HasMany
    {
        return $this->hasMany(Cart::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return HasMany<ShippingZone, $this>
     */
    public function shippingZones(): HasMany
    {
        return $this->hasMany(ShippingZone::class);
    }

    /**
     * @return HasOne<TaxSettings, $this>
     */
    public function taxSettings(): HasOne
    {
        return $this->hasOne(TaxSettings::class);
    }

    /**
     * @return HasMany<Discount, $this>
     */
    public function discounts(): HasMany
    {
        return $this->hasMany(Discount::class);
    }

    /**
     * @return HasMany<Order, $this>
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    /**
     * @return HasMany<AppInstallation, $this>
     */
    public function appInstallations(): HasMany
    {
        return $this->hasMany(AppInstallation::class);
    }

    /**
     * @return HasMany<WebhookSubscription, $this>
     */
    public function webhookSubscriptions(): HasMany
    {
        return $this->hasMany(WebhookSubscription::class);
    }

    public function isSuspended(): bool
    {
        return $this->status === StoreStatus::Suspended;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => StoreStatus::class,
        ];
    }
}


Output:
Chunk ID: 7feac3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 217
Output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class StoreSettings extends Model
{
    /** @use HasFactory<\Database\Factories\StoreSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'settings_json',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}


Output:
Chunk ID: cfd249
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 130
Output:
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Organization>
 */
class OrganizationFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'billing_email' => fake()->unique()->companyEmail(),
        ];
    }
}


Output:
Chunk ID: 191426
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 257
Output:
<?php

namespace Database\Factories;

use App\Enums\StoreStatus;
use App\Models\Organization;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Store>
 */
class StoreFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $name = fake()->company().' Store';

        return [
            'organization_id' => Organization::factory(),
            'name' => $name,
            'handle' => Str::slug(fake()->unique()->words(2, true)),
            'status' => StoreStatus::Active,
            'default_currency' => 'EUR',
            'default_locale' => 'en',
            'timezone' => 'Europe/Berlin',
        ];
    }

    public function suspended(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => StoreStatus::Suspended,
        ]);
    }
}


I’ll keep this platform slice minimal and self-contained: session admins with an owner role can manage platform resources, bearer tokens need `manage-platform`, store `/me` reports membership, and invites return the spec payload without adding an unspecced invitation table.


php artisan make:middleware AuthenticatePlatformApi --no-interaction


php artisan make:controller Api/Admin/V1/PlatformOrganizationController --no-interaction


php artisan make:controller Api/Admin/V1/PlatformStoreController --no-interaction


php artisan make:controller Api/Admin/V1/StoreInviteController --no-interaction


php artisan make:controller Api/Admin/V1/StoreMembershipController --no-interaction


Output:
Chunk ID: c5a461
Wall time: 0.1707 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Middleware [app/Http/Middleware/AuthenticatePlatformApi.php] created successfully.


Output:
Chunk ID: 93e857
Wall time: 0.1709 seconds
Process exited with code 0
Original token count: 30
Output:

   INFO  Controller [app/Http/Controllers/Api/Admin/V1/PlatformOrganizationController.php] created successfully.


Output:
Chunk ID: 230ecf
Wall time: 0.1629 seconds
Process exited with code 0
Original token count: 28
Output:

   INFO  Controller [app/Http/Controllers/Api/Admin/V1/PlatformStoreController.php] created successfully.


Output:
Chunk ID: b97089
Wall time: 0.1616 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Controller [app/Http/Controllers/Api/Admin/V1/StoreInviteController.php] created successfully.


Output:
Chunk ID: 1a35c7
Wall time: 0.1604 seconds
Process exited with code 0
Original token count: 28
Output:

   INFO  Controller [app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php] created successfully.


php artisan make:middleware AuthenticatePlatformApi --no-interaction

INFO  Middleware [app/Http/Middleware/AuthenticatePlatformApi.php] created successfully.


php artisan make:controller Api/Admin/V1/PlatformOrganizationController --no-interaction

INFO  Controller [app/Http/Controllers/Api/Admin/V1/PlatformOrganizationController.php] created successfully.


php artisan make:controller Api/Admin/V1/PlatformStoreController --no-interaction

INFO  Controller [app/Http/Controllers/Api/Admin/V1/PlatformStoreController.php] created successfully.


php artisan make:controller Api/Admin/V1/StoreInviteController --no-interaction

INFO  Controller [app/Http/Controllers/Api/Admin/V1/StoreInviteController.php] created successfully.


php artisan make:controller Api/Admin/V1/StoreMembershipController --no-interaction

INFO  Controller [app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php] created successfully.


php artisan make:request Api/Admin/V1/StorePlatformOrganizationRequest --no-interaction


php artisan make:request Api/Admin/V1/StorePlatformStoreRequest --no-interaction


php artisan make:request Api/Admin/V1/StoreInviteRequest --no-interaction


php artisan make:resource Admin/V1/OrganizationResource --no-interaction


php artisan make:resource Admin/V1/StoreResource --no-interaction


php artisan make:test Api/AdminPlatformApiTest --pest --no-interaction


Output:
Chunk ID: 693463
Wall time: 0.1733 seconds
Process exited with code 0
Original token count: 29
Output:

   INFO  Request [app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php] created successfully.


Output:
Chunk ID: ef038f
Wall time: 0.1683 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Request [app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php] created successfully.


Output:
Chunk ID: d29c95
Wall time: 0.1656 seconds
Process exited with code 0
Original token count: 25
Output:

   INFO  Request [app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php] created successfully.


Output:
Chunk ID: 986a75
Wall time: 0.1568 seconds
Process exited with code 0
Original token count: 25
Output:

   INFO  Resource [app/Http/Resources/Admin/V1/OrganizationResource.php] created successfully.


Output:
Chunk ID: 90535e
Wall time: 0.1544 seconds
Process exited with code 0
Original token count: 23
Output:

   INFO  Resource [app/Http/Resources/Admin/V1/StoreResource.php] created successfully.


Output:
Chunk ID: 884375
Wall time: 0.1567 seconds
Process exited with code 0
Original token count: 22
Output:

   INFO  Test [tests/Feature/Api/AdminPlatformApiTest.php] created successfully.


php artisan make:request Api/Admin/V1/StoreInviteRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php] created successfully.


php artisan make:test Api/AdminPlatformApiTest --pest --no-interaction

INFO  Test [tests/Feature/Api/AdminPlatformApiTest.php] created successfully.


php artisan make:resource Admin/V1/StoreResource --no-interaction

INFO  Resource [app/Http/Resources/Admin/V1/StoreResource.php] created successfully.


php artisan make:request Api/Admin/V1/StorePlatformOrganizationRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php] created successfully.


php artisan make:request Api/Admin/V1/StorePlatformStoreRequest --no-interaction

INFO  Request [app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php] created successfully.


php artisan make:resource Admin/V1/OrganizationResource --no-interaction

INFO  Resource [app/Http/Resources/Admin/V1/OrganizationResource.php] created successfully.


I’m editing the platform slice now: one middleware for platform-level API auth, small controllers/resources/requests, and a focused API test file. The store-bound routes reuse the existing `admin.api` middleware where possible.


Input:
*** Begin Patch
*** Update File: app/Http/Middleware/AuthenticatePlatformApi.php
@@
 namespace App\Http\Middleware;

+use App\Enums\AppInstallationStatus;
+use App\Enums\StoreUserRole;
+use App\Models\OauthToken;
+use App\Models\User;
 use Closure;
 use Illuminate\Http\Request;
 use Symfony\Component\HttpFoundation\Response;

 class AuthenticatePlatformApi
 {
@@
      */
     public function handle(Request $request, Closure $next): Response
     {
+        app()->forgetInstance('admin_api_oauth_token');
+
+        $user = $request->user();
+
+        if ($user instanceof User) {
+            if ($this->userCanManagePlatform($user)) {
+                return $next($request);
+            }
+
+            return response()->json(['message' => 'Forbidden.'], 403);
+        }
+
+        $plainTextToken = $request->bearerToken();
+
+        if (! is_string($plainTextToken) || $plainTextToken === '') {
+            return response()->json(['message' => 'Unauthenticated.'], 401);
+        }
+
+        $token = OauthToken::query()
+            ->with('installation')
+            ->where('access_token_hash', hash('sha256', $plainTextToken))
+            ->first();
+
+        if (! $token instanceof OauthToken || $token->isExpired()) {
+            return response()->json(['message' => 'Unauthenticated.'], 401);
+        }
+
+        $installation = $token->installation;
+
+        if ($installation === null || $installation->status !== AppInstallationStatus::Active) {
+            return response()->json(['message' => 'Forbidden.'], 403);
+        }
+
+        if (! $this->hasManagePlatformAbility($token)) {
+            return response()->json(['message' => 'Forbidden.'], 403);
+        }
+
+        $token->forceFill(['last_used_at' => now()])->save();
+
+        $request->attributes->set('admin_api_oauth_token', $token);
+        app()->instance('admin_api_oauth_token', $token);
+
         return $next($request);
     }
+
+    private function userCanManagePlatform(User $user): bool
+    {
+        return $user->stores()
+            ->wherePivot('role', StoreUserRole::Owner->value)
+            ->exists();
+    }
+
+    private function hasManagePlatformAbility(OauthToken $token): bool
+    {
+        $abilities = $token->abilities_json ?? [];
+
+        return in_array('*', $abilities, true) || in_array('manage-platform', $abilities, true);
+    }
 }
*** End Patch


Success. Updated the following files:
M app/Http/Middleware/AuthenticatePlatformApi.php

@@ -4,2 +4,6 @@

+use App\Enums\AppInstallationStatus;
+use App\Enums\StoreUserRole;
+use App\Models\OauthToken;
+use App\Models\User;
 use Closure;
@@ -17,4 +21,60 @@
     {
+        app()->forgetInstance('admin_api_oauth_token');
+
+        $user = $request->user();
+
+        if ($user instanceof User) {
+            if ($this->userCanManagePlatform($user)) {
+                return $next($request);
+            }
+
+            return response()->json(['message' => 'Forbidden.'], 403);
+        }
+
+        $plainTextToken = $request->bearerToken();
+
+        if (! is_string($plainTextToken) || $plainTextToken === '') {
+            return response()->json(['message' => 'Unauthenticated.'], 401);
+        }
+
+        $token = OauthToken::query()
+            ->with('installation')
+            ->where('access_token_hash', hash('sha256', $plainTextToken))
+            ->first();
+
+        if (! $token instanceof OauthToken || $token->isExpired()) {
+            return response()->json(['message' => 'Unauthenticated.'], 401);
+        }
+
+        $installation = $token->installation;
+
+        if ($installation === null || $installation->status !== AppInstallationStatus::Active) {
+            return response()->json(['message' => 'Forbidden.'], 403);
+        }
+
+        if (! $this->hasManagePlatformAbility($token)) {
+            return response()->json(['message' => 'Forbidden.'], 403);
+        }
+
+        $token->forceFill(['last_used_at' => now()])->save();
+
+        $request->attributes->set('admin_api_oauth_token', $token);
+        app()->instance('admin_api_oauth_token', $token);
+
         return $next($request);
     }
+
+    private function userCanManagePlatform(User $user): bool
+    {
+        return $user->stores()
+            ->wherePivot('role', StoreUserRole::Owner->value)
+            ->exists();
+    }
+
+    private function hasManagePlatformAbility(OauthToken $token): bool
+    {
+        $abilities = $token->abilities_json ?? [];
+
+        return in_array('*', $abilities, true) || in_array('manage-platform', $abilities, true);
+    }
 }