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
222 lines
6.0 KiB
PHP
222 lines
6.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines\Matching;
|
|
|
|
use App\Support\Resources\ProviderResourceDescriptor;
|
|
use Illuminate\Contracts\Support\Arrayable;
|
|
use JsonSerializable;
|
|
|
|
/**
|
|
* @implements Arrayable<string, mixed>
|
|
*/
|
|
final readonly class MatchingOutcome implements Arrayable, JsonSerializable
|
|
{
|
|
public const string Resolved = 'resolved';
|
|
|
|
public const string Ambiguous = 'ambiguous';
|
|
|
|
public const string MissingProviderResource = 'missing_provider_resource';
|
|
|
|
public const string MissingLocalEvidence = 'missing_local_evidence';
|
|
|
|
public const string UnresolvedIdentity = 'unresolved_identity';
|
|
|
|
public const string Unsupported = 'unsupported';
|
|
|
|
public const string Limited = 'limited';
|
|
|
|
public const string Excluded = 'excluded';
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public function __construct(
|
|
public string $status,
|
|
public string $reasonCode,
|
|
public BaselineSubjectDescriptor $subject,
|
|
public ?ProviderResourceDescriptor $matchedDescriptor = null,
|
|
public ?string $matchedSubjectKey = null,
|
|
public string $trust = 'none',
|
|
public array $proof = [],
|
|
) {}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function resolved(
|
|
BaselineSubjectDescriptor $subject,
|
|
ProviderResourceDescriptor $matchedDescriptor,
|
|
string $matchedSubjectKey,
|
|
string $reasonCode,
|
|
string $trust,
|
|
array $proof = [],
|
|
): self {
|
|
return new self(
|
|
status: self::Resolved,
|
|
reasonCode: $reasonCode,
|
|
subject: $subject,
|
|
matchedDescriptor: $matchedDescriptor,
|
|
matchedSubjectKey: $matchedSubjectKey,
|
|
trust: $trust,
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function ambiguous(BaselineSubjectDescriptor $subject, array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::Ambiguous,
|
|
reasonCode: 'ambiguous_match',
|
|
subject: $subject,
|
|
trust: 'none',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function missingLocalEvidence(BaselineSubjectDescriptor $subject, array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::MissingLocalEvidence,
|
|
reasonCode: 'missing_local_evidence',
|
|
subject: $subject,
|
|
trust: 'none',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function missingProviderResource(BaselineSubjectDescriptor $subject, array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::MissingProviderResource,
|
|
reasonCode: 'missing_provider_resource',
|
|
subject: $subject,
|
|
trust: 'none',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function unresolvedIdentity(BaselineSubjectDescriptor $subject, array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::UnresolvedIdentity,
|
|
reasonCode: 'identity_required',
|
|
subject: $subject,
|
|
trust: 'none',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function unsupported(BaselineSubjectDescriptor $subject, array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::Unsupported,
|
|
reasonCode: 'unsupported_subject',
|
|
subject: $subject,
|
|
trust: 'none',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function limited(BaselineSubjectDescriptor $subject, string $reasonCode = 'accepted_limitation', array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::Limited,
|
|
reasonCode: $reasonCode,
|
|
subject: $subject,
|
|
trust: 'limited',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public static function excluded(BaselineSubjectDescriptor $subject, array $proof = []): self
|
|
{
|
|
return new self(
|
|
status: self::Excluded,
|
|
reasonCode: 'excluded_non_governed',
|
|
subject: $subject,
|
|
trust: 'none',
|
|
proof: $proof,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string|int|float|bool|null> $proof
|
|
*/
|
|
public function isComparable(): bool
|
|
{
|
|
return $this->status === self::Resolved;
|
|
}
|
|
|
|
public function isGap(): bool
|
|
{
|
|
return ! $this->isComparable();
|
|
}
|
|
|
|
public function requiresWarning(): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'status' => $this->status,
|
|
'reason_code' => $this->reasonCode,
|
|
'subject' => $this->subject->toArray(),
|
|
'matched_descriptor' => $this->matchedDescriptor?->toArray(),
|
|
'matched_subject_key' => $this->matchedSubjectKey,
|
|
'trust' => $this->trust,
|
|
'proof' => $this->safeProof($this->proof),
|
|
];
|
|
}
|
|
|
|
public function jsonSerialize(): array
|
|
{
|
|
return $this->toArray();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $proof
|
|
* @return array<string, string|int|float|bool|null>
|
|
*/
|
|
private function safeProof(array $proof): array
|
|
{
|
|
$safe = [];
|
|
|
|
foreach ($proof as $key => $value) {
|
|
if (! is_string($key) || trim($key) === '' || (! is_scalar($value) && $value !== null)) {
|
|
continue;
|
|
}
|
|
|
|
$safe[$key] = $value;
|
|
}
|
|
|
|
ksort($safe);
|
|
|
|
return $safe;
|
|
}
|
|
}
|