TenantAtlas/specs/334-nested-filament-context-contract-hardening/plan.md
ahmido f967db7983 Spec 334: harden nested Filament Livewire context contract (#395)
## Summary
- harden nested Filament and Livewire tenant-context handling across the backup schedule operation runs relation manager, managed-environment triage arrival continuity, the backup set policy picker table, and the Operate Hub shell
- add architecture, feature, and browser coverage for nested Filament tenant-context continuity and restore-run resource behavior
- add the Spec 334 artifacts (`spec.md`, `plan.md`, `tasks.md`, and the requirements checklist)

## Testing
- Not run as part of this commit/push/PR workflow

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #395
2026-05-24 21:33:19 +00:00

9.3 KiB

Implementation Plan: Spec 334 - Nested Filament / Livewire Context Contract Hardening

  • Branch: 334-nested-filament-context-contract-hardening
  • Date: 2026-05-24
  • Spec: specs/334-nested-filament-context-contract-hardening/spec.md
  • Input: User-provided Spec 334 draft + repo inspection for path truth.

Summary

Define and enforce a nested Filament/Livewire context contract so operator-critical nested surfaces do not depend on ambient Filament::getTenant() during modal, wizard, relation manager, widget, and Livewire update lifecycles.

Implement a narrow hardening slice across four confirmed surfaces, plus guard tests:

  • apps/platform/app/Livewire/BackupSetPolicyPickerTable.php
  • apps/platform/app/Filament/Resources/RestoreRunResource.php (+ apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php seam)
  • apps/platform/app/Filament/Resources/BackupScheduleResource/RelationManagers/BackupScheduleOperationRunsRelationManager.php
  • apps/platform/app/Filament/Widgets/ManagedEnvironment/ManagedEnvironmentTriageArrivalContinuity.php
  • new architecture guard: apps/platform/tests/Architecture/FilamentTenantContextContractTest.php

Technical Context

  • Language/Version: PHP 8.4.15, Laravel 12.52.x.
  • Primary Dependencies: Filament 5.2.x, Livewire 4.1.x, Pest 4.x, Tailwind CSS 4.x.
  • Storage: PostgreSQL; no schema change expected.
  • Testing: Pest Feature + Livewire component tests + one architecture guard; browser smoke only for the two user-visible regression scenarios.
  • Validation Lanes: confidence + browser (scoped).
  • Target Platform: Laravel Sail locally; Dokploy/container deployment posture unchanged.
  • Project Type: Laravel monolith under apps/platform.
  • Performance Goals: DB-only context recovery; no Graph calls during UI render; no broad unscoped queries in nested surfaces.
  • Constraints: No new persisted truth, migrations, packages, env vars, queue/scheduler changes, or routing architecture changes. Scale/Scope: Four confirmed nested surfaces + shared seam hardening + guard tests.

UI / Surface Guardrail Plan

  • Guardrail scope: changed existing operator-facing nested surfaces (modal, wizard, relation manager, widget).
  • Affected routes/pages/actions/states/navigation/panel/provider surfaces:
    • Backup Set “Add policies” modal table (Livewire component).
    • Restore Run Create wizard (resource create page).
    • Backup Schedule Operation Runs relation manager table (nested resource detail).
    • Environment dashboard triage widget (nested widget).
  • No-impact class: N/A.
  • Native vs custom classification summary: native Filament + Livewire surfaces; no custom UI framework.
  • Shared-family relevance: environment context resolution, authorization/visibility gating, table selection semantics, wizard option closures, remembered context validation.
  • State layers in scope: route-owned workspace/environment, Livewire component state (captured at mount), remembered environment context (validated only), ambient Filament tenant (fallback convenience only).
  • Audience modes in scope: operator-MSP / platform operators. No customer/read-only surface changes expected.
  • Decision/diagnostic/raw hierarchy plan: fail-closed states must be honest; do not show “no records” when context is invalid; no raw debug “framework state” troubleshooting exposed to operators.
  • Handling modes: review-mandatory. This is a correctness hardening slice affecting authorization and scope.
  • Special surface test profiles: exception-coded-surface (context recovery) + shared-detail-family (nested relation manager) + browser smoke for two user-visible regressions.
  • Required tests or manual smoke:
    • Feature/Livewire tests for each confirmed surface and its fail-closed behavior.
    • Architecture guard preventing unsafe Filament::setTenant(...) usage in nested surfaces.
    • Browser smoke for:
      • Backup Set “Add policies” checkbox visibility + selection.
      • Restore Run Create wizard no-crash on Livewire update transitions.
  • Exception path and spread control: no broad fallback that sets tenant from arbitrary model IDs. Any referer-based recovery must validate workspace membership and environment entitlement.
  • UI/Productization coverage decision: no new routes/pages; cover via tests + browser smoke + PR close-out note.

Shared Pattern & System Fit

  • Cross-cutting feature marker: yes (multiple nested surfaces).
  • Systems touched:
    • Filament tenant resolution seam: apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php
    • Route/shell context: apps/platform/app/Support/OperateHub/OperateHubShell.php
    • Workspace/environment context + remembered context: apps/platform/app/Support/Workspaces/WorkspaceContext.php
    • UI gating: apps/platform/app/Support/Rbac/UiEnforcement.php
  • Shared abstractions reused: prefer existing helpers/policies over new framework layers.
  • New abstraction introduced?: none by default. A small helper class may be introduced only if at least two confirmed surfaces would otherwise duplicate validated context recovery logic (ABSTR-001).
  • Bounded deviation / spread control: any new helper must be feature-local in scope and must not become a generic “ambient context fixer” without explicit follow-up spec approval.

OperationRun UX Impact

N/A. This feature does not introduce new queued operations. It must not create new OperationRun types or change OperationRun UX policy.

Provider Boundary / Platform Core Check

Platform-core hardening only. No Graph contract, provider registry, or provider vocabulary changes.

Implementation Approach

Phase 1 — Repo truth + reproduce

  • Inspect current behavior and confirm the concrete failure path(s) for:
    • Add Policies modal selection checkbox hidden.
    • Restore Run Create wizard crash during Livewire update.
  • Document the exact call path(s) and which closures/methods access context.

Phase 2 — Define nested context resolution order

Implement (or codify within existing seams) a deterministic resolution order for managed-environment context in nested surfaces:

  1. Owner/domain record (owner record, component-bound record, backup set record, schedule record, widget record).
  2. Validated component state captured at mount (e.g., environment ID saved in the Livewire component state).
  3. Route-owned workspace/environment (when route params are available).
  4. Remembered environment only if workspace membership and entitlement validate.
  5. Ambient Filament tenant as fallback convenience only.
  6. Fail closed with a clear, honest UI state.

Prohibited:

  • blindly trusting referer as authority
  • unguarded Filament::setTenant($model->...) derived from an arbitrary identifier
  • broad “set tenant from model id” fallback in the shared seam

Phase 3 — Apply the contract to confirmed surfaces

  • BackupSetPolicyPickerTable:
    • Resolve context from the BackupSet (validated) and scope policy query accordingly.
    • Ensure mutation-time recheck before attaching policy IDs.
    • Do not override a mismatched existing tenant; fail closed.
  • RestoreRunResource:
    • Ensure option closures used in wizard lifecycles can resolve environment context without route params.
    • Implement validated Livewire update context recovery via the shared seam (apps/platform/app/Support/OperateHub/OperateHubShell.php) by treating the Referer path as a candidate (never authority) and re-validating workspace + membership + operability before using it.
  • BackupScheduleOperationRunsRelationManager:
    • Use owner schedule context ($this->getOwnerRecord()) as primary context.
    • Avoid ambient ManagedEnvironment::currentOrFail()-style assumptions when owner exists.
  • ManagedEnvironmentTriageArrivalContinuity:
    • Prefer widget record context; ambient tenant only fallback.
    • Use the same resolver for visibility and mutation-time checks.

Phase 4 — Guardrails

  • Add apps/platform/tests/Architecture/FilamentTenantContextContractTest.php to:
    • detect unsafe Filament::setTenant(...) patterns in nested surfaces
    • keep an explicit allowlist for infrastructure-only locations
    • optionally classify (not fail) direct Filament::getTenant() usage in known high-risk directories first, to avoid noisy failures

Phase 5 — Tests and validation

Add focused tests (Feature/Livewire) for:

  • tenantless Add Policies picker: authorized user still sees checkboxes and can select items
  • restore create wizard: does not throw when route params are missing in Livewire update lifecycle
  • owner-scoped relation manager: query does not broaden when ambient tenant is null/wrong
  • widget context: record context is preferred; missing context fails closed

Validation commands (narrow first):

  • cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament --filter='BackupSetPolicyPicker|RestoreRun|BackupScheduleOperationRuns|Triage' --compact
  • cd apps/platform && ./vendor/bin/sail artisan test tests/Architecture --filter='FilamentTenantContextContract' --compact

Browser smoke (only when feature tests pass):

  • cd apps/platform && php vendor/bin/pest tests/Browser/Spec334NestedFilamentContextContractSmokeTest.php --compact

Deployment / Ops Impact

  • Migrations: none expected.
  • Env vars: none expected.
  • Queues/scheduler: none expected.
  • Filament assets: no new registered assets expected; deployment posture unchanged.