Skip to content

Commit e7b2f27

Browse files
committed
fix(ci): preserve full precision and guard malformed resolution artifacts (#1167)
- scripts/resolution-benchmark.ts: stop rounding precision/recall to 3 decimals before writing the artifact. The rounding let a near-miss like 0.8497 round up to 0.850 and silently clear a 0.85 threshold in CI artifact mode while failing in fixture mode. - tests/benchmarks/resolution/resolution-benchmark.test.ts: validate numeric fields in metricsFromArtifact so a stale or malformed artifact surfaces a clear 'regenerate' error instead of a confusing TypeError at the threshold assertions. - tests/benchmarks/resolution/resolution-benchmark.test.ts: reject an empty artifact in loadArtifact. Without this guard, an empty {} would register zero describe blocks and vitest would exit 0 with '0 tests', silently passing the gate.
1 parent d697929 commit e7b2f27

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

scripts/resolution-benchmark.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,12 @@ function computeMetrics(resolvedEdges: ResolvedEdge[], expectedEdges: ExpectedEd
127127
m.recall = m.expected > 0 ? m.resolved / m.expected : 0;
128128
}
129129

130+
// Keep full precision so the artifact-mode gate compares the exact same
131+
// values the fixture-mode gate would compute. Rounding here let a near-miss
132+
// like 0.8497 round up to 0.850 and silently clear a 0.85 threshold.
130133
return {
131-
precision: Math.round(precision * 1000) / 1000,
132-
recall: Math.round(recall * 1000) / 1000,
134+
precision,
135+
recall,
133136
truePositives: truePositives.size,
134137
falsePositives: falsePositives.size,
135138
falseNegatives: falseNegatives.size,

tests/benchmarks/resolution/resolution-benchmark.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,37 @@ function loadArtifact(artifactPath: string): Record<string, ArtifactLangResult>
291291
`RESOLUTION_RESULT_JSON=${artifactPath} not found — run scripts/resolution-benchmark.ts first.`,
292292
);
293293
}
294-
return JSON.parse(fs.readFileSync(artifactPath, 'utf-8'));
294+
const parsed = JSON.parse(fs.readFileSync(artifactPath, 'utf-8')) as Record<
295+
string,
296+
ArtifactLangResult
297+
>;
298+
// Refuse to proceed on an empty artifact: with zero languages, vitest would
299+
// register no describe blocks and exit 0, silently passing the gate without
300+
// evaluating a single threshold.
301+
if (!parsed || typeof parsed !== 'object' || Object.keys(parsed).length === 0) {
302+
throw new Error(
303+
`RESOLUTION_RESULT_JSON=${artifactPath} contains no language results — regenerate with scripts/resolution-benchmark.ts.`,
304+
);
305+
}
306+
return parsed;
295307
}
296308

297309
function metricsFromArtifact(lang: string, raw: ArtifactLangResult): BenchmarkMetrics {
310+
if (
311+
typeof raw.precision !== 'number' ||
312+
typeof raw.recall !== 'number' ||
313+
typeof raw.truePositives !== 'number' ||
314+
typeof raw.falsePositives !== 'number' ||
315+
typeof raw.falseNegatives !== 'number' ||
316+
typeof raw.totalResolved !== 'number' ||
317+
typeof raw.totalExpected !== 'number' ||
318+
!raw.byMode ||
319+
typeof raw.byMode !== 'object'
320+
) {
321+
throw new Error(
322+
`Resolution artifact for ${lang} is missing required numeric fields — regenerate with the current resolution-benchmark.ts.`,
323+
);
324+
}
298325
if (!Array.isArray(raw.falsePositiveEdges) || !Array.isArray(raw.falseNegativeEdges)) {
299326
throw new Error(
300327
`Resolution artifact for ${lang} is missing falsePositiveEdges/falseNegativeEdges — regenerate with the current resolution-benchmark.ts.`,

0 commit comments

Comments
 (0)