## Summary - introduce the governance subject taxonomy registry and canonical Baseline Scope V2 normalization and persistence - update baseline profile Filament surfaces, validation, capture/compare gating, and add the optional scope backfill command with audit logging - add focused unit, feature, Filament, and browser smoke coverage for save-forward behavior, operation truth, authorization continuity, and invalid-scope rendering - remove the duplicate legacy spec plan under `specs/001-governance-subject-taxonomy/plan.md` ## Verification - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec202GovernanceSubjectTaxonomySmokeTest.php` - focused Spec 202 regression pack: `56 passed (300 assertions)` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` ## Notes - no schema migration required - no new Filament asset registration required - branch includes the final browser smoke test coverage for the current feature Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #232
211 lines
7.6 KiB
PHP
211 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Governance;
|
|
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
|
|
final class GovernanceSubjectTaxonomyRegistry
|
|
{
|
|
/**
|
|
* @var array<string, list<string>>
|
|
*/
|
|
private const DOMAIN_CLASSES = [
|
|
GovernanceDomainKey::Intune->value => [GovernanceSubjectClass::Policy->value],
|
|
GovernanceDomainKey::PlatformFoundation->value => [GovernanceSubjectClass::ConfigurationResource->value],
|
|
GovernanceDomainKey::Entra->value => [GovernanceSubjectClass::Control->value],
|
|
];
|
|
|
|
/**
|
|
* @return list<GovernanceSubjectType>
|
|
*/
|
|
public function all(): array
|
|
{
|
|
return array_values(array_merge(
|
|
$this->policySubjectTypes(),
|
|
$this->foundationSubjectTypes(),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return list<GovernanceSubjectType>
|
|
*/
|
|
public function active(): array
|
|
{
|
|
return array_values(array_filter(
|
|
$this->all(),
|
|
static fn (GovernanceSubjectType $subjectType): bool => $subjectType->active,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
public function activeLegacyBucketKeys(string $legacyBucket): array
|
|
{
|
|
$subjectTypes = array_filter(
|
|
$this->active(),
|
|
static fn (GovernanceSubjectType $subjectType): bool => $subjectType->legacyBucket === $legacyBucket,
|
|
);
|
|
|
|
$keys = array_map(
|
|
static fn (GovernanceSubjectType $subjectType): string => $subjectType->subjectTypeKey,
|
|
$subjectTypes,
|
|
);
|
|
|
|
sort($keys, SORT_STRING);
|
|
|
|
return array_values(array_unique($keys));
|
|
}
|
|
|
|
public function find(string $domainKey, string $subjectTypeKey): ?GovernanceSubjectType
|
|
{
|
|
foreach ($this->all() as $subjectType) {
|
|
if ($subjectType->domainKey->value !== trim($domainKey)) {
|
|
continue;
|
|
}
|
|
|
|
if ($subjectType->subjectTypeKey !== trim($subjectTypeKey)) {
|
|
continue;
|
|
}
|
|
|
|
return $subjectType;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function isKnownDomain(string $domainKey): bool
|
|
{
|
|
return array_key_exists(trim($domainKey), self::DOMAIN_CLASSES);
|
|
}
|
|
|
|
public function allowsSubjectClass(string $domainKey, string $subjectClass): bool
|
|
{
|
|
$domainKey = trim($domainKey);
|
|
$subjectClass = trim($subjectClass);
|
|
|
|
return in_array($subjectClass, self::DOMAIN_CLASSES[$domainKey] ?? [], true);
|
|
}
|
|
|
|
public function supportsFilters(string $domainKey, string $subjectClass): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function groupLabel(string $domainKey, string $subjectClass): string
|
|
{
|
|
return match ([trim($domainKey), trim($subjectClass)]) {
|
|
[GovernanceDomainKey::Intune->value, GovernanceSubjectClass::Policy->value] => 'Intune policies',
|
|
[GovernanceDomainKey::PlatformFoundation->value, GovernanceSubjectClass::ConfigurationResource->value] => 'Platform foundation configuration resources',
|
|
[GovernanceDomainKey::Entra->value, GovernanceSubjectClass::Control->value] => 'Entra controls',
|
|
default => trim($domainKey).' / '.trim($subjectClass),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return list<GovernanceSubjectType>
|
|
*/
|
|
private function policySubjectTypes(): array
|
|
{
|
|
return array_values(array_map(
|
|
function (array $row): GovernanceSubjectType {
|
|
$type = (string) ($row['type'] ?? '');
|
|
$label = (string) ($row['label'] ?? $type);
|
|
$category = is_string($row['category'] ?? null) ? (string) $row['category'] : null;
|
|
$platform = is_string($row['platform'] ?? null) ? (string) $row['platform'] : null;
|
|
$contract = InventoryPolicyTypeMeta::baselineSupportContract($type);
|
|
|
|
return new GovernanceSubjectType(
|
|
domainKey: GovernanceDomainKey::Intune,
|
|
subjectClass: GovernanceSubjectClass::Policy,
|
|
subjectTypeKey: $type,
|
|
label: $label,
|
|
description: $this->descriptionFor($category, $platform),
|
|
captureSupported: in_array($contract['capture_capability'] ?? null, ['supported', 'limited'], true),
|
|
compareSupported: in_array($contract['compare_capability'] ?? null, ['supported', 'limited'], true),
|
|
inventorySupported: true,
|
|
active: true,
|
|
supportMode: $this->supportModeForContract($contract),
|
|
legacyBucket: 'policy_types',
|
|
);
|
|
},
|
|
array_values(array_filter(
|
|
InventoryPolicyTypeMeta::supported(),
|
|
static fn (array $row): bool => filled($row['type'] ?? null),
|
|
)),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return list<GovernanceSubjectType>
|
|
*/
|
|
private function foundationSubjectTypes(): array
|
|
{
|
|
return array_values(array_map(
|
|
function (array $row): GovernanceSubjectType {
|
|
$type = (string) ($row['type'] ?? '');
|
|
$label = InventoryPolicyTypeMeta::baselineCompareLabel($type) ?? (string) ($row['label'] ?? $type);
|
|
$category = is_string($row['category'] ?? null) ? (string) $row['category'] : null;
|
|
$platform = is_string($row['platform'] ?? null) ? (string) $row['platform'] : null;
|
|
$contract = InventoryPolicyTypeMeta::baselineSupportContract($type);
|
|
$supported = (bool) data_get($row, 'baseline_compare.supported', false);
|
|
|
|
return new GovernanceSubjectType(
|
|
domainKey: GovernanceDomainKey::PlatformFoundation,
|
|
subjectClass: GovernanceSubjectClass::ConfigurationResource,
|
|
subjectTypeKey: $type,
|
|
label: $label,
|
|
description: $this->descriptionFor($category, $platform),
|
|
captureSupported: in_array($contract['capture_capability'] ?? null, ['supported', 'limited'], true),
|
|
compareSupported: in_array($contract['compare_capability'] ?? null, ['supported', 'limited'], true),
|
|
inventorySupported: in_array($contract['source_model_expected'] ?? null, ['inventory', 'policy'], true),
|
|
active: $supported,
|
|
supportMode: $this->supportModeForContract($contract),
|
|
legacyBucket: 'foundation_types',
|
|
);
|
|
},
|
|
array_values(array_filter(
|
|
InventoryPolicyTypeMeta::foundations(),
|
|
static fn (array $row): bool => filled($row['type'] ?? null),
|
|
)),
|
|
));
|
|
}
|
|
|
|
private function descriptionFor(?string $category, ?string $platform): ?string
|
|
{
|
|
$parts = array_values(array_filter([$category, $platform], static fn (?string $part): bool => filled($part)));
|
|
|
|
if ($parts === []) {
|
|
return null;
|
|
}
|
|
|
|
return implode(' | ', $parts);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $contract
|
|
*/
|
|
private function supportModeForContract(array $contract): string
|
|
{
|
|
$capabilities = [
|
|
(string) ($contract['capture_capability'] ?? 'unsupported'),
|
|
(string) ($contract['compare_capability'] ?? 'unsupported'),
|
|
];
|
|
|
|
if (! (bool) ($contract['runtime_valid'] ?? false) && (bool) ($contract['config_supported'] ?? false)) {
|
|
return 'invalid_support_config';
|
|
}
|
|
|
|
if (in_array('supported', $capabilities, true)) {
|
|
return 'supported';
|
|
}
|
|
|
|
if (in_array('limited', $capabilities, true)) {
|
|
return 'limited';
|
|
}
|
|
|
|
return 'excluded';
|
|
}
|
|
} |