Added `BaselineSubjectResolution` page and supporting logic to visualize missing identities, ambiguous matches, and skipped coverages as defined in Spec 384. Replaces legacy compare warnings with an actionable, deterministic UI surface. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #455
169 lines
7.2 KiB
PHP
169 lines
7.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Baselines\BaselineSubjectResolutionQuery;
|
|
use App\Support\Baselines\SubjectClass;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\Resources\ResourceIdentity;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\Feature\Baselines\Support\BaselineSubjectResolutionFixtures;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('derives actionable rows and candidates from persisted compare semantics and inventory identities', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
$leftIdentity = ResourceIdentity::providerResource('fake-provider', 'policy', 'candidate-left');
|
|
$rightIdentity = ResourceIdentity::providerResource('fake-provider', 'policy', 'candidate-right');
|
|
|
|
BaselineSubjectResolutionFixtures::inventoryCandidate($tenant, $leftIdentity, 'Duplicate policy');
|
|
BaselineSubjectResolutionFixtures::inventoryCandidate($tenant, $rightIdentity, 'Duplicate policy');
|
|
$leftDescriptor = BaselineSubjectResolutionFixtures::providerDescriptor($leftIdentity, 'Duplicate policy');
|
|
$rightDescriptor = BaselineSubjectResolutionFixtures::providerDescriptor($rightIdentity, 'Duplicate policy');
|
|
|
|
$run = seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $snapshot,
|
|
compareContext: [
|
|
'result_semantics' => [
|
|
'version' => 1,
|
|
'subject_outcomes' => [
|
|
BaselineSubjectResolutionFixtures::semanticOutcome([
|
|
'reason' => 'unresolved_duplicate_candidates',
|
|
'actionability' => 'binding_required',
|
|
'readiness_impact' => 'customer_blocker',
|
|
'subject' => [
|
|
'subject_domain' => 'baseline',
|
|
'subject_class' => SubjectClass::PolicyBacked->value,
|
|
'subject_type_key' => 'deviceConfiguration',
|
|
'subject_key' => 'legacy-display-key',
|
|
'display_label' => 'Duplicate policy',
|
|
'candidate_descriptors' => [
|
|
$leftDescriptor->toArray(),
|
|
$rightDescriptor->toArray(),
|
|
],
|
|
],
|
|
]),
|
|
BaselineSubjectResolutionFixtures::semanticOutcome([
|
|
'reason' => 'verified_no_drift',
|
|
'actionability' => 'none',
|
|
'readiness_impact' => 'no_impact',
|
|
'subject' => [
|
|
'subject_type_key' => 'deviceConfiguration',
|
|
'subject_key' => 'resolved-subject',
|
|
'display_label' => 'Resolved subject',
|
|
],
|
|
]),
|
|
],
|
|
],
|
|
],
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
|
|
$rows = app(BaselineSubjectResolutionQuery::class)->rows($tenant, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
]);
|
|
|
|
expect($rows)->toHaveCount(1)
|
|
->and($rows[0]['reason'])->toBe('unresolved_duplicate_candidates')
|
|
->and($rows[0]['actionability'])->toBe('binding_required')
|
|
->and($rows[0]['candidate_count'])->toBe(2)
|
|
->and($rows[0]['decision_identity'])->toBeNull()
|
|
->and(collect($rows[0]['candidates'])->pluck('identity.provider_resource_id')->all())
|
|
->toContain('candidate-left', 'candidate-right');
|
|
|
|
expect(app(BaselineSubjectResolutionQuery::class)->rows($tenant, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'actionability' => 'binding_required',
|
|
'readiness_impact' => 'customer_blocker',
|
|
'reason' => 'unresolved_duplicate_candidates',
|
|
'resource_type' => 'deviceConfiguration',
|
|
'candidates' => 'yes',
|
|
'active_binding' => 'no',
|
|
]))->toHaveCount(1);
|
|
|
|
$resolvedRows = app(BaselineSubjectResolutionQuery::class)->rows($tenant, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'include_resolved' => true,
|
|
]);
|
|
|
|
expect($resolvedRows)->toHaveCount(2);
|
|
});
|
|
|
|
it('does not derive bindable candidates from display label matches alone', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
BaselineSubjectResolutionFixtures::inventoryCandidate(
|
|
$tenant,
|
|
ResourceIdentity::providerResource('fake-provider', 'policy', 'candidate-left'),
|
|
'Duplicate policy',
|
|
);
|
|
BaselineSubjectResolutionFixtures::inventoryCandidate(
|
|
$tenant,
|
|
ResourceIdentity::providerResource('fake-provider', 'policy', 'candidate-right'),
|
|
'Duplicate policy',
|
|
);
|
|
|
|
$run = seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $snapshot,
|
|
compareContext: [
|
|
'result_semantics' => [
|
|
'version' => 1,
|
|
'subject_outcomes' => [
|
|
BaselineSubjectResolutionFixtures::semanticOutcome([
|
|
'reason' => 'unresolved_duplicate_candidates',
|
|
'actionability' => 'binding_required',
|
|
'readiness_impact' => 'customer_blocker',
|
|
'subject' => [
|
|
'subject_domain' => 'baseline',
|
|
'subject_class' => SubjectClass::PolicyBacked->value,
|
|
'subject_type_key' => 'deviceConfiguration',
|
|
'subject_key' => 'legacy-display-key',
|
|
'display_label' => 'Duplicate policy',
|
|
],
|
|
]),
|
|
],
|
|
],
|
|
],
|
|
status: OperationRunStatus::Completed->value,
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
|
|
$rows = app(BaselineSubjectResolutionQuery::class)->rows($tenant, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
]);
|
|
|
|
expect($rows)->toHaveCount(1)
|
|
->and($rows[0]['candidate_count'])->toBe(0)
|
|
->and($rows[0]['candidates'])->toBe([]);
|
|
});
|
|
|
|
it('does not treat legacy evidence-gap payloads as authoritative subject decisions', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
$run = seedBaselineCompareRun($tenant, $profile, $snapshot, [
|
|
'evidence_gaps' => [
|
|
'count' => 1,
|
|
'by_reason' => ['unresolved_duplicate_candidates' => 1],
|
|
'subjects' => [[
|
|
'policy_type' => 'deviceConfiguration',
|
|
'subject_key' => 'legacy-only',
|
|
'reason_code' => 'unresolved_duplicate_candidates',
|
|
]],
|
|
],
|
|
], outcome: OperationRunOutcome::PartiallySucceeded->value);
|
|
|
|
$query = app(BaselineSubjectResolutionQuery::class);
|
|
|
|
expect($query->rows($tenant, ['operation_run_id' => (int) $run->getKey()]))->toBe([])
|
|
->and($query->summary($tenant, (int) $run->getKey())['legacy_payload_only'])->toBeTrue();
|
|
});
|