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