12.0, ], ciContext: [ 'workflowId' => $laneId === 'confidence' ? 'main-confidence' : 'pr-fast-feedback', 'triggerClass' => $laneId === 'confidence' ? 'mainline-push' : 'pull-request', 'entryPointResolved' => true, 'workflowLaneMatched' => true, ], comparisonProfile: in_array($laneId, ['fast-feedback', 'confidence'], true) ? 'shared-test-fixture-slimming' : null, ); $templateRecord = $report['trendHistoryArtifact']['history'][0]; return array_values(array_map( static function (int $index) use ($templateRecord): array { $record = $templateRecord; $record['runRef'] = sprintf('%s-evidence-%d', $templateRecord['laneId'], $index + 1); $record['generatedAt'] = sprintf('2026-04-%02dT10:00:00+00:00', $index + 1); return $record; }, range(0, $count - 1), )); } it('keeps baseline and budget recalibration rules separate and enforces the stronger budget evidence window', function (): void { $assessment = [ 'recalibrationRecommendation' => 'review-baseline', ]; $baselineHistory = recalibrationEvidenceHistory('fast-feedback', 3); $budgetHistory = recalibrationEvidenceHistory('confidence', 5); $baselineDecision = TestLaneBudget::buildRecalibrationDecisionRecord( laneId: 'fast-feedback', targetType: 'baseline', assessment: $assessment, historyRecords: $baselineHistory, decisionStatus: 'approved', rationaleCode: 'lane-scope-change', recordedIn: 'specs/211-runtime-trend-recalibration/spec.md', proposedValueSeconds: 184.0, ); $budgetDecision = TestLaneBudget::buildRecalibrationDecisionRecord( laneId: 'confidence', targetType: 'budget', assessment: ['recalibrationRecommendation' => 'review-budget'], historyRecords: $budgetHistory, decisionStatus: 'approved', rationaleCode: 'sustained-erosion', recordedIn: 'specs/211-runtime-trend-recalibration/spec.md', proposedValueSeconds: 470.0, ); expect($baselineDecision['targetType'])->toBe('baseline') ->and($baselineDecision['decisionStatus'])->toBe('approved') ->and($budgetDecision['targetType'])->toBe('budget') ->and($budgetDecision['decisionStatus'])->toBe('approved') ->and($budgetDecision['evidenceRunRefs'])->toHaveCount(5); expect(static fn () => TestLaneBudget::buildRecalibrationDecisionRecord( laneId: 'confidence', targetType: 'budget', assessment: ['recalibrationRecommendation' => 'review-budget'], historyRecords: recalibrationEvidenceHistory('confidence', 4), decisionStatus: 'approved', rationaleCode: 'sustained-erosion', recordedIn: 'specs/211-runtime-trend-recalibration/spec.md', proposedValueSeconds: 470.0, ))->toThrow(InvalidArgumentException::class); }); it('requires approved versus rejected rationale handling that matches the policy', function (): void { $history = recalibrationEvidenceHistory('fast-feedback', 3); $rejectedDecision = TestLaneBudget::buildRecalibrationDecisionRecord( laneId: 'fast-feedback', targetType: 'budget', assessment: ['recalibrationRecommendation' => 'investigate'], historyRecords: [$history[0]], decisionStatus: 'rejected', rationaleCode: 'noise-rejected', recordedIn: 'specs/211-runtime-trend-recalibration/spec.md', ); expect($rejectedDecision['decisionStatus'])->toBe('rejected') ->and($rejectedDecision['rationaleCode'])->toBe('noise-rejected'); expect(static fn () => TestLaneBudget::buildRecalibrationDecisionRecord( laneId: 'fast-feedback', targetType: 'baseline', assessment: ['recalibrationRecommendation' => 'review-baseline'], historyRecords: $history, decisionStatus: 'approved', rationaleCode: 'sustained-erosion', recordedIn: 'specs/211-runtime-trend-recalibration/spec.md', proposedValueSeconds: 184.0, ))->toThrow(InvalidArgumentException::class); });