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