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
17 KiB
Tasks: Operations Tenantless Canonical Migration
Input: Design documents from /specs/078-operations-tenantless-canonical/
Prerequisites: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
Tests: Tests are REQUIRED (Pest). This feature changes runtime routing and rendering behavior.
Operations: No new operations introduced. Existing OperationRun model is read-only in this feature.
RBAC: Authorization unchanged — workspace membership enforced via OperationRunPolicy::view(). Non-member gets 404 (deny-as-not-found). No new capabilities, no destructive actions.
Badges: No badge changes. Existing BadgeRenderer reused via shared infolist schema.
Organization: Tasks are grouped by user story. US1 (P1) is the MVP. US2/US3 (P2) can proceed in parallel after foundational phase. US4 (P3) is independent.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (US1, US2, US3, US4)
- Exact file paths included in descriptions
Path Conventions
- Standard Laravel monolith:
app/,resources/,routes/,tests/,config/ - New spec tests:
tests/Feature/078/
Phase 1: Setup
Purpose: Create test directory and verify branch readiness
- T001 Create spec test directory
tests/Feature/078/ - T002 Verify branch is clean and on
078-operations-tenantless-canonical
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Make OperationRunResource headless, delete decommissioned pages, delete dead code, and fix all existing tests that reference deleted classes/routes. This phase MUST complete before any user story work.
Why blocking: US1 needs ViewOperationRun deleted so infolist lives on standalone page. US2 needs routes removed. US3 needs the viewer to be the sole detail surface. All existing tests must pass after this phase.
- T003 Change
getPages()to return[]inapp/Filament/Resources/OperationRunResource.php - T004 [P] Delete
app/Filament/Resources/OperationRunResource/Pages/ViewOperationRun.php - T005 [P] Delete
app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php - T006 [P] Delete
app/Livewire/Monitoring/OperationsDetail.php(dead code) - T007 [P] Delete
resources/views/livewire/monitoring/operations-detail.blade.php(dead code) - T008 Update
tests/Feature/Verification/VerificationAuthorizationTest.php— replaceOperationRunResource::getUrl('view', ...)withroute('admin.operations.view', ...)and replaceViewOperationRunLivewire mount withTenantlessOperationRunViewer - T009 [P] Update
tests/Feature/OpsUx/FailureSanitizationTest.php— replaceOperationRunResource::getUrl('view', ...)with canonical route; replaceViewOperationRunmount withTenantlessOperationRunViewer - T010 [P] Update
tests/Feature/OpsUx/CanonicalViewRunLinksTest.php— update guard regex to account for headless resource (nogetUrlcalls exist) - T011 [P] Update
tests/Feature/Verification/VerificationReportViewerDbOnlyTest.php— replaceViewOperationRunmount withTenantlessOperationRunViewer - T012 [P] Update
tests/Feature/Verification/VerificationReportRedactionTest.php+tests/Feature/Verification/VerificationReportMissingOrMalformedTest.php— replaceViewOperationRunmounts withTenantlessOperationRunViewer - T013 [P] Update
tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php— removeListOperationRunstest block; add route-not-registered assertion forfilament.admin.resources.operations.index - T014 [P] Update
tests/Feature/Monitoring/OperationsTenantScopeTest.php— removeListOperationRunsreference - T015 Run
grep -r "ViewOperationRun\|ListOperationRuns" app/ tests/ resources/to verify no stale references remain - T016 Run existing test suite for affected files:
vendor/bin/sail artisan test --compact tests/Feature/Verification/ tests/Feature/OpsUx/ tests/Feature/Monitoring/ tests/Feature/Operations/
Checkpoint: All existing tests pass. Resource is headless. Dead code removed. No stale references.
Phase 3: User Story 1 — View operation run via canonical URL (Priority: P1) MVP
Goal: The canonical detail page at /admin/operations/{run} renders with full Filament infolist (summary, target scope, verification report, counts, context JSON) — replacing the current hand-coded Blade with OperationRunResource::infolist() reuse via the unified schema system.
Independent Test: Create runs with and without tenant_id, visit /admin/operations/{run}, assert all infolist sections render.
Tests for User Story 1
- T017 [P] [US1] Write test T-078-001 (canonical detail renders with tenant_id) in
tests/Feature/078/CanonicalDetailRenderTest.php— create OperationRun with tenant_id, visit canonical URL as workspace member, assert 200 + infolist sections visible (status badge, outcome, timestamps, target scope, summary counts) - T018 [P] [US1] Write test T-078-001 (canonical detail renders without tenant_id) in
tests/Feature/078/CanonicalDetailRenderTest.php— create OperationRun with tenant_id=null, visit canonical URL, assert 200 + graceful rendering ("No target scope details") - T019 [P] [US1] Write test T-078-001 (non-member gets 404) in
tests/Feature/078/CanonicalDetailRenderTest.php— visit as non-member, assert 404 - T020 [P] [US1] Write test T-078-008 (verification report renders tenantless) in
tests/Feature/078/VerificationReportTenantlessTest.php— create run with verification_report in context, visit canonical URL, assert verification section renders with badges and acknowledgements - T021 [P] [US1] Write test T-078-007 (DB-only rendering) in
tests/Feature/078/CanonicalDetailRenderTest.php— assert canonical detail rendering does not dispatch jobs or HTTP calls
Implementation for User Story 1
- T022 [US1] Add
public function infolist(Schema $schema): Schematoapp/Filament/Pages/Operations/TenantlessOperationRunViewer.php— delegates toOperationRunResource::infolist($schema) - T023 [US1] Add
public function defaultInfolist(Schema $schema): Schematoapp/Filament/Pages/Operations/TenantlessOperationRunViewer.php— sets->record($this->run)->columns(2) - T024 [US1] Add
public bool $opsUxIsTabHidden = falseproperty toapp/Filament/Pages/Operations/TenantlessOperationRunViewer.php(required for polling callback in infolist) - T025 [US1] Add
public function content(Schema $schema): Schematoapp/Filament/Pages/Operations/TenantlessOperationRunViewer.phpreturningEmbeddedSchema::make('infolist') - T026 [US1] Replace hand-coded HTML in
resources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.phpwith infolist render ({{ $this->infolist }}) - T027 [US1] Run tests:
vendor/bin/sail artisan test --compact tests/Feature/078/CanonicalDetailRenderTest.php tests/Feature/078/VerificationReportTenantlessTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php
Checkpoint: Canonical detail at /admin/operations/{run} renders full Filament infolist. Runs with and without tenant_id render correctly. MVP is functional.
Phase 4: User Story 2 — Legacy tenant-scoped detail URLs return 404 (Priority: P2)
Goal: Verify that decommissioned tenant-scoped routes naturally return 404. No redirect handlers, no existence leakage.
Independent Test: Hit legacy detail URLs; assert 404 for all users.
Note: The actual route removal was done in Phase 2 (Foundational). This phase adds the spec-required tests that validate the behavior.
Tests for User Story 2
- T028 [P] [US2] Write test T-078-002 (legacy detail URL returns 404) in
tests/Feature/078/LegacyRoutesReturnNotFoundTest.php— visit/admin/t/{tenant}/operations/r/{record}as any user, assert 404 - T029 [P] [US2] Write test T-078-002 (slug variant returns 404) in
tests/Feature/078/LegacyRoutesReturnNotFoundTest.php— visit/admin/operations/r/{record}, assert 404 - T030 [P] [US2] Write test T-078-004 (auto-generated route names not registered) in
tests/Feature/078/LegacyRoutesReturnNotFoundTest.php— assertRoute::has('filament.admin.resources.operations.view')is false andRoute::has('filament.admin.resources.operations.index')is false
Implementation for User Story 2
No additional implementation needed — routes were removed in Phase 2.
- T031 [US2] Run tests:
vendor/bin/sail artisan test --compact tests/Feature/078/LegacyRoutesReturnNotFoundTest.php
Checkpoint: All legacy URLs return 404. Route names are not registered. No existence leakage.
Phase 5: User Story 3 — Contextual navigation from run detail (Priority: P2)
Goal: Replace the "Admin details" button on canonical detail with OperationRunLinks::related() action group in header actions — providing richer contextual navigation (Operations index, Policy, Backup Set, Restore Run, etc.).
Independent Test: Create runs of different types, verify related links appear in header actions and "Admin details" link is absent.
Tests for User Story 3
- T032 [P] [US3] Write test T-078-010 (related links appear) in
tests/Feature/078/RelatedLinksOnDetailTest.php— create run of typerestore.executewithrestore_run_idin context, visit canonical detail, assert header actions include "Restore Run" link - T033 [P] [US3] Write test T-078-010 (generic links for tenantless run) in
tests/Feature/078/RelatedLinksOnDetailTest.php— create run with tenant_id=null, assert only generic links (Operations index) appear - T034 [P] [US3] Write tests T-078-005 + T-078-012 in
tests/Feature/078/RelatedLinksOnDetailTest.php— assert canonical detail does not render/admin/t/.../operations/r/...links and uses exact CTA label "View run" (legacy "Admin details" absent)
Implementation for User Story 3
- T035 [US3] Add
getHeaderActions()method toapp/Filament/Pages/Operations/TenantlessOperationRunViewer.php— return actions fromOperationRunLinks::related($this->run, $this->run->tenant) - T036 [US3] Remove "Admin details" button code (~line 61) from
app/Filament/Pages/Operations/TenantlessOperationRunViewer.php - T037 [US3] Run tests:
vendor/bin/sail artisan test --compact tests/Feature/078/RelatedLinksOnDetailTest.php
Checkpoint: Header shows contextual "Open" action group. "Admin details" link is gone. Related links vary by run type.
Phase 6: User Story 4 — Operations list remains workspace-scoped (Priority: P3)
Goal: Ensure no regression on /admin/operations list. KPI header hidden in tenantless mode. 302 redirect for decommissioned list URL.
Independent Test: Visit /admin/operations with and without tenant context; verify workspace scoping, KPI behavior, and redirect.
Tests for User Story 4
- T038 [P] [US4] Write test T-078-006 (KPI header hidden without tenant) in
tests/Feature/078/KpiHeaderTenantlessTest.php— visit/admin/operationswithout tenant context, assert KPI stats are not rendered (empty array fromgetStats()) - T039 [P] [US4] Write test T-078-009 (tenant-scoped list redirect) in
tests/Feature/078/TenantListRedirectTest.php— visit/admin/t/{tenant}/operations, assert 302 redirect to/admin/operations - T048 [P] [US4] Write test T-078-011 (tenantless list query safety) in
tests/Feature/078/OperationsListTenantlessSafetyTest.php— visit/admin/operationswith and without tenant context, assert runs includingtenant_id = nullrender without errors
Implementation for User Story 4
- T040 [US4] Add tenant-null guard in
getStats()ofapp/Filament/Widgets/Operations/OperationsKpiHeader.php— ifFilament::getTenant()is null, return[] - T041 [US4] Add 302 redirect route in
routes/web.php—/admin/t/{tenant}/operationsredirects to/admin/operations(FR-078-012) - T042 [US4] Run tests:
vendor/bin/sail artisan test --compact tests/Feature/078/KpiHeaderTenantlessTest.php tests/Feature/078/TenantListRedirectTest.php tests/Feature/078/OperationsListTenantlessSafetyTest.php
Checkpoint: Operations list works in workspace mode. KPI hidden without tenant. List redirect works.
Phase 7: Polish & Cross-Cutting Concerns
Purpose: Final validation, formatting, and stale reference sweep
- T043 Run
grep -r "ViewOperationRun\|ListOperationRuns\|OperationsDetail" app/ tests/ resources/— verify zero stale references across entire codebase - T044 Run
vendor/bin/sail bin pint --dirty— fix any formatting issues - T049 Remove obsolete temporary layout
resources/views/filament/layouts/topbar-only.blade.php - T045 Run focused test pack:
vendor/bin/sail artisan test --compact tests/Feature/078/ tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php tests/Feature/Monitoring/OperationsTenantScopeTest.php tests/Feature/Verification/VerificationAuthorizationTest.php tests/Feature/Verification/VerificationReportViewerDbOnlyTest.php tests/Feature/Verification/VerificationReportRedactionTest.php tests/Feature/Verification/VerificationReportMissingOrMalformedTest.php tests/Feature/OpsUx/FailureSanitizationTest.php tests/Feature/OpsUx/CanonicalViewRunLinksTest.php - T046 Run quickstart.md validation:
vendor/bin/sail artisan route:list --name=filament.admin.resources.operations— assert empty output - T047 Ask user if they want to run the full test suite:
vendor/bin/sail artisan test --compact
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies — start immediately
- Foundational (Phase 2): Depends on Phase 1 — BLOCKS all user stories
- US1 (Phase 3): Depends on Phase 2 completion (resource must be headless first)
- US2 (Phase 4): Depends on Phase 2 completion (routes must be removed)
- US3 (Phase 5): Depends on Phase 3 completion (viewer must have infolist before adding header actions)
- US4 (Phase 6): Depends on Phase 2 completion only (independent of US1/US2/US3)
- Polish (Phase 7): Depends on all desired user stories being complete
User Story Dependencies
- US1 (P1): After Phase 2 — no other story dependencies. This is the MVP.
- US2 (P2): After Phase 2 — independent of US1 (tests only, implementation in Phase 2)
- US3 (P2): After US1 (Phase 3) — needs infolist on viewer before adding header actions
- US4 (P3): After Phase 2 — independent of US1/US2/US3
Within Each User Story
- Tests written FIRST, verified to FAIL before implementation
- Implementation follows test guidance
- Story checkpoint validates independently
Parallel Opportunities
After Phase 2 completes:
- US1 and US2 and US4 can proceed in parallel (different files, no dependencies)
- US3 must wait for US1 (same file: TenantlessOperationRunViewer.php)
Within Phase 2:
- T004, T005, T006, T007 (file deletions) can all run in parallel
- T008-T014 (test file updates) can all run in parallel
Within Phase 3 (US1):
- T017-T021 (test writing) can all run in parallel
- T022-T026 (implementation) are sequential (same file)
Parallel Example: After Phase 2
# US1 (developer/agent A):
T017-T021: Write US1 tests (parallel)
T022-T026: Implement infolist reuse (sequential, same file)
T027: Run US1 tests
# US2 (developer/agent B — can run simultaneously):
T028-T030: Write US2 tests (parallel)
T031: Run US2 tests
# US4 (developer/agent C — can run simultaneously):
T038-T039: Write US4 tests (parallel)
T040-T041: Implement KPI guard + redirect
T042: Run US4 tests
# US3 (must wait for US1):
T032-T034: Write US3 tests (parallel)
T035-T036: Implement related links
T037: Run US3 tests
Implementation Strategy
MVP First (User Story 1 Only)
- Complete Phase 1: Setup
- Complete Phase 2: Foundational (headless resource + dead code + test fixes)
- Complete Phase 3: User Story 1 (infolist reuse on canonical detail)
- STOP and VALIDATE: Canonical detail renders full infolist for all run types
- This is deployable as MVP — core value delivered
Incremental Delivery
- Setup + Foundational -> Foundation ready (all existing tests pass)
- Add US1 -> Canonical detail has full infolist (MVP!)
- Add US2 -> Legacy URLs confirmed 404 (validation tests)
- Add US3 -> Related links replace "Admin details" button
- Add US4 -> KPI hidden + list redirect
- Polish -> Full sweep, formatting, final validation
Notes
- All tests use Pest (not PHPUnit class syntax)
- Use existing
OperationRunfactory for test data setup TenantlessOperationRunVieweris the sole detail surface after migrationOperationRunResourceretained as headless utility (provides::table()and::infolist())- No new migrations, no new models, no new dependencies
- Total: 47 tasks across 7 phases