# Data Model: Operations Tenantless Canonical Migration **Feature**: 078-operations-tenantless-canonical **Date**: 2026-02-06 --- ## Overview This feature does **not** introduce new models or migrations. It reorganizes how existing models are rendered and routed. ## Entities (Existing — No Changes) ### OperationRun | Field | Type | Notes | |-------|------|-------| | `id` | `bigint` (PK) | Auto-increment | | `workspace_id` | `bigint` (FK) | Required. Authorization boundary. | | `tenant_id` | `bigint` (FK, nullable) | Null for workspace-level runs (e.g., onboarding). | | `type` | `string` | Operation type slug (e.g., `policy.sync`, `restore.execute`). | | `status` | `enum` | `OperationRunStatus`: Queued, Running, Completed, Cancelled. | | `outcome` | `enum` (nullable) | `OperationRunOutcome`: Succeeded, PartiallySucceeded, Failed. | | `context` | `jsonb` | Contains `target_scope`, `verification_report`, etc. | | `summary_counts` | `jsonb` | `{total, processed, succeeded, failed, skipped}` | | `failure_summary` | `jsonb` (nullable) | Sanitized failure details. | | `initiated_by` | `bigint` (FK, nullable) | User who started the run. | | `started_at` | `timestamp` (nullable) | | | `completed_at` | `timestamp` (nullable) | | | `created_at` | `timestamp` | | | `updated_at` | `timestamp` | | **Relationships**: `belongsTo Workspace`, `belongsTo Tenant` (nullable), `belongsTo User` (initiated_by) ### WorkspaceMembership (Authorization Boundary) The `OperationRunPolicy::view()` checks: 1. User must be a member of `$run->workspace_id` 2. Returns `Response::denyAsNotFound()` if not a member No changes to this model or policy logic. ## Routing Changes (No Model Impact) ### Routes Removed | Route Name | Pattern | Handler | |------------|---------|---------| | `filament.admin.resources.operations.index` | `GET /admin/t/{tenant}/operations` | `ListOperationRuns` (resource-generated) | | `filament.admin.resources.operations.view` | `GET /admin/t/{tenant}/operations/r/{record}` | `ViewOperationRun` | ### Route Added | Route Name | Pattern | Handler | |------------|---------|---------| | `admin.operations.legacy-index` | `GET /admin/t/{tenant}/operations` | Redirect 302 to `/admin/operations` | ### Routes Retained (Unchanged) | Route Name | Pattern | Handler | |------------|---------|---------| | `admin.operations.index` | `GET /admin/operations` | `Operations.php` | | `admin.operations.view` | `GET /admin/operations/{run}` | `TenantlessOperationRunViewer` | ### Files Deleted | File | Reason | |------|--------| | `app/Filament/Resources/OperationRunResource/Pages/ViewOperationRun.php` | Replaced by TenantlessOperationRunViewer | | `app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php` | Replaced by Operations.php | | `app/Livewire/Monitoring/OperationsDetail.php` | Dead code | | `resources/views/livewire/monitoring/operations-detail.blade.php` | Dead code (blade for OperationsDetail) | ### Files Modified | File | Change | |------|--------| | `app/Filament/Resources/OperationRunResource.php` | `getPages()` returns `[]` | | `app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` | Reuse infolist via schema, add related links | | `resources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.php` | Replace hand-coded HTML with `{{ $this->infolist }}` | | `app/Filament/Widgets/Operations/OperationsKpiHeader.php` | Hide stats when no tenant context | ### Test Files Requiring Updates | File | Change Required | |------|----------------| | `tests/Feature/Verification/VerificationAuthorizationTest.php` | Replace `OperationRunResource::getUrl('view')` with `route('admin.operations.view')` | | `tests/Feature/OpsUx/FailureSanitizationTest.php` | Replace `OperationRunResource::getUrl('view')` with canonical route; replace `ViewOperationRun` mount | | `tests/Feature/OpsUx/CanonicalViewRunLinksTest.php` | Update guard regex to account for headless resource | | `tests/Feature/Verification/VerificationReportViewerDbOnlyTest.php` | Replace `ViewOperationRun` mount with `TenantlessOperationRunViewer` | | `tests/Feature/Verification/VerificationReportRedactionTest.php` | Replace `ViewOperationRun` mount with `TenantlessOperationRunViewer` | | `tests/Feature/Verification/VerificationReportMissingOrMalformedTest.php` | Replace `ViewOperationRun` mount with `TenantlessOperationRunViewer` | | `tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php` | Remove `ListOperationRuns` test; add route-not-registered assertion | | `tests/Feature/Monitoring/OperationsTenantScopeTest.php` | Remove `ListOperationRuns` test |