, * notes: string * } */ private function field( string $name, TrustedStateClass $stateClass, string $phpType, string $sourceOfTruth, bool $usedForProtectedAction, bool $revalidationRequired, array $implementationMarkers, string $notes, ): array { return [ 'name' => $name, 'state_class' => $stateClass->value, 'php_type' => $phpType, 'source_of_truth' => $sourceOfTruth, 'used_for_protected_action' => $usedForProtectedAction, 'revalidation_required' => $revalidationRequired, 'implementation_markers' => $implementationMarkers, 'notes' => $notes, ]; } /** * @return array, * locked_identities: list, * locked_identity_fields: list, * notes: string * }>, * mutable_selectors: list, * mutable_selector_fields: list, * notes: string * }>, * server_derived_authority_fields: list, * notes: string * }>, * forbidden_public_authority_fields: list * }> */ public function firstSlice(): array { return [ self::MANAGED_TENANT_ONBOARDING_WIZARD => [ 'component_name' => 'Managed tenant onboarding wizard', 'plane' => 'admin_workspace', 'route_anchor' => 'onboarding_draft', 'authority_sources' => [ 'route_binding', 'workspace_context', 'persisted_onboarding_draft', 'explicit_scoped_query', ], 'locked_identities' => [ 'workspace_id', 'managed_tenant_id', 'onboarding_session_id', ], 'locked_identity_fields' => [ $this->field( name: 'managedTenantId', stateClass: TrustedStateClass::LockedIdentity, phpType: '?int', sourceOfTruth: 'persisted_onboarding_draft', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ "#[Locked]\n public ?int \$managedTenantId = null;", 'public ?int $managedTenantId = null;', 'currentManagedTenantRecord()', ], notes: 'Continuity-only tenant identity; protected actions must re-resolve the tenant record before use.', ), $this->field( name: 'onboardingSessionId', stateClass: TrustedStateClass::LockedIdentity, phpType: '?int', sourceOfTruth: 'persisted_onboarding_draft', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ "#[Locked]\n public ?int \$onboardingSessionId = null;", 'public ?int $onboardingSessionId = null;', 'currentOnboardingSessionRecord()', ], notes: 'Continuity-only draft identity; protected actions re-resolve canonical draft truth from the persisted session.', ), ], 'mutable_selectors' => [ 'selected_provider_connection_id', 'selected_bootstrap_operation_types', ], 'mutable_selector_fields' => [ $this->field( name: 'selectedProviderConnectionId', stateClass: TrustedStateClass::Presentation, phpType: '?int', sourceOfTruth: 'explicit_scoped_query', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'public ?int $selectedProviderConnectionId = null;', 'selectedProviderConnectionId', 'resolveOnboardingDraft(', ], notes: 'Provider selection is a mutable proposal and must be validated against the canonical draft and workspace before use.', ), $this->field( name: 'selectedBootstrapOperationTypes', stateClass: TrustedStateClass::Presentation, phpType: 'array', sourceOfTruth: 'presentation_only', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ 'public array $selectedBootstrapOperationTypes = [];', ], notes: 'Wizard UI choice only; it does not define tenant or workspace authority.', ), ], 'server_derived_authority_fields' => [ $this->field( name: 'workspace', stateClass: TrustedStateClass::ServerDerivedAuthority, phpType: 'Workspace', sourceOfTruth: 'workspace_context', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'public Workspace $workspace;', 'currentWorkspaceForMember(', ], notes: 'Workspace membership and current workspace context outrank any client-submitted state.', ), $this->field( name: 'onboardingSession', stateClass: TrustedStateClass::ServerDerivedAuthority, phpType: '?TenantOnboardingSession', sourceOfTruth: 'persisted_onboarding_draft', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'public ?TenantOnboardingSession $onboardingSession = null;', 'resolveOnboardingDraft(', 'currentOnboardingSessionRecord()', ], notes: 'Draft model instances remain convenience state only and are refreshed from canonical persisted draft truth.', ), $this->field( name: 'managedTenant', stateClass: TrustedStateClass::ServerDerivedAuthority, phpType: '?Tenant', sourceOfTruth: 'explicit_scoped_query', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'public ?Tenant $managedTenant = null;', 'currentManagedTenantRecord()', ], notes: 'Tenant model instances are display helpers only and must be re-derived from draft or scoped tenant queries.', ), ], 'forbidden_public_authority_fields' => [ 'workspace', 'managedTenant', 'onboardingSession', ], ], self::TENANT_REQUIRED_PERMISSIONS => [ 'component_name' => 'Tenant required permissions', 'plane' => 'admin_tenant', 'route_anchor' => 'tenant', 'authority_sources' => [ 'route_binding', 'workspace_context', 'explicit_scoped_query', ], 'locked_identities' => [ 'scoped_tenant_id', ], 'locked_identity_fields' => [ $this->field( name: 'scopedTenantId', stateClass: TrustedStateClass::LockedIdentity, phpType: '?int', sourceOfTruth: 'route_binding', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ "#[Locked]\n public ?int \$scopedTenantId = null;", 'public ?int $scopedTenantId = null;', 'trustedScopedTenant()', ], notes: 'Route-derived tenant identity stays locked for continuity and is re-scoped against workspace context before use.', ), ], 'mutable_selectors' => [ 'status', 'type', 'features', 'search', ], 'mutable_selector_fields' => [ $this->field( name: 'status', stateClass: TrustedStateClass::Presentation, phpType: 'string', sourceOfTruth: 'presentation_only', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ "public string \$status = 'missing';", ], notes: 'Filter-only state for the permissions view model.', ), $this->field( name: 'type', stateClass: TrustedStateClass::Presentation, phpType: 'string', sourceOfTruth: 'presentation_only', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ "public string \$type = 'all';", ], notes: 'Filter-only state for the permissions view model.', ), $this->field( name: 'features', stateClass: TrustedStateClass::Presentation, phpType: 'array', sourceOfTruth: 'presentation_only', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ 'public array $features = [];', ], notes: 'Filter-only state for the permissions view model.', ), $this->field( name: 'search', stateClass: TrustedStateClass::Presentation, phpType: 'string', sourceOfTruth: 'presentation_only', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ "public string \$search = '';", ], notes: 'Filter-only state for the permissions view model.', ), ], 'server_derived_authority_fields' => [ $this->field( name: 'scopedTenant', stateClass: TrustedStateClass::ServerDerivedAuthority, phpType: '?Tenant', sourceOfTruth: 'explicit_scoped_query', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'resolveScopedTenant()', 'trustedScopedTenant()', 'currentWorkspaceId(request())', ], notes: 'Tenant scope remains route- and workspace-derived even when mutable filters change.', ), ], 'forbidden_public_authority_fields' => [ 'scopedTenant', ], ], self::SYSTEM_RUNBOOKS => [ 'component_name' => 'System runbooks', 'plane' => 'system_platform', 'route_anchor' => null, 'authority_sources' => [ 'allowed_tenant_universe', 'explicit_scoped_query', ], 'locked_identities' => [], 'locked_identity_fields' => [], 'mutable_selectors' => [ 'findingsTenantId', 'tenantId', 'findingsScopeMode', 'scopeMode', ], 'mutable_selector_fields' => [ $this->field( name: 'findingsTenantId', stateClass: TrustedStateClass::Presentation, phpType: '?int', sourceOfTruth: 'allowed_tenant_universe', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'public ?int $findingsTenantId = null;', 'resolveAllowedOrFail($this->findingsTenantId)', ], notes: 'Single-tenant runbook proposal only; it must be validated against the operator allowed-tenant universe.', ), $this->field( name: 'tenantId', stateClass: TrustedStateClass::Presentation, phpType: '?int', sourceOfTruth: 'allowed_tenant_universe', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ 'public ?int $tenantId = null;', ], notes: 'Mirrored display state for the last trusted preflight result.', ), $this->field( name: 'findingsScopeMode', stateClass: TrustedStateClass::Presentation, phpType: 'string', sourceOfTruth: 'presentation_only', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'public string $findingsScopeMode = FindingsLifecycleBackfillScope::MODE_ALL_TENANTS;', 'trustedFindingsScopeFromState(', ], notes: 'Scope mode remains mutable UI state but protected actions re-normalize it into a trusted scope DTO.', ), $this->field( name: 'scopeMode', stateClass: TrustedStateClass::Presentation, phpType: 'string', sourceOfTruth: 'presentation_only', usedForProtectedAction: false, revalidationRequired: false, implementationMarkers: [ 'public string $scopeMode = FindingsLifecycleBackfillScope::MODE_ALL_TENANTS;', ], notes: 'Mirrored display state for the last trusted preflight result.', ), ], 'server_derived_authority_fields' => [ $this->field( name: 'findingsScope', stateClass: TrustedStateClass::ServerDerivedAuthority, phpType: 'FindingsLifecycleBackfillScope', sourceOfTruth: 'allowed_tenant_universe', usedForProtectedAction: true, revalidationRequired: true, implementationMarkers: [ 'trustedFindingsScopeFromFormData(', 'trustedFindingsScopeFromState(', 'resolveAllowedOrFail(', ], notes: 'Protected actions must convert mutable selector state into a trusted scope DTO via AllowedTenantUniverse.', ), ], 'forbidden_public_authority_fields' => [], ], ]; } /** * @return array{ * component_name: string, * plane: string, * route_anchor: string|null, * authority_sources: list, * locked_identities: list, * locked_identity_fields: list, * notes: string * }>, * mutable_selectors: list, * mutable_selector_fields: list, * notes: string * }>, * server_derived_authority_fields: list, * notes: string * }>, * forbidden_public_authority_fields: list * } */ public function forComponent(string $component): array { $policy = $this->firstSlice()[$component] ?? null; if ($policy === null) { throw new InvalidArgumentException("Unknown trusted-state component [{$component}]."); } return $policy; } /** * @return list */ public function components(): array { return array_keys($this->firstSlice()); } }