fix(review): the payload decides the engine, and a tie keeps the reviewer finding
An engine's brief-conflict mark could lower a reviewer agent's MAJOR: ingest kept an engine field a reviewer wrote on its own finding, and on a severity tie the survivor depended on payload order (engine first gave ALLOW, reviewer first WARN). ingest now takes engine from the payload source; Pass 1 and the workflow dedup keep the reviewer-agent finding on a tie. Prose (coordinator Pass 1, Handover 6) follows. Decision 13, operator 12.10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
11315d04eb
commit
305ac9284f
6 changed files with 140 additions and 12 deletions
|
|
@ -98,6 +98,15 @@ export const UNVERIFIED_REASONS = Object.freeze([
|
|||
|
||||
const ENGINE_NAMES = Object.freeze(['code-review', 'security-review']);
|
||||
|
||||
// The payload `reviewer` names lib/review/engine-findings.mjs writes, and the
|
||||
// engine each one stands for.
|
||||
const ENGINE_OF_PAYLOAD = Object.freeze({
|
||||
'claude-code-review': 'code-review',
|
||||
'claude-security-review': 'security-review',
|
||||
});
|
||||
|
||||
const isEngineFinding = (f) => ENGINE_NAMES.includes(f.engine);
|
||||
|
||||
function carries(f, field) {
|
||||
return f?.[field] !== undefined && f?.[field] !== null;
|
||||
}
|
||||
|
|
@ -214,8 +223,18 @@ export function ingest(reviewerPayloads) {
|
|||
skipped.push({ reviewer: payload?.reviewer ?? null, error_codes: r.errors.map((e) => e.code) });
|
||||
continue;
|
||||
}
|
||||
// "Came from an engine" is read from the payload, never from an `engine`
|
||||
// field a reviewer wrote on its own finding (decision 13, operator 12.10):
|
||||
// an engine payload's findings get `engine` from its `reviewer`, every other
|
||||
// payload's findings lose any `engine` they carry.
|
||||
const eng = ENGINE_OF_PAYLOAD[payload.reviewer];
|
||||
for (const f of payload.findings) {
|
||||
findings.push({ ...f, reviewer: f.reviewer ?? payload.reviewer ?? f.owner_reviewer ?? null });
|
||||
const { engine: _claimed, ...rest } = f;
|
||||
findings.push({
|
||||
...rest,
|
||||
...(eng ? { engine: eng } : {}),
|
||||
reviewer: f.reviewer ?? payload.reviewer ?? f.owner_reviewer ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
return { findings, skipped };
|
||||
|
|
@ -223,7 +242,8 @@ export function ingest(reviewerPayloads) {
|
|||
|
||||
/**
|
||||
* Pass 1 — dedup by (file, line, rule_key) triplet. Survivor = highest
|
||||
* catalogue severity; severity tie → prefer the conformance reviewer; carries
|
||||
* catalogue severity; severity tie → prefer a reviewer-agent finding over an
|
||||
* engine finding, then the conformance reviewer; carries
|
||||
* raised_by provenance, concatenates other reviewers' attribution into detail,
|
||||
* and recomputes the id over the triplet.
|
||||
* @param {object[]} findings
|
||||
|
|
@ -240,15 +260,20 @@ export function dedupByTriplet(findings) {
|
|||
for (const group of groups.values()) {
|
||||
// A brief-conflict mark is not carried over from a group member to the
|
||||
// survivor: when the higher-severity survivor is unmarked, the group stays
|
||||
// unmarked and is judged at its full severity. Conservative on purpose —
|
||||
// a mark can only lower, so dropping it never makes a verdict greener.
|
||||
// unmarked and is judged at its full severity. On a severity tie a
|
||||
// reviewer-agent finding survives over an engine finding (decision 13,
|
||||
// operator 12.10), so an engine's mark never lowers what a reviewer agent
|
||||
// asserted, whatever the payload order; among findings of one kind the
|
||||
// conformance reviewer wins the tie, else the first-seen one stays.
|
||||
let survivor = group[0];
|
||||
for (const f of group.slice(1)) {
|
||||
const higher = severityRank(f.severity) < severityRank(survivor.severity);
|
||||
const rf = severityRank(f.severity);
|
||||
const rs = severityRank(survivor.severity);
|
||||
const tieToReviewer = rf === rs && !isEngineFinding(f) && isEngineFinding(survivor);
|
||||
const tieToConformance =
|
||||
severityRank(f.severity) === severityRank(survivor.severity) &&
|
||||
rf === rs && isEngineFinding(f) === isEngineFinding(survivor) &&
|
||||
isConformance(f.reviewer) && !isConformance(survivor.reviewer);
|
||||
if (higher || tieToConformance) survivor = f;
|
||||
if (rf < rs || tieToReviewer || tieToConformance) survivor = f;
|
||||
}
|
||||
const raised_by = [...new Set(group.map((f) => f.reviewer).filter(Boolean))];
|
||||
const others = group.filter((f) => f !== survivor);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue