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

6.7 KiB

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_designcovered_by_test
  • covered_by_testguarded_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
  • 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.