# Phase 1 — Spatie Permission Foundation and Migration Bridge

## Goal

Install Spatie Laravel Permission and create its storage contract without changing the application's current authorization decisions.

Phase 1 is intentionally a **storage bridge**, not a cutover.

## Runtime authority during Phase 1

The existing application RBAC remains authoritative:

- `App\Models\User::roles()` still uses `role_user`.
- `User::hasRole()` remains unchanged.
- `User::hasPermission()` remains unchanged.
- the existing `permission` route middleware remains unchanged.
- branch isolation remains fully independent.

Spatie's Gate permission registration is deliberately disabled with:

```php
'permission.register_permission_check_method' => false
```

`Spatie\Permission\Traits\HasRoles` is **not** added to `User` in Phase 1 because the current `roles()` relation would conflict with the trait. That replacement is a Phase 2 cutover concern.

## Dependency baseline

- Laravel: 12.x
- PHP project floor: 8.3+
- Spatie Laravel Permission: `^8.0`
- guard: `web`
- Teams: disabled
- wildcard permissions: disabled
- direct model permissions: prohibited during the bridge

## Schema bridge

The migration keeps the existing `roles` and `permissions` records and IDs intact, then:

1. adds `guard_name = web` to `roles` and `permissions`;
2. adds the Spatie-compatible composite unique indexes;
3. creates `role_has_permissions`;
4. creates `model_has_roles`;
5. creates `model_has_permissions`;
6. copies `permission_role` into `role_has_permissions`;
7. copies `role_user` into `model_has_roles` using `App\Models\User` as `model_type`.

The legacy `permission_role` and `role_user` tables are intentionally retained.

## Fresh database behavior

On a fresh install, migrations run before seeders, so the bridge migration initially has no seeded assignments to copy. `SpatiePermissionBridgeSeeder` therefore runs after all role/user seeders and synchronizes the bridge to exact parity.

## Verification and repair command

Check parity without modifying data:

```bash
php artisan authorization:spatie-bridge --json
```

Synchronize bridge assignments from the legacy authoritative pivots, then verify:

```bash
php artisan authorization:spatie-bridge --sync --json
```

A successful report requires:

- exact `permission_role` ↔ `role_has_permissions` parity;
- exact `role_user` ↔ `model_has_roles` parity for `App\Models\User`;
- every role and permission using the `web` guard;
- zero direct `model_has_permissions` rows;
- zero unexpected role model types.

## Deployment order

For Phase 1 deployment:

```bash
composer install --no-dev --classmap-authoritative
php artisan optimize:clear
php artisan migrate --force
php artisan authorization:spatie-bridge --sync --json
php artisan authorization:inventory --json --strict
```

Do not enable Spatie Gate registration or add `HasRoles` as part of a Phase 1 deployment.

## CI protections

Foundation CI validates the bridge on both SQLite and MySQL after a fresh migrate/seed. The feature suite also proves that legacy role changes produce detectable bridge drift and that `--sync` restores exact parity.

## Phase 1 completion criteria

Phase 1 is complete when:

- Composer installs the locked Spatie dependency on PHP 8.3;
- SQLite and MySQL migrations succeed;
- legacy role and permission IDs are preserved;
- bridge tables have exact seeded parity;
- current authorization helper behavior is unchanged;
- Spatie Gate registration remains disabled;
- `User` does not yet use `HasRoles`;
- branch-isolation regressions remain green;
- production/release CI remains green.

The next phase is **Phase 2 — Model and Gate Integration**, where custom models will extend Spatie models and the application will introduce `HasRoles` and the planned Gate integration behind equivalence tests.
