Implements Spec 078 operations tenantless canonical migration.
Highlights:
- Canonical run detail at `/admin/operations/{run}` renders with standard Filament chrome + sidebar and reuses `OperationRunResource::infolist()` (schema-based, Filament v5).
- Legacy tenant-scoped resource pages removed; legacy URLs return 404 as required.
- Added full spec test pack under `tests/Feature/078/` and updated existing tests.
- Added safe refresh/header actions wiring and KPI header guard when tenant context is null.
Validation:
- `vendor/bin/sail artisan test --compact tests/Feature/078/` (pass)
- `vendor/bin/sail bin pint --dirty` (pass)
Notes:
- Livewire v4+ compliant (Filament v5).
- Panel providers remain registered in `bootstrap/providers.php` (Laravel 11+ standard).
Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #95
95 lines
4.5 KiB
Markdown
95 lines
4.5 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` (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 |
|