TenantAtlas/apps/platform/app/Support/Governance/GovernanceSubjectTaxonomyRegistry.php
ahmido 7541b1eb41 Spec 202: implement governance subject taxonomy and baseline scope V2 (#232)
## 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
2026-04-13 15:33:33 +00:00

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';
}
}