TenantAtlas/specs/152-livewire-context-locking/data-model.md
ahmido 5ec62cd117 feat: harden livewire trusted state boundaries (#182)
## Summary
- add the shared trusted-state model and resolver helpers for first-slice Livewire and Filament surfaces
- harden managed tenant onboarding, tenant required permissions, and system runbooks against forged or stale public state
- add focused Pest guard and regression coverage plus the complete spec 152 artifact set

## Validation
- `vendor/bin/sail artisan test --compact`
- manual smoke validated on `/admin/onboarding/{onboardingDraft}`
- manual smoke validated on `/admin/tenants/{tenant}/required-permissions`
- manual smoke validated on `/system/ops/runbooks`

## Notes
- Livewire v4.0+ / Filament v5 stack unchanged
- no new panels, routes, assets, or global-search changes
- provider registration remains in `bootstrap/providers.php`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #182
2026-03-18 23:01:14 +00:00

189 lines
6.7 KiB
Markdown

# Data Model: Livewire Context Locking and Trusted-State Reduction
This feature does not introduce new database tables in the first slice. The data-model work formalizes trust-boundary entities and field classes that are already implicit in existing Livewire and Filament components.
## 1. Trusted State Class
### Purpose
Defines how a given piece of component state may be stored and used.
### Allowed values
- `presentation`
- `locked_identity`
- `server_derived_authority`
### Rules
- `presentation` state may remain public and mutable.
- `locked_identity` state may remain public only when it is client-immutable and still re-resolved before protected actions.
- `server_derived_authority` state must not rely on client mutability at all and is derived from route, session, resolver, or persisted workflow truth.
## 2. Component Trusted-State Policy
### Purpose
Represents the trust contract for one stateful Livewire or Filament surface.
### Fields
- `component_name`: human-readable identifier for the surface
- `plane`: `admin_tenant`, `admin_workspace`, or `system_platform`
- `route_anchor`: route-bound record or context source, if any
- `authority_sources`: canonical sources used to re-derive protected targets
- `locked_identities`: list of public scalar IDs allowed to persist for continuity
- `mutable_selectors`: list of user-controlled selectors that remain proposals only
- `forbidden_public_authority_fields`: list of model objects or mutable IDs disallowed as final authority
### Relationships
- One component policy has many trusted fields.
- One component policy has many forged-state regression cases.
## 3. Trusted Field
### Purpose
Represents one public property or equivalent state slot on a covered component.
### Fields
- `field_name`
- `state_class`
- `php_type`
- `source_of_truth`
- `used_for_protected_action`: boolean
- `revalidation_required`: boolean
- `notes`
### Validation rules
- If `state_class = presentation`, then `used_for_protected_action` must be false.
- If `used_for_protected_action = true`, then `state_class` must be `locked_identity` or `server_derived_authority`.
- If `php_type` is an Eloquent model and the field is ownership-relevant, it is a migration target and should be phased toward locked scalar or server-derived access.
## 4. Authority Source
### Purpose
Represents the canonical seam used to re-derive truth on the server.
### First-slice source types
- `route_binding`
- `workspace_context`
- `tenant_panel_context`
- `persisted_onboarding_draft`
- `allowed_tenant_universe`
- `explicit_scoped_query`
### Example mappings
- Onboarding draft identity: route binding + persisted onboarding draft
- Current workspace: `WorkspaceContext`
- Tenant-context page scope: route binding or `ResolvesPanelTenantContext`
- System runbook tenant selector: `AllowedTenantUniverse`
## 5. Selector Proposal
### Purpose
Represents mutable client-submitted selection state that is allowed to change but must be validated before use.
### Fields
- `selector_name`
- `target_model`
- `scope_rules`
- `null_allowed`: boolean
- `validation_outcome`
### Validation outcomes
- `accepted`
- `rejected_not_found`
- `rejected_forbidden`
- `reset_required`
### Rules
- A selector proposal never grants authority by itself.
- A selector proposal must be re-scoped to the current workspace, tenant, or allowed-universe before action execution.
## 6. Forged-State Regression Case
### Purpose
Represents a reproducible test scenario where client state is mutated to challenge the trust boundary.
### Fields
- `component_name`
- `mutation_type`: `foreign_id`, `stale_id`, `null_forced`, `cross_workspace`, `cross_plane`
- `entry_point`: page load, action call, modal submit, or rerun path
- `expected_outcome`: `404`, `403`, or no-op fail-closed
- `must_preserve_data_integrity`: boolean
### State transitions
- `pending_design``covered_by_test`
- `covered_by_test``guarded_in_ci`
## 7. First-Slice Surface Inventory
### Managed Tenant Onboarding Wizard
- `route_anchor`: onboarding draft route parameter
- Locked identities:
- `managedTenantId` (`?int`) backed by persisted onboarding draft identity and re-resolved through `currentManagedTenantRecord()`
- `onboardingSessionId` (`?int`) backed by persisted onboarding draft identity and re-resolved through `currentOnboardingSessionRecord()`
- Mutable selector proposals:
- `selectedProviderConnectionId` (`?int`) revalidated against canonical draft and scoped provider queries before verify/bootstrap paths
- `selectedBootstrapOperationTypes` (`array<int, string>`) remains presentation-only wizard state
- Server-derived authority fields:
- public `Workspace $workspace` refreshed from `WorkspaceContext`
- public `?Tenant $managedTenant` treated as display convenience only; canonical tenant truth comes from draft/scoped query
- public `?TenantOnboardingSession $onboardingSession` treated as display convenience only; canonical draft truth comes from resolver-backed persisted session lookup
- Canonical authority sources:
- `WorkspaceContext`
- onboarding draft resolver
- persisted draft state
- scoped provider-connection query
### Tenant Required Permissions Page
- `route_anchor`: tenant route parameter
- Locked identities:
- `scopedTenantId` (`?int`) derived from the route tenant and revalidated through `trustedScopedTenant()`
- Mutable selector proposals:
- `status`, `type`, `features`, and `search` remain presentation-only filters
- Server-derived authority fields:
- canonical tenant scope is derived through `resolveScopedTenant()`, `WorkspaceContext`, and `trustedScopedTenant()`
- Canonical authority sources:
- route-bound tenant resolution
- workspace context
### System Runbooks Page
- `route_anchor`: none
- Locked identities:
- none in the first slice; platform selector state remains proposal-only
- Public selector proposals:
- `findingsTenantId` (`?int`) is revalidated through `AllowedTenantUniverse::resolveAllowedOrFail()`
- `tenantId` (`?int`) is mirrored display state for the last trusted preflight result
- `findingsScopeMode` and `scopeMode` remain mutable UI state that must normalize into a trusted scope DTO before action execution
- Server-derived authority fields:
- trusted scope derives from `trustedFindingsScopeFromFormData()` / `trustedFindingsScopeFromState()` and the allowed tenant universe
- Canonical authority sources:
- authenticated platform user
- allowed tenant universe
- runbook scope DTO
## 8. Out-of-Scope but Related Fields
- Public widget record models such as tenant widgets storing `public ?Tenant $record`
- Resource page state handled primarily through Filament route model binding
- Non-stateful controller endpoints and queued job payloads
These remain rollout inventory candidates after the first slice proves the trusted-state pattern.