Added `BaselineReadinessGate`, resolution propagation, and disclosure semantics logic per Spec 385. Integrates baseline unreadiness into Customer Review Workspace and Review Packs to prevent report generation when identity bindings are unresolved. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #456
126 lines
5.8 KiB
PHP
126 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Support\Baselines\CompareSemantics\CompareResultReason;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\OperationRunOutcome;
|
|
|
|
it('blocks environment review publication when baseline subject identity is unresolved', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $baselineSnapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $baselineSnapshot,
|
|
compareContext: spec385EnvironmentCompareContext([CompareResultReason::UnresolvedAmbiguousIdentity]),
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
$baselineSection = $review->sections->firstWhere('section_key', 'baseline_drift_posture');
|
|
|
|
expect($baselineSection->completeness_state)->toBe(EnvironmentReviewCompletenessState::Partial->value)
|
|
->and($baselineSection->summary_payload['baseline_readiness']['readiness_state'])->toBe('baseline_identity_unresolved')
|
|
->and($baselineSection->summary_payload['publication_blockers'])->not->toBeEmpty()
|
|
->and($review->status)->toBe(EnvironmentReviewStatus::Draft->value)
|
|
->and($review->publishBlockers())->toContain('Baseline drift posture: Baseline subject identity must be resolved before customer-ready publication.');
|
|
});
|
|
|
|
it('keeps trusted baseline drift complete with findings and publication-ready', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $baselineSnapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $baselineSnapshot,
|
|
compareContext: spec385EnvironmentCompareContext([CompareResultReason::VerifiedDriftDetected]),
|
|
);
|
|
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 1);
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
|
|
expect($review->status)->toBe(EnvironmentReviewStatus::Ready->value)
|
|
->and($review->publishBlockers())->toBeEmpty()
|
|
->and($review->summary['baseline_readiness']['customer_safe_claim'])->toBe('customer_ready_with_findings')
|
|
->and($review->summary['baseline_readiness']['publication_blockers'])->toBe([]);
|
|
});
|
|
|
|
it('carries accepted baseline limitations without blocking publication', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $baselineSnapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $baselineSnapshot,
|
|
compareContext: spec385EnvironmentCompareContext([CompareResultReason::AcceptedLimitation]),
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 1, driftCount: 0);
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
|
|
expect($review->publishBlockers())->toBeEmpty()
|
|
->and($review->status)->toBe(EnvironmentReviewStatus::Ready->value)
|
|
->and($review->summary['baseline_readiness']['state'])->toBe(EnvironmentReviewCompletenessState::Partial->value)
|
|
->and($review->summary['baseline_readiness']['limitation_codes'])->toBe(['baseline_accepted_limitations'])
|
|
->and($review->summary['baseline_readiness']['publication_blockers'])->toBe([]);
|
|
});
|
|
|
|
it('turns missing baseline local evidence into refresh guidance and a review blocker', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
[$profile, $baselineSnapshot] = seedActiveBaselineForTenant($tenant);
|
|
|
|
seedBaselineCompareRun(
|
|
tenant: $tenant,
|
|
profile: $profile,
|
|
snapshot: $baselineSnapshot,
|
|
compareContext: spec385EnvironmentCompareContext([CompareResultReason::MissingLocalEvidence]),
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 1, driftCount: 0);
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
$baselineSection = $review->sections->firstWhere('section_key', 'baseline_drift_posture');
|
|
|
|
expect($review->status)->toBe(EnvironmentReviewStatus::Draft->value)
|
|
->and($review->summary['baseline_readiness']['readiness_state'])->toBe('baseline_local_evidence_missing')
|
|
->and($review->summary['baseline_readiness']['next_action'])->toBe('open_evidence_basis')
|
|
->and($review->publishBlockers())->toContain('Baseline drift posture: Baseline local evidence is missing and must be refreshed before publication.')
|
|
->and($baselineSection->render_payload['next_actions'])->toContain('Refresh baseline compare evidence before relying on the review output.');
|
|
});
|
|
|
|
/**
|
|
* @param list<CompareResultReason> $reasons
|
|
* @return array<string, mixed>
|
|
*/
|
|
function spec385EnvironmentCompareContext(array $reasons): array
|
|
{
|
|
$byReason = [];
|
|
$byReadinessImpact = [];
|
|
|
|
foreach ($reasons as $reason) {
|
|
$byReason[$reason->value] = ($byReason[$reason->value] ?? 0) + 1;
|
|
|
|
$impact = $reason->readinessImpact()->value;
|
|
$byReadinessImpact[$impact] = ($byReadinessImpact[$impact] ?? 0) + 1;
|
|
}
|
|
|
|
return [
|
|
'result_semantics' => [
|
|
'version' => 'compare_semantics.v1',
|
|
'run_outcome' => 'completed',
|
|
'operation_outcome' => OperationRunOutcome::Succeeded->value,
|
|
'counts' => [
|
|
'by_reason' => $byReason,
|
|
'by_readiness_impact' => $byReadinessImpact,
|
|
],
|
|
],
|
|
];
|
|
}
|