TenantAtlas/specs/144-canonical-operation-viewer-context-decoupling/plan.md

20 KiB

Implementation Plan: Canonical Operation Viewer Context Decoupling

Branch: 144-canonical-operation-viewer-context-decoupling | Date: 2026-03-15 | Spec: 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)

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)

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.

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 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 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 R-003

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 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 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

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