fix(review): Pass 1 ranks by catalogue tier, and a duplicate never lowers its group

Review finding bc7f4e23 (library side).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-13 14:24:33 +02:00
commit f04fecea7f
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 134 additions and 45 deletions

View file

@ -463,9 +463,12 @@ test('Handover 6 — names the fields, engine findings only, the one lowering an
// ---- Decision 13 (operator 12.10): the source decides, a tie keeps the reviewer --
//
// "Came from an engine" is read from the payload a finding arrived in, never
// from an `engine` field a reviewer wrote on its own finding; and on a severity
// tie a reviewer-agent finding survives over an engine finding, so an engine's
// mark can never lower what a reviewer agent asserted, whatever the order.
// from an `engine` field a reviewer wrote on its own finding; and Pass 1 ranks
// a triplet's findings by catalogue tier first (for a catalogue key the tier
// always ties inside one triplet), then a reviewer-agent finding over an engine
// finding, and the survivor carries the highest severity its group asserted,
// so an engine's mark can never lower what a reviewer agent asserted, whatever
// the order (review bc7f4e23).
function reviewerCopy(extra = {}) {
const { engine: _e, ...rest } = codeReviewDefect({ title: 'Reviewer: no retry', ...extra });
@ -513,18 +516,36 @@ for (const order of ['reviewer first', 'engine first']) {
});
}
test('decision 13 — an engine MAJOR over a reviewer MINOR on the same triplet still drops to MINOR (the engine wins on severity)', () => {
const result = m.runContract([
{ reviewer: 'code-correctness-reviewer', findings: [reviewerCopy({ rule_key: 'MISSING_TEST', severity: 'MINOR' })] },
{ reviewer: 'claude-code-review', findings: [codeReviewDefect({ rule_key: 'MISSING_TEST', contradicts_brief: NON_GOAL })] },
]);
const out = result.findings[0];
assert.equal(out.engine, 'code-review');
assert.equal(out.severity, 'MINOR');
assert.equal(out.brief_lowered_from, 'MAJOR');
assert.equal(result.verdict, 'ALLOW');
assertAllOutputLegal(result);
});
function minorReviewerAndMarkedEngine(order) {
const reviewerPayload = { reviewer: 'code-correctness-reviewer', findings: [reviewerCopy({ rule_key: 'MISSING_TEST', severity: 'MINOR' })] };
const enginePayload = { reviewer: 'claude-code-review', findings: [codeReviewDefect({ rule_key: 'MISSING_TEST', contradicts_brief: NON_GOAL })] };
return order === 'reviewer first' ? [reviewerPayload, enginePayload] : [enginePayload, reviewerPayload];
}
for (const order of ['reviewer first', 'engine first']) {
test(`bc7f4e23 — a marked /code-review MAJOR over a reviewer MINOR on the same triplet keeps the reviewer at MAJOR (${order}) → WARN`, () => {
const result = m.runContract(minorReviewerAndMarkedEngine(order));
assert.equal(result.findings.length, 1);
const out = result.findings[0];
assert.equal('engine' in out, false, 'the survivor is the reviewer finding');
assert.equal(out.severity, 'MAJOR', 'the group asserted MAJOR; dropping the engine duplicate never lowers it');
assert.equal('brief_lowered_from' in out, false, 'the engine mark never lowers what a reviewer agent asserted');
assert.equal('contradicts_brief' in out, false);
assert.equal(result.verdict, 'WARN');
assertAllOutputLegal(result);
});
test(`bc7f4e23 — dedupByTriplet(ingest(...)) keeps the reviewer finding at the group's MAJOR over a marked /code-review MAJOR (${order})`, () => {
const { findings } = m.ingest(minorReviewerAndMarkedEngine(order));
const out = m.dedupByTriplet(findings);
assert.equal(out.length, 1);
assert.equal('engine' in out[0], false, 'the reviewer-agent finding survives the tier tie');
assert.equal(out[0].reviewer, 'code-correctness-reviewer');
assert.equal(out[0].severity, 'MAJOR', 'the survivor carries the highest severity its group asserted');
assert.equal('contradicts_brief' in out[0], false, 'the engine mark is not carried to the survivor');
assert.deepEqual([...out[0].raised_by].sort(), ['claude-code-review', 'code-correctness-reviewer']);
});
}
test('decision 13 — the prose states the tie rule (coordinator Pass 1, Handover 6)', () => {
const coordinator = readDoc('agents', 'review-coordinator.md');
@ -534,7 +555,34 @@ test('decision 13 — the prose states the tie rule (coordinator Pass 1, Handove
assert.match(pass1, /never\s+lowers\s+what\s+a\s+reviewer\s+agent\s+asserted/);
const h6 = cut(readDoc('docs', 'HANDOVER-CONTRACTS.md'), '## Handover 6 — `review.md` → plan', '\n## Handover 7', 'Handover 6');
assert.match(h6, /takes? `engine` from the payload a finding arrived in/);
assert.match(h6, /on a severity tie[^.]*a reviewer-agent finding survives[^.]*over an engine finding/i);
assert.match(h6, /ranked by catalogue tier[^.]*a reviewer-agent finding survives over an engine finding[^.]*highest severity the group asserted/i,
'Handover 6: Pass 1 ranks by catalogue tier and the survivor carries the group severity (bc7f4e23)');
});
test('b406a628 — coordinator Pass 1 ranks in the library order and writes the group severity (decision 24)', () => {
const coordinator = readDoc('agents', 'review-coordinator.md');
const pass1 = cut(coordinator, '### Pass 1', '### Pass 2', 'coordinator Pass 1');
assert.doesNotMatch(pass1, /highest\s+catalogue\s+severity/, 'Pass 1 no longer keeps the highest catalogue severity');
const ranks = [
/higher\s+catalogue\s+tier/,
/prefer\s+a\s+reviewer-agent\s+finding\s+over\s+an\s+engine\s+finding/,
/higher\s+asserted\s+severity/,
/brief-conformance-reviewer/,
/first\s+seen/,
];
let previous = -1;
for (const rank of ranks) {
const at = pass1.search(rank);
assert.ok(at >= 0, `Pass 1 names the rank ${rank}`);
assert.ok(at > previous, `Pass 1 names the rank ${rank} after the one before it`);
previous = at;
}
assert.match(pass1, /not\s+in\s+the\s+catalogue/, 'Pass 1 names the unknown-key fallback');
assert.match(pass1, /highest\s+severity\s+any\s+finding\s+in\s+its\s+group\s+asserted/,
'Pass 1 writes the group severity');
assert.doesNotMatch(coordinator, /not\s+the\s+reviewer's\s+emitted\s+value/,
'no Hard rule still says the tiebreak ignores the emitted value');
assert.match(coordinator, /Pass\s+1\s+ranks\s+by\s+the\s+catalogue\s+tier\s+first/);
});
// ---- The engine of a payload is read from an own property (review 3799ea6c) --