Skip to content

Commit f82c155

Browse files
committed
fix(miner): sanitize reviewer consensus ingestion
1 parent 44aa54c commit f82c155

2 files changed

Lines changed: 186 additions & 1 deletion

File tree

packages/gittensory-engine/src/reviewer-consensus-calibration.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,86 @@ function isReviewerConsensusCalibrationIngestion(value: unknown): value is Revie
306306
return isRecord(value) && Array.isArray(value.accepted) && Array.isArray(value.rejected);
307307
}
308308

309+
function sanitizeReviewerConsensusCalibrationIngestion(
310+
ingestion: ReviewerConsensusCalibrationIngestion,
311+
): ReviewerConsensusCalibrationIngestion {
312+
const accepted: ReviewerConsensusCalibrationSignal[] = [];
313+
const rejected: ReviewerConsensusCalibrationIngestion["rejected"] = [];
314+
315+
for (const signal of ingestion.accepted) {
316+
if (!isRecord(signal) || !Array.isArray(signal.dimensions)) continue;
317+
const repoFullName = typeof signal.repoFullName === "string" ? normalizeRepoFullName(signal.repoFullName) : null;
318+
const replayRunId = typeof signal.replayRunId === "string" ? normalizeId(signal.replayRunId) : null;
319+
const reviewRunId = typeof signal.reviewRunId === "string" ? normalizeId(signal.reviewRunId) : null;
320+
if (!repoFullName || !replayRunId || !reviewRunId) continue;
321+
const dimensions = signal.dimensions.flatMap((dimension): ReviewerConsensusDimensionSignal[] => {
322+
if (
323+
!isRecord(dimension) ||
324+
typeof dimension.dimension !== "string" ||
325+
typeof dimension.voteCount !== "number" ||
326+
typeof dimension.majorityOutcome !== "string" ||
327+
typeof dimension.agreement !== "number"
328+
) {
329+
return [];
330+
}
331+
const normalizedDimension = normalizeDimension(dimension.dimension);
332+
const majorityOutcome = normalizeVote(dimension.majorityOutcome);
333+
if (
334+
!normalizedDimension ||
335+
!majorityOutcome ||
336+
!Number.isFinite(dimension.voteCount) ||
337+
dimension.voteCount <= 0 ||
338+
!Number.isInteger(dimension.voteCount) ||
339+
!Number.isFinite(dimension.agreement)
340+
) {
341+
return [];
342+
}
343+
const agreement = roundScore(dimension.agreement);
344+
return [
345+
{
346+
dimension: normalizedDimension,
347+
voteCount: dimension.voteCount,
348+
majorityOutcome,
349+
agreement,
350+
score: agreement,
351+
},
352+
];
353+
});
354+
const score = scoreDimensions(dimensions);
355+
if (dimensions.length === 0 || score === null) continue;
356+
accepted.push({
357+
repoFullName,
358+
replayRunId,
359+
reviewRunId,
360+
observedAt: typeof signal.observedAt === "string" ? normalizeObservedAt(signal.observedAt) : null,
361+
dimensions,
362+
score,
363+
});
364+
}
365+
366+
for (const row of ingestion.rejected) {
367+
if (!isRecord(row)) continue;
368+
const repoFullName =
369+
typeof row.repoFullName === "string"
370+
? (normalizeRepoFullName(row.repoFullName) ?? normalizeId(row.repoFullName))
371+
: null;
372+
const replayRunId = typeof row.replayRunId === "string" ? normalizeId(row.replayRunId) : null;
373+
const reviewRunId = typeof row.reviewRunId === "string" ? normalizeId(row.reviewRunId) : null;
374+
const reason = row.reason;
375+
if (
376+
!repoFullName ||
377+
!replayRunId ||
378+
!reviewRunId ||
379+
!["not_opted_in", "empty_dimensions", "invalid_repo", "invalid_run_id"].includes(reason as string)
380+
) {
381+
continue;
382+
}
383+
rejected.push({ repoFullName, replayRunId, reviewRunId, reason });
384+
}
385+
386+
return { accepted, rejected };
387+
}
388+
309389
function normalizeCompositeWeights(weights: ReviewerConsensusCalibrationWeights | undefined): {
310390
objectiveAnchor: number;
311391
pairwiseJudge: number;
@@ -472,7 +552,7 @@ export function computeReviewerConsensusCompositeCalibrationScore(input: {
472552
weights?: ReviewerConsensusCalibrationWeights | undefined;
473553
}): ReviewerConsensusCompositeCalibrationScore {
474554
const ingestion = isReviewerConsensusCalibrationIngestion(input.reviewerConsensus)
475-
? input.reviewerConsensus
555+
? sanitizeReviewerConsensusCalibrationIngestion(input.reviewerConsensus)
476556
: ingestReviewerConsensusCalibrationSignals(input.reviewerConsensus);
477557
const objectiveAnchorScore =
478558
typeof input.objectiveAnchor === "number" ? roundScore(input.objectiveAnchor) : input.objectiveAnchor.score;

packages/gittensory-engine/test/reviewer-consensus-calibration.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,111 @@ test("composite honors custom weights and falls back to objective-only when all
309309
assert.equal(allZero.compositeScore, 0.4);
310310
});
311311

312+
test("composite sanitizes pre-ingested reviewer-consensus rows before auditing", () => {
313+
const poisoned = {
314+
accepted: [
315+
{
316+
repoFullName: "ACME/Widgets",
317+
replayRunId: " replay-1 ",
318+
reviewRunId: "review-1",
319+
observedAt: "2026-07-04T00:00:00Z",
320+
score: 0,
321+
privateMetadata: "do-not-leak",
322+
dimensions: [
323+
{
324+
dimension: "coverage",
325+
voteCount: 2,
326+
majorityOutcome: "success",
327+
agreement: 1,
328+
score: 0,
329+
rawReviewText: "do-not-leak",
330+
},
331+
{
332+
dimension: "security",
333+
voteCount: 2,
334+
majorityOutcome: "fail",
335+
agreement: "not-a-number",
336+
rawReviewText: "do-not-leak",
337+
},
338+
],
339+
},
340+
],
341+
rejected: [
342+
{
343+
repoFullName: "ACME/Widgets",
344+
replayRunId: "replay-2",
345+
reviewRunId: "review-2",
346+
reason: "not_opted_in",
347+
privateMetadata: "do-not-leak",
348+
},
349+
{
350+
repoFullName: "bad",
351+
replayRunId: "replay-3",
352+
reviewRunId: "review-3",
353+
reason: "invalid_repo",
354+
privateMetadata: "do-not-leak",
355+
},
356+
{
357+
repoFullName: "ACME/Widgets",
358+
replayRunId: "replay-4",
359+
reviewRunId: "review-4",
360+
reason: "private_reason",
361+
privateMetadata: "do-not-leak",
362+
},
363+
],
364+
};
365+
366+
const result = computeReviewerConsensusCompositeCalibrationScore({
367+
objectiveAnchor: 0.5,
368+
pairwise: null,
369+
reviewerConsensus: poisoned as never,
370+
});
371+
372+
assert.equal(result.structuredReviewerConsensusScore, 1);
373+
assert.deepEqual(result.audit.contributingRepos, [
374+
{
375+
repoFullName: "acme/widgets",
376+
replayRunId: "replay-1",
377+
reviewRunId: "review-1",
378+
observedAt: "2026-07-04T00:00:00.000Z",
379+
score: 1,
380+
dimensions: [{ dimension: "tests", voteCount: 2, majorityOutcome: "pass", agreement: 1, score: 1 }],
381+
},
382+
]);
383+
assert.deepEqual(result.audit.rejected, [
384+
{ repoFullName: "acme/widgets", replayRunId: "replay-2", reviewRunId: "review-2", reason: "not_opted_in" },
385+
{ repoFullName: "bad", replayRunId: "replay-3", reviewRunId: "review-3", reason: "invalid_repo" },
386+
]);
387+
assert.ok(!JSON.stringify(result).includes("do-not-leak"));
388+
});
389+
390+
test("composite drops malformed pre-ingested rows instead of rendering invalid dimensions", () => {
391+
const result = computeReviewerConsensusCompositeCalibrationScore({
392+
objectiveAnchor: 0.5,
393+
pairwise: 0.7,
394+
reviewerConsensus: {
395+
accepted: [
396+
{
397+
repoFullName: "acme/widgets",
398+
replayRunId: "replay-1",
399+
reviewRunId: "review-1",
400+
observedAt: null,
401+
score: 1,
402+
dimensions: [
403+
{ dimension: "correctness", voteCount: 1, majorityOutcome: "pass", agreement: "1", score: 1 },
404+
],
405+
},
406+
],
407+
rejected: [{ repoFullName: "acme/widgets", replayRunId: "replay-2", reviewRunId: "review-2" }],
408+
} as never,
409+
});
410+
411+
assert.equal(result.structuredReviewerConsensusScore, null);
412+
assert.deepEqual(result.audit.contributingRepos, []);
413+
assert.deepEqual(result.audit.rejected, []);
414+
assert.doesNotThrow(() => renderReviewerConsensusCalibrationAuditMarkdown(result));
415+
});
416+
312417
test("renderAuditMarkdown is deterministic, public-safe, and reports contributors and rejections", () => {
313418
const ingestion = ingestReviewerConsensusCalibrationSignals([
314419
signal({ repoFullName: "acme/widgets", observedAt: "2026-07-04T00:00:00Z" }),

0 commit comments

Comments
 (0)