Some checks failed
Main Confidence / confidence (push) Failing after 46s
## Summary - implement Spec 211 runtime trend reporting with bounded lane history, drift classification, hotspot trend output, and recalibration evidence handling - extend the repo-truth governance seams and workflow wrappers for comparable-bundle hydration, trend artifact publication, and contract-backed reporting - add the Spec 211 planning artifacts, data model, quickstart, tasks, and repository contract documents ## Validation - parsed `specs/211-runtime-trend-recalibration/contracts/test-runtime-trend-history.schema.json` - parsed `specs/211-runtime-trend-recalibration/contracts/test-runtime-trend.logical.openapi.yaml` - re-ran cross-artifact consistency analysis for the Spec 211 artifact set until no material findings remained - no application test suite was re-run as part of this final commit/push/PR step Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #244
128 lines
5.6 KiB
PHP
128 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Tests\Support\TestLaneBudget;
|
|
use Tests\Support\TestLaneManifest;
|
|
use Tests\Support\TestLaneReport;
|
|
use Tests\Support\TestLaneTrendFixtures;
|
|
|
|
function reportWithSeededHistory(string $laneId, string $suffix, array $seededHistory, float $currentSeconds): array
|
|
{
|
|
$artifactDirectory = TestLaneTrendFixtures::artifactDirectory('trend-recalibration/'.$suffix);
|
|
$durationsByFile = [
|
|
'tests/Feature/Guards/TestLaneRecalibrationEvidenceContractTest.php' => 11.0,
|
|
];
|
|
$workflowId = $laneId === 'confidence' ? 'main-confidence' : 'pr-fast-feedback';
|
|
$triggerClass = $laneId === 'confidence' ? 'mainline-push' : 'pull-request';
|
|
$comparisonProfile = in_array($laneId, ['fast-feedback', 'confidence'], true)
|
|
? 'shared-test-fixture-slimming'
|
|
: null;
|
|
$baseReport = TestLaneTrendFixtures::buildReport(
|
|
laneId: $laneId,
|
|
wallClockSeconds: $seededHistory[0],
|
|
durationsByFile: $durationsByFile,
|
|
artifactDirectory: $artifactDirectory,
|
|
ciContext: [
|
|
'workflowId' => $workflowId,
|
|
'triggerClass' => $triggerClass,
|
|
'entryPointResolved' => true,
|
|
'workflowLaneMatched' => true,
|
|
],
|
|
comparisonProfile: $comparisonProfile,
|
|
);
|
|
|
|
$artifact = $baseReport['trendHistoryArtifact'];
|
|
$templateRecord = $artifact['history'][0];
|
|
$artifact['history'] = array_values(array_map(
|
|
static function (float $seconds, int $index) use ($templateRecord): array {
|
|
$record = $templateRecord;
|
|
$record['runRef'] = sprintf('%s-recalibration-%d', $templateRecord['laneId'], $index + 1);
|
|
$record['generatedAt'] = sprintf('2026-04-%02dT08:30:00+00:00', $index + 1);
|
|
$record['wallClockSeconds'] = round($seconds, 6);
|
|
|
|
return $record;
|
|
},
|
|
$seededHistory,
|
|
array_keys($seededHistory),
|
|
));
|
|
|
|
TestLaneTrendFixtures::writeTrendHistory($laneId, $artifact, $artifactDirectory);
|
|
|
|
return TestLaneTrendFixtures::buildReport(
|
|
laneId: $laneId,
|
|
wallClockSeconds: $currentSeconds,
|
|
durationsByFile: $durationsByFile,
|
|
artifactDirectory: $artifactDirectory,
|
|
ciContext: [
|
|
'workflowId' => $workflowId,
|
|
'triggerClass' => $triggerClass,
|
|
'entryPointResolved' => true,
|
|
'workflowLaneMatched' => true,
|
|
],
|
|
comparisonProfile: $comparisonProfile,
|
|
);
|
|
}
|
|
|
|
it('emits candidate, approved, and rejected recalibration records with explicit summary disclosure', function (): void {
|
|
$candidateReport = reportWithSeededHistory('confidence', 'candidate', [420.0, 380.0, 340.0, 300.0, 260.0], 460.0);
|
|
$approvedReport = reportWithSeededHistory('fast-feedback', 'approved', [176.0, 176.3, 176.1, 176.4, 176.2], 176.3);
|
|
$rejectedReport = reportWithSeededHistory('fast-feedback', 'rejected', [176.0, 191.0, 177.0, 192.0, 178.0], 193.0);
|
|
|
|
$approvedDecision = TestLaneBudget::buildRecalibrationDecisionRecord(
|
|
laneId: 'fast-feedback',
|
|
targetType: 'baseline',
|
|
assessment: ['recalibrationRecommendation' => 'review-baseline'],
|
|
historyRecords: $approvedReport['trendHistoryArtifact']['history'],
|
|
decisionStatus: 'approved',
|
|
rationaleCode: 'post-improvement-reset',
|
|
recordedIn: 'specs/211-runtime-trend-recalibration/spec.md',
|
|
proposedValueSeconds: 182.0,
|
|
notes: 'Approved baseline reset after the suite stabilized following a deliberate improvement pass.',
|
|
);
|
|
|
|
$candidateDecision = $candidateReport['trendRecalibrationDecisions'][0] ?? null;
|
|
$rejectedDecision = $rejectedReport['trendRecalibrationDecisions'][0] ?? null;
|
|
|
|
$approvedReport['trendRecalibrationDecisions'][] = $approvedDecision;
|
|
$approvedReport['trendHistoryArtifact']['recalibrationDecisions'][] = $approvedDecision;
|
|
|
|
TestLaneReport::writeArtifacts(
|
|
laneId: 'confidence',
|
|
report: $candidateReport,
|
|
artifactDirectory: $candidateReport['artifactDirectory'],
|
|
);
|
|
TestLaneReport::writeArtifacts(
|
|
laneId: 'fast-feedback',
|
|
report: $approvedReport,
|
|
artifactDirectory: $approvedReport['artifactDirectory'],
|
|
);
|
|
TestLaneReport::writeArtifacts(
|
|
laneId: 'fast-feedback',
|
|
report: $rejectedReport,
|
|
artifactDirectory: $rejectedReport['artifactDirectory'],
|
|
);
|
|
|
|
$candidateSummary = (string) file_get_contents(TestLaneManifest::absolutePath(
|
|
TestLaneReport::artifactPaths('confidence', $candidateReport['artifactDirectory'])['summary'],
|
|
));
|
|
$approvedSummary = (string) file_get_contents(TestLaneManifest::absolutePath(
|
|
TestLaneReport::artifactPaths('fast-feedback', $approvedReport['artifactDirectory'])['summary'],
|
|
));
|
|
$rejectedSummary = (string) file_get_contents(TestLaneManifest::absolutePath(
|
|
TestLaneReport::artifactPaths('fast-feedback', $rejectedReport['artifactDirectory'])['summary'],
|
|
));
|
|
|
|
expect($candidateDecision)->toBeArray()
|
|
->and($candidateDecision['decisionStatus'])->toBe('candidate')
|
|
->and($candidateDecision['targetType'])->toBe('budget')
|
|
->and($approvedDecision['decisionStatus'])->toBe('approved')
|
|
->and($approvedDecision['targetType'])->toBe('baseline')
|
|
->and($rejectedDecision)->toBeArray()
|
|
->and($rejectedDecision['decisionStatus'])->toBe('rejected')
|
|
->and($rejectedDecision['rationaleCode'])->toBe('noise-rejected')
|
|
->and($candidateSummary)->toContain('Recalibration: budget candidate')
|
|
->and($approvedSummary)->toContain('Recalibration: baseline approved')
|
|
->and($rejectedSummary)->toContain('Recalibration: budget rejected', 'noise-rejected');
|
|
});
|