TenantAtlas/apps/platform/app/Support/Baselines/BaselineSubjectKey.php
ahmido 788efee1c2 feat(baselines): implement baseline matching canonicalization (#453)
Replaced legacy tenant and environment bindings in the BaselineDriftEngine with the new ProviderResourceIdentity framework as defined in Spec 382. This ensures cross-environment compatibility and deterministic baseline matching.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #453
2026-06-15 22:48:48 +00:00

109 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Baselines;
use App\Support\Resources\ResourceIdentity;
final class BaselineSubjectKey
{
public static function workspaceSafeSubjectExternalId(string $policyType, string $subjectKey): string
{
return hash('sha256', $policyType.'|'.$subjectKey);
}
public static function forProviderResourceIdentity(
string $subjectDomain,
SubjectClass|string $subjectClass,
string $subjectTypeKey,
ResourceIdentity $identity,
): ?string {
$domain = self::canonicalSegment($subjectDomain);
$class = self::canonicalSegment($subjectClass instanceof SubjectClass ? $subjectClass->value : $subjectClass);
$type = self::canonicalSegment($subjectTypeKey);
$provider = self::canonicalSegment($identity->providerKey);
$resourceType = self::canonicalSegment($identity->providerResourceType ?? 'none');
$stableIdentity = $identity->stableIdentityValue();
if ($domain === null || $class === null || $type === null || $provider === null || $resourceType === null || $stableIdentity === null) {
return null;
}
return implode(':', [
'provider-resource',
'v1',
$domain,
$class,
$type,
$provider,
$resourceType,
$identity->identityKind,
hash('sha256', $stableIdentity),
]);
}
public static function fromProviderResourceIdentity(
string $subjectDomain,
SubjectClass|string $subjectClass,
string $subjectTypeKey,
string $providerKey,
?string $providerResourceType,
string $stableIdentity,
string $identityKind = 'provider_resource',
): ?string {
return self::forProviderResourceIdentity(
$subjectDomain,
$subjectClass,
$subjectTypeKey,
new ResourceIdentity(
providerKey: $providerKey,
identityKind: $identityKind,
providerResourceType: $providerResourceType,
providerResourceId: $identityKind === 'provider_resource' ? $stableIdentity : null,
canonicalDiscriminator: $identityKind === 'provider_resource' ? null : $stableIdentity,
),
);
}
public static function isProviderResourceCanonicalKey(?string $subjectKey): bool
{
if (! is_string($subjectKey) || trim($subjectKey) === '') {
return false;
}
$parts = explode(':', trim($subjectKey));
if (count($parts) !== 9) {
return false;
}
[$prefix, $version, $domain, $class, $type, $provider, $resourceType, $identityKind, $hash] = $parts;
if ($prefix !== 'provider-resource' || $version !== 'v1') {
return false;
}
foreach ([$domain, $class, $type, $provider, $resourceType, $identityKind] as $segment) {
if (! is_string($segment) || ! preg_match('/^[a-z0-9._-]+$/', $segment)) {
return false;
}
}
return preg_match('/^[a-f0-9]{64}$/', $hash) === 1;
}
private static function canonicalSegment(?string $value): ?string
{
if (! is_string($value)) {
return null;
}
$normalized = trim(mb_strtolower($value));
$normalized = preg_replace('/[^a-z0-9._-]+/', '-', $normalized);
$normalized = is_string($normalized) ? trim($normalized, '-') : null;
return $normalized !== null && $normalized !== '' ? $normalized : null;
}
}