221 lines
6.6 KiB
PHP
221 lines
6.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines;
|
|
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
|
|
/**
|
|
* Value object for baseline scope resolution.
|
|
*
|
|
* A scope defines which policy types are included in a baseline profile.
|
|
*
|
|
* Spec 116 semantics:
|
|
* - Empty policy_types means "all supported policy types" (excluding foundations).
|
|
* - Empty foundation_types means "none".
|
|
*/
|
|
final class BaselineScope
|
|
{
|
|
/**
|
|
* @param array<string> $policyTypes
|
|
* @param array<string> $foundationTypes
|
|
*/
|
|
public function __construct(
|
|
public readonly array $policyTypes = [],
|
|
public readonly array $foundationTypes = [],
|
|
) {}
|
|
|
|
/**
|
|
* Create from the scope_jsonb column value.
|
|
*
|
|
* @param array<string, mixed>|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<string>
|
|
*/
|
|
public function allTypes(): array
|
|
{
|
|
$expanded = $this->expandDefaults();
|
|
|
|
return self::uniqueSorted(array_merge(
|
|
$expanded->policyTypes,
|
|
$expanded->foundationTypes,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function toJsonb(): array
|
|
{
|
|
return [
|
|
'policy_types' => $this->policyTypes,
|
|
'foundation_types' => $this->foundationTypes,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Effective scope payload for OperationRun.context.
|
|
*
|
|
* @return array{policy_types: list<string>, foundation_types: list<string>, all_types: list<string>, 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<string>
|
|
*/
|
|
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<string>
|
|
*/
|
|
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<string> $types
|
|
* @return list<string>
|
|
*/
|
|
private static function normalizePolicyTypes(array $types): array
|
|
{
|
|
$supported = self::supportedPolicyTypes();
|
|
|
|
return self::uniqueSorted(array_values(array_intersect($types, $supported)));
|
|
}
|
|
|
|
/**
|
|
* @param array<string> $types
|
|
* @return list<string>
|
|
*/
|
|
private static function normalizeFoundationTypes(array $types): array
|
|
{
|
|
$supported = self::supportedFoundationTypes();
|
|
|
|
return self::uniqueSorted(array_values(array_intersect($types, $supported)));
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $types
|
|
* @return list<string>
|
|
*/
|
|
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;
|
|
}
|
|
}
|