$policyTypes * @param array $foundationTypes */ public function __construct( public readonly array $policyTypes = [], public readonly array $foundationTypes = [], ) {} /** * Create from the scope_jsonb column value. * * @param array|null $scopeJsonb */ public static function fromJsonb(?array $scopeJsonb): self { if ($scopeJsonb === null) { return new self; } $policyTypes = $scopeJsonb['policy_types'] ?? []; $foundationTypes = $scopeJsonb['foundation_types'] ?? []; $policyTypes = is_array($policyTypes) ? array_values(array_filter($policyTypes, 'is_string')) : []; $foundationTypes = is_array($foundationTypes) ? array_values(array_filter($foundationTypes, 'is_string')) : []; return new self( policyTypes: $policyTypes === [] ? [] : self::normalizePolicyTypes($policyTypes), foundationTypes: self::normalizeFoundationTypes($foundationTypes), ); } /** * Normalize the effective scope by intersecting profile scope with an optional override. * * Override can only narrow the profile scope (subset enforcement). * Empty override means "no override". */ public static function effective(self $profileScope, ?self $overrideScope): self { $profileScope = $profileScope->expandDefaults(); if ($overrideScope === null || $overrideScope->isEmpty()) { return $profileScope; } $overridePolicyTypes = self::normalizePolicyTypes($overrideScope->policyTypes); $overrideFoundationTypes = self::normalizeFoundationTypes($overrideScope->foundationTypes); $effectivePolicyTypes = $overridePolicyTypes !== [] ? array_values(array_intersect($profileScope->policyTypes, $overridePolicyTypes)) : $profileScope->policyTypes; $effectiveFoundationTypes = $overrideFoundationTypes !== [] ? array_values(array_intersect($profileScope->foundationTypes, $overrideFoundationTypes)) : $profileScope->foundationTypes; return new self( policyTypes: self::uniqueSorted($effectivePolicyTypes), foundationTypes: self::uniqueSorted($effectiveFoundationTypes), ); } /** * An empty scope means "no override" (for override_scope semantics). */ public function isEmpty(): bool { return $this->policyTypes === [] && $this->foundationTypes === []; } /** * Apply Spec 116 defaults and filter to supported types. */ public function expandDefaults(): self { $policyTypes = $this->policyTypes === [] ? self::supportedPolicyTypes() : self::normalizePolicyTypes($this->policyTypes); $foundationTypes = self::normalizeFoundationTypes($this->foundationTypes); return new self( policyTypes: $policyTypes, foundationTypes: $foundationTypes, ); } /** * @return list */ public function allTypes(): array { $expanded = $this->expandDefaults(); return self::uniqueSorted(array_merge( $expanded->policyTypes, $expanded->foundationTypes, )); } /** * @return array */ public function toJsonb(): array { return [ 'policy_types' => $this->policyTypes, 'foundation_types' => $this->foundationTypes, ]; } /** * Effective scope payload for OperationRun.context. * * @return array{policy_types: list, foundation_types: list, all_types: list, foundations_included: bool} */ public function toEffectiveScopeContext(): array { $expanded = $this->expandDefaults(); $allTypes = self::uniqueSorted(array_merge($expanded->policyTypes, $expanded->foundationTypes)); return [ 'policy_types' => $expanded->policyTypes, 'foundation_types' => $expanded->foundationTypes, 'all_types' => $allTypes, 'foundations_included' => $expanded->foundationTypes !== [], ]; } /** * @return list */ private static function supportedPolicyTypes(): array { $supported = config('tenantpilot.supported_policy_types', []); if (! is_array($supported)) { return []; } $types = collect($supported) ->filter(fn (mixed $row): bool => is_array($row) && filled($row['type'] ?? null)) ->map(fn (array $row): string => (string) $row['type']) ->filter(fn (string $type): bool => $type !== '') ->values() ->all(); return self::uniqueSorted($types); } /** * @return list */ private static function supportedFoundationTypes(): array { $types = collect(InventoryPolicyTypeMeta::baselineSupportedFoundations()) ->filter(fn (mixed $row): bool => is_array($row) && filled($row['type'] ?? null)) ->map(fn (array $row): string => (string) $row['type']) ->filter(fn (string $type): bool => $type !== '') ->values() ->all(); return self::uniqueSorted($types); } /** * @param array $types * @return list */ private static function normalizePolicyTypes(array $types): array { $supported = self::supportedPolicyTypes(); return self::uniqueSorted(array_values(array_intersect($types, $supported))); } /** * @param array $types * @return list */ private static function normalizeFoundationTypes(array $types): array { $supported = self::supportedFoundationTypes(); return self::uniqueSorted(array_values(array_intersect($types, $supported))); } /** * @param array $types * @return list */ private static function uniqueSorted(array $types): array { $types = array_values(array_unique(array_filter($types, fn (mixed $type): bool => is_string($type) && $type !== ''))); sort($types, SORT_STRING); return $types; } }