TenantAtlas/specs/078-operations-tenantless-canonical/contracts/routes.md

110 lines
3.7 KiB
Markdown

# Route Contracts: Operations Tenantless Canonical Migration
**Feature**: 078-operations-tenantless-canonical
**Date**: 2026-02-06
---
## Canonical Routes (Retained — No Changes)
### GET /admin/operations
| Property | Value |
|----------|-------|
| **Route name** | `admin.operations.index` |
| **Handler** | `App\Filament\Pages\Monitoring\Operations` |
| **Middleware** | `web`, `panel:admin`, `ensure-correct-guard:web`, `DenyNonMemberTenantAccess`, Filament middleware, `ensure-workspace-selected`, `ensure-filament-tenant-selected` |
| **Auth** | Requires authentication + workspace membership |
| **Scope** | Workspace-level (shows all runs in workspace) |
| **Response** | 200 HTML (Livewire page) |
### GET /admin/operations/{run}
| Property | Value |
|----------|-------|
| **Route name** | `admin.operations.view` |
| **Handler** | `App\Filament\Pages\Operations\TenantlessOperationRunViewer` |
| **Middleware** | `web`, `panel:admin`, `ensure-correct-guard:web`, `DenyNonMemberTenantAccess`, Filament middleware |
| **Auth** | Requires authentication + workspace membership for `$run->workspace_id` |
| **Model binding** | `{run}` resolves to `OperationRun` by ID |
| **Non-member** | 404 (deny-as-not-found) |
| **Not found** | 404 (Laravel model binding) |
| **Response** | 200 HTML (Livewire page with infolist) |
---
## Decommissioned Routes (Resource-Generated Routes Removed After Migration)
### GET /admin/t/{tenant}/operations/r/{record}
| Property | Value |
|----------|-------|
| **Route name** | `filament.admin.resources.operations.view` |
| **Status** | ❌ **REMOVED** — route no longer registered |
| **After migration** | Natural 404 |
| **Previously** | `ViewOperationRun` (Filament ViewRecord page) |
### GET /admin/t/{tenant}/operations
| Property | Value |
|----------|-------|
| **Route name** | `filament.admin.resources.operations.index` |
| **Status** | ❌ **REMOVED** — route no longer registered |
| **After migration** | Replaced by explicit convenience route `admin.operations.legacy-index` that redirects 302 → `/admin/operations` |
| **Previously** | `ListOperationRuns` (Filament ListRecords page) |
---
## Link Generation Contracts
### OperationRunLinks::view($run, $tenant)
| Property | Value |
|----------|-------|
| **Returns** | `route('admin.operations.view', ['run' => $run])` |
| **Delegates to** | `OperationRunLinks::tenantlessView($run)` |
| **Tenant parameter** | Ignored (no-op) |
| **Change** | None — already canonical |
### OperationRunLinks::tenantlessView($run)
| Property | Value |
|----------|-------|
| **Returns** | `route('admin.operations.view', ['run' => $run])` |
| **Change** | None |
### OperationRunLinks::index()
| Property | Value |
|----------|-------|
| **Returns** | `route('admin.operations.index')` |
| **Change** | None |
### OperationRunLinks::related($run, $tenant)
| Property | Value |
|----------|-------|
| **Returns** | Array of up to 11 contextual link arrays |
| **Change** | None — consumed by `TenantlessOperationRunViewer` header actions |
---
## Test Route Assertions
### Positive (must work)
| Test | Route | Expected |
|------|-------|----------|
| T-078-001 | `GET /admin/operations/{run}` | 200 (member) |
| T-078-001 | `GET /admin/operations/{run}` | 200 (run with `tenant_id = null`) |
| T-078-009 | `GET /admin/t/{tenant}/operations` | 302 redirect to `/admin/operations` |
### Negative (must 404)
| Test | Route | Expected |
|------|-------|----------|
| T-078-001 | `GET /admin/operations/{run}` | 404 (non-member) |
| T-078-002 | `GET /admin/t/{tenant}/operations/r/{record}` | 404 (any user) |
| T-078-004 | Route name `filament.admin.resources.operations.view` | Not registered |
| T-078-004 | Route name `filament.admin.resources.operations.index` | Not registered |