- plan.md: 5 implementation phases (A-E), constitution check, risk assessment, test strategy - research.md: 5 findings (R-001 through R-005) on Filament v5 schema reuse - data-model.md: entity documentation, routing changes, file deletion/modification map - contracts/routes.md: canonical vs decommissioned route contracts - quickstart.md: verification steps for implementors - Updated copilot agent context with plan technologies
88 lines
4.1 KiB
Markdown
88 lines
4.1 KiB
Markdown
# 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` |
|
|
| `filament.admin.resources.operations.view` | `GET /admin/t/{tenant}/operations/r/{record}` | `ViewOperationRun` |
|
|
|
|
### 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/VerificationDbOnlyTest.php` | Replace `ViewOperationRun` mount with `TenantlessOperationRunViewer` |
|
|
| `tests/Feature/Verification/VerificationReportRenderingTest.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 |
|