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 $reasons * @return array */ 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, ], ], ]; }