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

151 lines
9.3 KiB
Markdown

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