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
4.5 KiB
4.5 KiB
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:
- User must be a member of
$run->workspace_id - 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 |