# Implementation Plan: Canonical Operation Viewer Context Decoupling **Branch**: `144-canonical-operation-viewer-context-decoupling` | **Date**: 2026-03-15 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from `/specs/144-canonical-operation-viewer-context-decoupling/spec.md` ## Summary Harden the canonical operation run viewer so `/admin/operations/{run}` resolves legitimacy from the run, workspace, and direct tenant entitlement instead of remembered tenant context. The implementation keeps `OperationRunPolicy` as the authorization boundary, keeps `OperationRunLinks::tenantlessView()` as the canonical deep-link contract, and adds explicit non-blocking context messaging plus regression coverage for mismatched tenant context, onboarding or non-selectable tenants, tenantless runs, and 404 versus 403 semantics. Key approach: preserve the existing canonical route family and viewer architecture, treat `OperateHubShell::activeEntitledTenant()` as a display and navigation input only, surface mismatch and lifecycle context in the viewer wrapper rather than in policy code, and deepen tests around the existing seams instead of introducing a new abstraction. ## Technical Context **Language/Version**: PHP 8.4 (Laravel 12) **Primary Dependencies**: Filament v5, Livewire v4, Laravel Gates and Policies, `OperateHubShell`, `OperationRunLinks` **Storage**: PostgreSQL plus session-backed workspace and remembered tenant context (no schema changes) **Testing**: Pest v4 feature tests and Livewire page tests **Target Platform**: Web admin panel running in Laravel Sail / Docker **Project Type**: Laravel monolith web application **Performance Goals**: Keep operations index and canonical run viewer DB-only at render and poll time, with no external HTTP or queue side effects during page load **Constraints**: No new tables, no ownership changes for `OperationRun`, no implicit tenant-context mutation when viewing a run, no cross-tenant leakage, and no reintroduction of selected-tenant-as-validity-gate logic **Scale/Scope**: Focused hardening across the canonical viewer, context-resolution helpers, and approximately 6 to 10 feature tests plus one new spec-scoped regression pack ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle | Status | Notes | |-----------|--------|-------| | Inventory-first | N/A | No inventory, snapshot, or backup semantics change | | Read/write separation | Pass | Feature is read-only hardening of routing, authorization, and messaging | | Graph contract path | N/A | No Graph calls or contract changes | | Deterministic capabilities | Pass | Existing `OperationRunCapabilityResolver` remains the canonical capability source | | RBAC-UX planes | Pass | Work stays in `/admin` canonical workspace routes; cross-plane behavior unchanged | | Workspace isolation | Pass | `OperationRunPolicy` already denies non-members as not found and remains the primary gate | | Tenant isolation | Pass | Tenant-linked runs still require direct tenant entitlement; remembered context does not bypass this | | RBAC 404 vs 403 semantics | Pass | Non-member or non-entitled remains 404; member missing resolved capability remains 403 | | Destructive confirmation | Pass | No new destructive actions; existing `Resume capture` confirmation remains unchanged | | Global search safety | Pass | No global-search expansion; canonical run access remains direct-route and deep-link focused | | Run observability | Pass | Existing `OperationRun` usage remains unchanged; monitoring pages stay DB-only | | Ops-UX 3-surface feedback | Pass | No new operation start or completion behavior is introduced | | Ops-UX lifecycle ownership | Pass | No status or outcome transition logic is modified | | Ops-UX summary counts | Pass | No `summary_counts` producer or consumer contract change | | Ops-UX guards | Pass | Existing guards stay applicable; this feature adds visibility and authorization regressions | | Data minimization | Pass | No new stored payloads or exposed secrets | | Badge semantics | Pass | Any lifecycle or status presentation remains centralized; no ad hoc badge mapping required | | UI naming | Pass | Vocabulary remains `View run`, `Back to Operations`, and explicit tenant-context messaging | | Filament Action Surface Contract | Pass | Existing action surfaces remain intact; only informational messaging and route legitimacy are hardened | | Filament UX-001 | Pass | Viewer remains an infolist-based detail page and operations remains a table page; no layout regressions required | **Post-design re-check**: Pass. Phase 1 artifacts preserve the same constraints: no schema changes, no new auth plane, no new mutation surface, and no bypass of the existing policy and capability resolver. ## Project Structure ### Documentation (this feature) ```text specs/144-canonical-operation-viewer-context-decoupling/ ├── spec.md ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── routes.md ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ └── Pages/ │ └── Operations/ │ └── TenantlessOperationRunViewer.php # Canonical viewer mount, header actions, derived banner state ├── Policies/ │ └── OperationRunPolicy.php # Direct workspace and tenant entitlement gate ├── Support/ │ ├── OperateHub/ │ │ └── OperateHubShell.php # Active entitled tenant resolution for display affordances only │ ├── Middleware/ │ │ └── EnsureFilamentTenantSelected.php # Canonical route exemption and Livewire update safety │ ├── Operations/ │ │ └── OperationRunCapabilityResolver.php # Capability lookup for 403 semantics on view │ ├── Tenants/ │ │ └── TenantPageCategory.php # Canonical route page-category classification │ └── OperationRunLinks.php # Canonical deep-link and related-link contract resources/ └── views/ └── filament/pages/operations/ └── tenantless-operation-run-viewer.blade.php # Non-blocking context/lifecycle banner wrapper tests/ ├── Feature/ │ ├── Operations/ │ │ └── TenantlessOperationRunViewerTest.php │ ├── Monitoring/ │ │ ├── OperationsTenantScopeTest.php │ │ ├── OperationsCanonicalUrlsTest.php │ │ ├── HeaderContextBarTest.php │ │ └── OperationRunResolvedReferencePresentationTest.php │ ├── OpsUx/ │ │ ├── OperateHubShellTest.php │ │ ├── CanonicalViewRunLinksTest.php │ │ └── NonLeakageWorkspaceOperationsTest.php │ ├── Filament/ │ │ └── OperationRunEnterpriseDetailPageTest.php │ ├── Verification/ │ │ └── VerificationAuthorizationTest.php │ ├── RunAuthorizationTenantIsolationTest.php │ └── 144/ │ ├── CanonicalOperationViewerContextMismatchTest.php │ └── CanonicalOperationViewerDeepLinkTrustTest.php └── Unit/ └── Support/ └── CanonicalNavigationContextTest.php # Existing canonical-link helper coverage, likely unchanged ``` **Structure Decision**: Standard Laravel monolith. The implementation is centered on one existing Filament page, one policy, one shell helper, one middleware path exemption, and focused Pest regressions. New spec-specific tests should live in `tests/Feature/144/` while existing coverage is extended where behavior is already anchored. ## Complexity Tracking > No constitution violations require justification. | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | None | N/A | N/A | --- ## Implementation Phases ### Phase A — Lock The Canonical Viewer Legitimacy Boundary **Goal**: Ensure the viewer remains authorized from the run, workspace, and direct tenant entitlement only, with no hidden selected-tenant validity gate. **Risk**: Medium — the current code mostly follows this rule already, so changes must not accidentally weaken legitimate tenant entitlement checks. **Tests first**: Existing authorization tests plus a new mismatch-success regression. | Step | File | Change | |------|------|--------| | A.1 | `app/Policies/OperationRunPolicy.php` | Confirm direct run-based entitlement path remains canonical; only adjust if tests expose hidden coupling or 403 vs 404 drift | | A.2 | `app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` | Keep `mount()` policy-first; add derived viewer-context state helpers if needed instead of inline header-state branching | | A.3 | `tests/Feature/RunAuthorizationTenantIsolationTest.php` | Extend tenant-entitlement 404 coverage for canonical run detail | | A.4 | `tests/Feature/Operations/TenantlessOperationRunViewerTest.php` | Add mismatched remembered/header tenant success path and tenantless-run success path | **Exit criteria**: Canonical run validity is demonstrably independent of remembered tenant context, while direct tenant entitlement still governs tenant-linked runs. ### Phase B — Add Explicit Context And Lifecycle Messaging **Goal**: Replace silent punitive behavior with a non-blocking informational surface when header tenant context differs or the run references onboarding, archived, selector-excluded, or no tenant. **Risk**: Medium — the banner must be informative only and must not become a second authorization gate or produce conflicting messages. **Tests first**: Enterprise detail rendering tests plus new spec-specific message assertions. | Step | File | Change | |------|------|--------| | B.1 | `app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` | Add computed banner and follow-up-affordance payload describing run tenant, current header tenant, tenantless state, or selector-excluded lifecycle state | | B.2 | `resources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.php` | Render the non-blocking canonical context banner above the infolist, alongside the existing redaction integrity note | | B.3 | `tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php` | Assert mismatch messaging renders without displacing existing run summary sections | | B.4 | `tests/Feature/144/CanonicalOperationViewerContextMismatchTest.php` | Add dedicated coverage for mismatch, onboarding, archived, selector-excluded, and tenantless informational states plus reduced follow-up actions | **Exit criteria**: Operators get explicit context and lifecycle framing, plus lifecycle-safe follow-up action treatment, without blocking page access or confusing mismatch with access failure. ### Phase C — Keep Display Affordances Separate From Legitimacy **Goal**: Ensure `OperateHubShell`, header actions, and middleware exemptions remain convenience-only behavior for canonical routes. **Risk**: Medium — Livewire update requests and stale remembered tenants are the easiest places for context drift to reappear. **Tests first**: Existing `OperateHubShellTest`, header-context tests, and canonical route tests. | Step | File | Change | |------|------|--------| | C.1 | `app/Support/OperateHub/OperateHubShell.php` | Keep `activeEntitledTenant()` as the display/navigation source only; adjust helper wording or fallback behavior if banner requirements expose ambiguity | | C.2 | `app/Support/Middleware/EnsureFilamentTenantSelected.php` | Verify canonical route and `/livewire/update` exemptions remain hands-off for the run viewer; only adjust if tests show re-entry side effects | | C.3 | `tests/Feature/OpsUx/OperateHubShellTest.php` | Extend remembered-vs-Filament tenant resolution assertions for canonical run routes | | C.4 | `tests/Feature/Monitoring/HeaderContextBarTest.php` | Add or update expectations for canonical-run pages with mismatched tenant context | **Exit criteria**: Header labels, return actions, and remembered tenant cleanup behave as convenience features only and never invalidate the viewer. ### Phase D — Preserve Canonical Deep-Link Trust **Goal**: Guarantee in-product `View run` links remain canonical and self-sufficient regardless of the source surface. **Risk**: Low — helpers already point to the canonical route, but coverage must include tenant, notification-style, and verification-surface entry points plus selector-excluded tenant scenarios. **Tests first**: Existing canonical-link tests plus new deep-link trust regression. | Step | File | Change | |------|------|--------| | D.1 | `app/Support/OperationRunLinks.php` | Keep canonical `tenantlessView()` contract and make any touched related-link semantics explicitly lifecycle-safe for selector-excluded tenants | | D.2 | `tests/Feature/OpsUx/CanonicalViewRunLinksTest.php` | Preserve canonical route helper expectations | | D.3 | `tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php` | Extend direct-route coverage for onboarding, archived, or selector-excluded tenant-linked runs and verification-surface entry points | | D.4 | `tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php` | Add tenant-page, notification-style, and monitoring or verification deep-link coverage under changed or missing header context | **Exit criteria**: Canonical run links remain trustworthy from tenant pages, notification-style entry points, verification surfaces, and monitoring entry points. ### Phase E — Regression Sweep And Guard Alignment **Goal**: Leave behind a focused regression pack that keeps Spec 143 and Spec 144 semantics enforceable in CI. **Risk**: Low — primarily additive tests and minor expectation updates. **Tests first**: New spec pack and touched existing tests. | Step | File | Change | |------|------|--------| | E.1 | `tests/Feature/144/CanonicalOperationViewerContextMismatchTest.php` | Positive path matrix: matching tenant, mismatched tenant, no selected tenant, onboarding tenant, archived or selector-excluded tenant, tenantless run, and reduced follow-up action case | | E.2 | `tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php` | Deep-link matrix from tenant page, notification-style, and workspace verification or monitoring surfaces | | E.3 | Existing touched tests | Preserve 404 vs 403 semantics, DB-only rendering, and enterprise detail layout expectations | | E.4 | `vendor/bin/sail bin pint --dirty --format agent` and focused Pest runs | Required verification before moving to tasks or implementation | **Exit criteria**: CI-relevant coverage exists for the canonical trust contract and no pre-existing canonical-view protections regress. --- ## Key Design Decisions ### D-001 — Keep `OperationRunPolicy` as the canonical authorization boundary The viewer must continue to authorize from the run and its direct relationships, not from selected tenant state. That keeps 404 versus 403 semantics explicit and prevents header context from becoming a second hidden gate. **See**: [research.md](research.md) R-001 ### D-002 — Use `OperateHubShell` only for display and navigation affordances `activeEntitledTenant()` already models the current header context for workspace-scoped pages. The plan preserves that role but explicitly keeps it out of route legitimacy, which limits changes to messaging and return affordances. **See**: [research.md](research.md) R-002 ### D-003 — Put context-mismatch UX in the viewer wrapper, not in policy code Mismatch and lifecycle messaging are presentation concerns. Rendering them in the Blade wrapper above the existing infolist minimizes code churn and avoids contaminating the authorization layer or `OperationRunResource::infolist()` reuse. **See**: [research.md](research.md) R-003 ### D-004 — Preserve the existing canonical deep-link helper contract `OperationRunLinks::tenantlessView()` is already the canonical route generator and should stay authoritative. The feature strengthens trust and coverage around that contract rather than inventing a second route family or source-aware URL rules. **See**: [research.md](research.md) R-004 ### D-005 — Keep the solution reusable for future canonical viewers The viewer hardening must stay expressed as canonical workspace-viewer semantics rather than as an operations-only exception. Any touched middleware, page-category, or viewer-state rules should remain generic enough to support future canonical workspace-level record viewers without changing ownership or introducing a second classification path. **See**: [spec.md](spec.md) FR-144-020 --- ## Risk Assessment | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | Hidden tenant-context coupling remains in middleware or page mount logic | Medium | Medium | Add route, Livewire update, and stale remembered-tenant regressions before changing behavior | | Viewer messaging accidentally implies entitlement where follow-up links are unavailable | Medium | Medium | Separate “run is viewable” from “tenant follow-up actions may be unavailable” in banner text, affordance gating, and tests | | Authorization changes drift from `OperationRunCapabilityResolver` semantics | Medium | Low | Keep policy and capability resolver together; add 404 vs 403 matrix tests | | Existing enterprise detail layout regresses when banner is added | Low | Medium | Reuse current wrapper and extend enterprise detail page tests | | Deep-link trust is hardened only for one entry point | Medium | Medium | Add spec-specific deep-link coverage from tenant, notification-style, and workspace verification or monitoring surfaces | --- ## Test Strategy ### New Tests (spec-specific in `tests/Feature/144/`) | Test ID | File | Coverage | |---------|------|----------| | T-144-001 | `CanonicalOperationViewerContextMismatchTest.php` | Authorized run remains viewable when current header tenant differs from run tenant | | T-144-002 | `CanonicalOperationViewerContextMismatchTest.php` | Tenantless run renders with workspace framing and no selected tenant requirement | | T-144-003 | `CanonicalOperationViewerContextMismatchTest.php` | Onboarding, archived, or selector-excluded tenant-linked run remains viewable for authorized actors | | T-144-004 | `CanonicalOperationViewerContextMismatchTest.php` | Non-entitled tenant-linked run remains deny-as-not-found | | T-144-005 | `CanonicalOperationViewerDeepLinkTrustTest.php` | Tenant-detail deep link opens canonical viewer under mismatched header context | | T-144-006 | `CanonicalOperationViewerDeepLinkTrustTest.php` | Monitoring, notification-style, or verification-surface deep link opens canonical viewer with no selected tenant | | T-144-007 | `CanonicalOperationViewerContextMismatchTest.php` | Lifecycle-safe follow-up actions remain reduced or absent without invalidating the canonical run viewer | ### Existing Tests To Extend | File | Purpose | |------|---------| | `tests/Feature/Operations/TenantlessOperationRunViewerTest.php` | Core canonical viewer behavior and Livewire page semantics | | `tests/Feature/OpsUx/OperateHubShellTest.php` | Remembered tenant, Filament tenant, and return-affordance resolution | | `tests/Feature/Monitoring/OperationsTenantScopeTest.php` | Operations index tenant prefilter and canonical detail 404 isolation | | `tests/Feature/RunAuthorizationTenantIsolationTest.php` | Direct tenant-entitlement isolation on canonical run routes | | `tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php` | Detail-page structure and contextual content rendering | | `tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php` | Canonical URL trust across operations surfaces | ### Focused Verification Command ```bash vendor/bin/sail artisan test --compact \ tests/Feature/144/CanonicalOperationViewerContextMismatchTest.php \ tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php \ tests/Feature/Operations/TenantlessOperationRunViewerTest.php \ tests/Feature/OpsUx/OperateHubShellTest.php \ tests/Feature/Monitoring/OperationsTenantScopeTest.php \ tests/Feature/RunAuthorizationTenantIsolationTest.php \ tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php ```