TenantAtlas/specs/078-operations-tenantless-canonical/tasks.md
ahmido d56ba85755 Spec 078: Operations tenantless canonical detail (#95)
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
2026-02-07 09:07:26 +00:00

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 [] in app/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 — replace OperationRunResource::getUrl('view', ...) with route('admin.operations.view', ...) and replace ViewOperationRun Livewire mount with TenantlessOperationRunViewer
  • T009 [P] Update tests/Feature/OpsUx/FailureSanitizationTest.php — replace OperationRunResource::getUrl('view', ...) with canonical route; replace ViewOperationRun mount with TenantlessOperationRunViewer
  • T010 [P] Update tests/Feature/OpsUx/CanonicalViewRunLinksTest.php — update guard regex to account for headless resource (no getUrl calls exist)
  • T011 [P] Update tests/Feature/Verification/VerificationReportViewerDbOnlyTest.php — replace ViewOperationRun mount with TenantlessOperationRunViewer
  • T012 [P] Update tests/Feature/Verification/VerificationReportRedactionTest.php + tests/Feature/Verification/VerificationReportMissingOrMalformedTest.php — replace ViewOperationRun mounts with TenantlessOperationRunViewer
  • T013 [P] Update tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php — remove ListOperationRuns test block; add route-not-registered assertion for filament.admin.resources.operations.index
  • T014 [P] Update tests/Feature/Monitoring/OperationsTenantScopeTest.php — remove ListOperationRuns reference
  • 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): Schema to app/Filament/Pages/Operations/TenantlessOperationRunViewer.php — delegates to OperationRunResource::infolist($schema)
  • T023 [US1] Add public function defaultInfolist(Schema $schema): Schema to app/Filament/Pages/Operations/TenantlessOperationRunViewer.php — sets ->record($this->run)->columns(2)
  • T024 [US1] Add public bool $opsUxIsTabHidden = false property to app/Filament/Pages/Operations/TenantlessOperationRunViewer.php (required for polling callback in infolist)
  • T025 [US1] Add public function content(Schema $schema): Schema to app/Filament/Pages/Operations/TenantlessOperationRunViewer.php returning EmbeddedSchema::make('infolist')
  • T026 [US1] Replace hand-coded HTML in resources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.php with 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 — assert Route::has('filament.admin.resources.operations.view') is false and Route::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 type restore.execute with restore_run_id in 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 to app/Filament/Pages/Operations/TenantlessOperationRunViewer.php — return actions from OperationRunLinks::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/operations without tenant context, assert KPI stats are not rendered (empty array from getStats())
  • 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/operations with and without tenant context, assert runs including tenant_id = null render without errors

Implementation for User Story 4

  • T040 [US4] Add tenant-null guard in getStats() of app/Filament/Widgets/Operations/OperationsKpiHeader.php — if Filament::getTenant() is null, return []
  • T041 [US4] Add 302 redirect route in routes/web.php/admin/t/{tenant}/operations redirects 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)

  1. Complete Phase 1: Setup
  2. Complete Phase 2: Foundational (headless resource + dead code + test fixes)
  3. Complete Phase 3: User Story 1 (infolist reuse on canonical detail)
  4. STOP and VALIDATE: Canonical detail renders full infolist for all run types
  5. This is deployable as MVP — core value delivered

Incremental Delivery

  1. Setup + Foundational -> Foundation ready (all existing tests pass)
  2. Add US1 -> Canonical detail has full infolist (MVP!)
  3. Add US2 -> Legacy URLs confirmed 404 (validation tests)
  4. Add US3 -> Related links replace "Admin details" button
  5. Add US4 -> KPI hidden + list redirect
  6. Polish -> Full sweep, formatting, final validation

Notes

  • All tests use Pest (not PHPUnit class syntax)
  • Use existing OperationRun factory for test data setup
  • TenantlessOperationRunViewer is the sole detail surface after migration
  • OperationRunResource retained as headless utility (provides ::table() and ::infolist())
  • No new migrations, no new models, no new dependencies
  • Total: 47 tasks across 7 phases