## 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
20 KiB
Feature Specification: Spec 334 - Nested Filament / Livewire Context Contract Hardening
- Feature Branch:
334-nested-filament-context-contract-hardening - Created: 2026-05-24
- Status: Draft
- Type: Platform integrity / Livewire lifecycle hardening / Filament context contract / productization blocker removal
- Runtime posture: Narrow platform hardening. No tenancy rewrite.
- Input: User-provided Spec 334 draft + repo inspection for path truth.
Dependencies And Historical Context
This spec exists because two independent operator flows exposed the same underlying defect class:
- Backup Set → “Add Policies” picker modal
Filament::getTenant()can benullin nested/modal contexts.- Authorization then fails false-negative for a bulk action / row selection.
- Filament hides the row selection checkbox column.
- Operator sees policies as non-selectable.
- Restore Run → Create wizard (Livewire update lifecycle)
- Livewire update request is a
POST /livewire-.../updaterequest with no route parameters. - Option closures can evaluate in that lifecycle.
- Context resolution that hard-requires ambient Filament tenant can throw, blocking the wizard.
Repo truth note: the user draft references a “Spec 332 Restore Flow Productization WIP” as the direct dependency. On platform-dev there is currently no specs/332-* directory. This spec still stands as a narrow platform hardening slice, and should be linked to the active restore productization spec/branch once it exists in-repo.
Related existing work (context only; do not modify closed specs here):
specs/152-livewire-context-locking(draft): broader “trusted state” hardening. This spec is narrower and explicitly tenant-context-centric.specs/302-tenant-owned-surface-route-audit: established the currentResolvesPanelTenantContextseam and route-owned environment posture.docs/research/filament-v5-notes.md: source of truth when Filament/Livewire behavior is uncertain.
Spec Candidate Check (mandatory - SPEC-GATE-001)
- Problem: Nested Filament / Livewire surfaces sometimes execute without reliable ambient tenant context, causing false-negative authorization, hidden selection controls, or hard runtime failures in operator-critical flows.
- Today's failure: Authorized operators cannot select policies in the Add Policies picker (checkbox column missing) and restore create flows can crash during Livewire updates with “no tenant context selected”.
- User-visible improvement: Operator-critical nested surfaces remain stable (no missing checkboxes, no wizard crash) and fail closed with honest UI states when context is genuinely unavailable.
- Smallest enterprise-capable version: Harden only confirmed high-risk nested surfaces and the shared context seam; add guardrails/tests so unsafe patterns do not reappear. No route architecture rewrite, no tenancy model redesign.
- Explicit non-goals: No new tenancy concept, no workspace model redesign, no panel rewrite, no broad repo-wide replacement of
Filament::getTenant(), no RBAC rebuild, no speculative general framework beyond what at least two confirmed surfaces require. - Permanent complexity imported: Targeted context resolver adjustments, a small optional helper (only if reused by ≥2 confirmed surfaces), focused Pest tests (Feature/Livewire + one architecture guard), and browser smoke steps for the two originally user-visible bugs.
- Why now: This is a platform-level productization blocker for restore workflows and breaks a visible day-to-day operator action (“Add policies”).
- Why not local: The same defect class occurs across independent surfaces (modal table, wizard update, relation manager, widget). Without an explicit contract + guardrails, it will recur.
- Approval class: Core Enterprise.
- Red flags triggered: (2) new meta-infrastructure risk (context helper + guard tests), (3) multiple surfaces. Defense: scope is explicitly limited to confirmed surfaces; helper is optional and must satisfy ABSTR-001 (≥2 real consumers); no new persisted truth, routes, or global “magic tenant switch”.
- Score: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | Gesamt: 11/12
- Decision: approve.
Spec Scope Fields (mandatory)
- Scope: tenant (managed-environment scoped operator surfaces under
/admin/workspaces/{workspace}/environments/{environment}/...) + Livewire update lifecycle hardening. - Primary Routes / Surfaces (representative, non-exhaustive):
- Backup Set detail → “Add policies” modal table (Livewire component).
- Restore Runs → Create wizard (Filament resource create page).
- Backup Schedule detail → Operation Runs relation manager table.
- Managed Environment dashboard → triage widget.
- Data Ownership:
- Tenant/environment-owned records:
ManagedEnvironment,BackupSet,BackupSchedule,RestoreRun, related policy inventory models. - Workspace ownership remains authoritative; context recovery must validate workspace membership/entitlement.
- No new persisted entity/table is introduced by this spec.
- Tenant/environment-owned records:
- RBAC:
- Workspace membership + environment entitlement required to view tenant-scoped surfaces.
- Mutations (attach policies, restore run create/confirm, etc.) require explicit capability checks and policy/gate enforcement; UI visibility is not authorization.
For canonical-view specs: N/A. This spec hardens environment/tenant-scoped nested surfaces, not a workspace-owned canonical viewer.
UI Surface Impact (mandatory - UI-COV-001)
Does this spec add, remove, rename, or materially change any reachable UI surface?
- No UI surface impact
- Existing page changed
- New page/route added
- Navigation changed
- Filament panel/provider surface changed
- New modal/drawer/wizard/action added
- New table/form/state added
- Customer-facing surface changed
- Dangerous action changed
- Status/evidence/review presentation changed
- Workspace/environment context presentation changed
UI/Productization Coverage
- Route/page/surface: Add Policies modal table, Restore Run Create wizard, Backup Schedule Operation Runs relation manager, Managed Environment triage widget.
- Current or new page archetype: Domain Pattern Surface (nested CRUD tooling and wizards), not a new strategic page archetype.
- Design depth: Internal/Hidden (but operator-reachable and workflow-critical).
- Repo-truth level: repo-verified for file paths and current helper seams; runtime behavior must be validated by tests + browser smoke.
- Existing pattern reused: current
ResolvesPanelTenantContextseam, route-owned environment posture,UiEnforcement, policies/gates, and owner-record scoping patterns. - New pattern required: explicit nested context resolution order for high-risk surfaces; optional small helper only if used by ≥2 surfaces.
- Screenshot required: no new design screenshots required; browser smoke is required for the two user-visible bugs.
- Page audit required: no UI audit registry update expected unless implementation changes navigation or surface archetype.
- Customer-safe review required: no. These are operator surfaces; still must fail closed and never broaden scope.
- Dangerous-action review required: yes. Restore-related and attach-related actions are high-impact and must keep explicit confirmation + authorization + audit posture.
- Coverage files updated or explicitly not needed:
docs/ui-ux-enterprise-audit/route-inventory.mddocs/ui-ux-enterprise-audit/design-coverage-matrix.mddocs/ui-ux-enterprise-audit/page-reports/...docs/ui-ux-enterprise-audit/strategic-surfaces.mddocs/ui-ux-enterprise-audit/grouped-follow-up-candidates.mddocs/ui-ux-enterprise-audit/unresolved-pages.mdN/A - no new routes/pages; coverage handled via tests + browser smoke + PR close-out note
Cross-Cutting / Shared Pattern Reuse
- Cross-cutting feature?: yes.
- Interaction class(es): authorization/visibility, table row selection, bulk actions, wizard step transitions, select option closures, widget visibility/mutation.
- Systems touched: Filament tenant resolution seam, route-owned environment posture, remembered environment context (validated only), and nested Livewire lifecycle behavior.
- Existing pattern(s) to extend: owner-record scoping,
UiEnforcement, deny-as-not-found (404) vs forbidden (403) semantics, and existing environment context middleware/helpers. - Shared contract / presenter / builder / renderer to reuse:
apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php,apps/platform/app/Support/OperateHub/OperateHubShell.php,apps/platform/app/Support/Workspaces/WorkspaceContext.php,apps/platform/app/Support/Rbac/UiEnforcement.php, and existing policies. - Why the existing shared path is sufficient or insufficient: sufficient for route-owned page loads; insufficient for nested Livewire update requests and nested surfaces that currently assume ambient tenant is always present.
- Allowed deviation and why: bounded “nested context contract” logic may be added only where confirmed surfaces prove it is needed; no global ambient-tenant-as-truth fallback is allowed.
- Review focus: fail-closed semantics (no broadening), no unsafe
Filament::setTenant($model->...)without validated ownership/capability, and stable UX for authorized operators when context is recoverable.
Summary
Harden TenantPilot’s nested Filament and Livewire context handling so critical nested surfaces do not depend on unreliable ambient Filament::getTenant() state during modal, wizard, relation manager, widget, and Livewire update lifecycles.
This spec does not rewrite tenancy, routing, RBAC, or the workspace model. It introduces targeted hardening for confirmed high-risk surfaces and installs guardrails so the same issue does not keep reappearing.
Problem Statement
Nested Filament/Livewire surfaces can execute outside the full page routing lifecycle:
- modal tables
- relation managers
- wizard step transitions
- select option closures
- Livewire update requests (
POST /livewire-.../update) - widgets
- bulk actions
- row selection
- table refresh/search/filter/pagination
In those contexts:
- route parameters may be missing
- ambient Filament tenant may be
null - authorization can fail false-negative
- UI controls may disappear
- option closures may hard-fail
Root problem:
Ambient framework context is being used where explicit domain context is required.
Desired nested context contract:
- Owner/domain context first.
- Validated route/workspace/environment context second.
- Remembered/session environment only if validated.
- Ambient Filament tenant only as fallback convenience.
- Fail closed with a clear product state.
- Never switch tenant from a model before ownership/capability is proven.
Goals
G1 — Define and enforce a nested context contract
Create a consistent rule for nested Filament/Livewire surfaces:
Domain context is authoritative. Filament tenant is runtime convenience.
The contract must cover: table queries, option closures, visibility/disabled checks, row selection, bulk actions, mutation handlers, relation managers, widgets, and wizard transitions.
G2 — Fix the confirmed Add Policies / BackupSet picker class
apps/platform/app/Livewire/BackupSetPolicyPickerTable.php must be self-protecting:
- does not trust a passed BackupSet ID before validating workspace/environment ownership and access
- authorized operators can select policies even if ambient tenant is initially null
- forged cross-workspace/cross-environment IDs fail closed
- no unguarded
Filament::setTenant(...)
G3 — Fix the confirmed Restore Run Create Wizard Livewire context loss
apps/platform/app/Filament/Resources/RestoreRunResource.php and shared context resolution must not hard-fail during Livewire update requests when context is recoverable from a validated source.
G4 — Harden owner-derived relation/widget surfaces
At minimum:
apps/platform/app/Filament/Resources/BackupScheduleResource/RelationManagers/BackupScheduleOperationRunsRelationManager.phpapps/platform/app/Filament/Widgets/ManagedEnvironment/ManagedEnvironmentTriageArrivalContinuity.php
must resolve context from owner record / widget record before using ambient tenant.
G5 — Install guardrails
Add guardrails/tests that prevent reintroducing unsafe patterns such as model-derived Filament::setTenant(...) in nested surfaces without prior ownership/capability validation.
G6 — Unblock restore productization validation
After this spec is implemented, restore flow productization work should be able to run restore create wizard browser validation without a tenantless Livewire update crash.
Non-Goals
- no workspace model redesign
- no managed environment model redesign
- no Filament panel rewrite
- no route architecture rewrite
- no RBAC rebuild
- no broad resource authorization audit
- no restore UX redesign beyond what is needed to make the wizard stable
- no broad repo-wide replacement of
Filament::getTenant()usage
Core Rules
R1 — Domain context before Filament tenant
Nested surfaces must use owner/domain records first.
R2 — No unguarded model-derived tenant switch
Switching tenant based on a loaded model is forbidden unless ownership/capability is validated and mismatch handling fails closed.
R3 — UI visibility is not authorization
Hidden checkboxes/buttons are not security boundaries. Mutations must be authorization-checked at execution time.
R4 — Fail closed, but not mysteriously
If context is invalid: no data leak, no mutation, no unscoped query, and no silent cross-tenant switch.
R5 — Livewire update requests need recoverable context
During Livewire update (POST /livewire-.../update), route params may be absent. Recovery must come from validated component state / owner record / validated remembered context. Referer may be a candidate only, never authority.
R6 — Mutation-time recheck
Every mutation handler must re-resolve and re-check context and authorization.
Confirmed Scope
S1 — BackupSetPolicyPickerTable
- File:
apps/platform/app/Livewire/BackupSetPolicyPickerTable.php - Issue class: tenantless modal context → false-negative authorization → checkbox column hidden.
- Required outcome: authorized users can select and attach in-scope policies; cross-scope mounts fail closed; no unsafe tenant switching.
S2 — RestoreRunResource Livewire update context loss
- Files:
apps/platform/app/Filament/Resources/RestoreRunResource.phpapps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php
- Issue class: Livewire update has no route params → tenant resolution throws.
- Required outcome: wizard does not crash; option closures do not depend exclusively on ambient tenant; context recovery is validated.
S3 — BackupScheduleOperationRunsRelationManager
- File:
apps/platform/app/Filament/Resources/BackupScheduleResource/RelationManagers/BackupScheduleOperationRunsRelationManager.php - Required outcome: owner schedule context drives query and URLs; missing/wrong ambient tenant does not broaden scope.
S4 — ManagedEnvironmentTriageArrivalContinuity
- File:
apps/platform/app/Filament/Widgets/ManagedEnvironment/ManagedEnvironmentTriageArrivalContinuity.php - Required outcome: record context first, ambient tenant fallback only; missing context fails closed; visibility and mutation use the same resolver.
S5 — Guardrail tests
New or existing guard tests must live alongside existing architecture tests at:
apps/platform/tests/Architecture/FilamentTenantContextContractTest.php(new)
Acceptance Criteria
AC1 — Restore Run Create Wizard no longer crashes on Livewire update
- Livewire updates do not throw “no tenant context selected” when context is recoverable.
- Backup set options remain environment-scoped.
- Cross-workspace or cross-environment options are never visible.
- If context is not recoverable, the UI fails closed (disabled/empty) without leaking scope.
AC2 — BackupSet Policy Picker is self-protecting
- Authorized tenantless modal mount shows selectable in-scope policies.
- Checkbox column remains visible for authorized users.
- Cross-scope or forged mounts fail closed.
- Cross-environment policy IDs cannot be attached.
AC3 — BackupSchedule Operation Runs relation is owner-scoped
- Relation uses owner BackupSchedule context.
- Missing ambient tenant does not crash.
- Wrong ambient tenant does not broaden query or URLs.
AC4 — Triage widget uses explicit context
- Record context is preferred; ambient tenant is fallback only.
- Missing context fails closed.
- Authorized operators do not lose actions due to ambient context loss.
AC5 — Guardrails exist
- Unsafe model-derived
Filament::setTenant(...)in nested surfaces is detected. - Allowlist is explicit and limited to infrastructure.
AC6 — No broad unrelated changes
- No panel/routing rewrite, no workspace/managed-environment redesign, no new persisted truth, and no unrelated UX/productization expansion.
Proportionality Review
- New source of truth?: no.
- New persisted entity/table/artifact?: no.
- New abstraction?: optional small helper only if it is reused by ≥2 confirmed surfaces; otherwise local resolvers only.
- New enum/status family?: no.
- Current operator problem: missing row selection and crashing restore wizard due to context loss.
- Narrowest correct implementation: validate and re-resolve context from owner/domain and canonical workspace/environment truth; fall back only when safe; add guard tests.
- Ownership cost: limited helper/tests + browser smoke for two user-visible issues.
- Alternative rejected: tenancy rewrite, route architecture changes, broad repo sweep of
Filament::getTenant(), or “always set tenant from model” fallback.
Testing / Lane / Runtime Impact (mandatory for runtime behavior changes)
- Test purpose / classification: Feature + Livewire + Architecture (guard); browser smoke for the two user-visible bugs.
- Validation lane(s): confidence + browser (only for the named smoke flows).
- Why this lane mix is sufficient: Feature/Livewire tests prove scoped behavior and prevent regressions; browser smoke verifies the original “checkbox missing” and “wizard crash” end-to-end.
- No Graph calls during UI render: must remain true; context recovery must be DB-only and authorization-safe.
Risks & Mitigations
- Risk: Resolver becomes too broad or “magic”.
- Mitigation: Fix only confirmed seams; keep helper optional and bounded; add explicit tests and allowlists.
- Risk: Referer parsing introduces security risk.
- Mitigation: Referer can only be a candidate; any extracted identifiers must be validated against DB membership and scope.
- Risk: Guardrail is too noisy.
- Mitigation: Start by guarding unsafe
setTenantpatterns; classifygetTenantusage before hard failing.
- Mitigation: Start by guarding unsafe
Open Questions
- Which exact capability constants gate:
- attaching policies to a backup set
- creating a restore run
- viewing schedule operation runs
- acting in triage widget actions (Implementation must use the existing capability registry; no new strings.)
- Does the repo already have a safe “current environment” helper for nested Livewire updates beyond
ResolvesPanelTenantContext? If yes, reuse it.
Follow-Up Spec Candidates (out of scope for Spec 334)
- Broader “environment-resource-context-follow-through” rollout beyond the 4 confirmed surfaces.
- Harmonize with Spec 152 “trusted state” hardening if both proceed; avoid duplicated helper layers.