fix(review): the Arm B workflow keeps the contract's row on a trimmed title and a tie
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9145d2bbf5
commit
878871a6ad
4 changed files with 164 additions and 8 deletions
|
|
@ -63,6 +63,14 @@ const normTitle = (f) => String(f.title).trim().toLowerCase();
|
|||
// The row a finding is matched to after the coordinator: its triplet plus its
|
||||
// `normTitle`. An untitled finding gets a key no titled row has.
|
||||
const rowOf = (f) => `${tripletOf(f)}\u0000${f.title === undefined ? '\u0001' : normTitle(f)}`;
|
||||
// The coordinator trims an over-long MINOR/SUGGESTION title as
|
||||
// lib/review/coordinator-contract.mjs judgeFilter does; its marker and limit
|
||||
// are inlined (scripts cannot import) and pinned equal by the workflow test.
|
||||
const TRIM_MARKER = ' […trimmed]';
|
||||
const JUDGE_TITLE_MAX = 100;
|
||||
// A title as that trim writes it: over JUDGE_TITLE_MAX characters, cut to
|
||||
// leave room for the marker, then the marker; any other title unchanged.
|
||||
const trimmedTitle = (t) => (t.length > JUDGE_TITLE_MAX ? t.slice(0, JUDGE_TITLE_MAX - TRIM_MARKER.length) + TRIM_MARKER : t);
|
||||
|
||||
// Findings schema — mirrors lib/review/findings-schema.mjs (NW1). The enum on
|
||||
// rule_key enforces the catalogue at the StructuredOutput layer (stronger than
|
||||
|
|
@ -129,9 +137,10 @@ const VERDICT_SCHEMA = {
|
|||
// order is coordinator-contract Pass 1's (review bc7f4e23): the catalogue tier
|
||||
// first (for a catalogue key it always ties inside one triplet, since the
|
||||
// rule_key is shared), then a reviewer finding (no `engine`) over an engine
|
||||
// finding (decision 13, operator 12.10), then the higher asserted severity;
|
||||
// the workflow has no conformance rule, so otherwise the first in the group's
|
||||
// order stays (the order the two passes below place the members in).
|
||||
// finding (decision 13, operator 12.10), then the higher asserted severity,
|
||||
// then the conformance reviewer (a source whose name holds "conformance", as
|
||||
// the contract's isConformance reads the reviewer name), else the first in the
|
||||
// group's order stays (the order the two passes below place the members in).
|
||||
// The survivor carries the highest severity any finding in its group asserted,
|
||||
// so an engine's mark never lowers what a reviewer agent asserted.
|
||||
// As in coordinator-contract Pass 1, one source never duplicates itself: its
|
||||
|
|
@ -153,6 +162,7 @@ const sameTitle = (a, b) => a.title === undefined || b.title === undefined || no
|
|||
// triplet is one group, as before. A `rows` Map, when given, is filled with
|
||||
// each row written -> the findings of its group (the put-back reads it).
|
||||
const sourceOf = new Map();
|
||||
const isConformance = (f) => String(sourceOf.get(f) ?? '').toLowerCase().includes('conformance');
|
||||
const cmpStr = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
||||
function groupTriplet(entries) {
|
||||
const srcOf = (f) => sourceOf.get(f) ?? null;
|
||||
|
|
@ -213,7 +223,13 @@ function dedupByTriplet(findings, { perSource = true, rows = null } = {}) {
|
|||
if (!ef) survivor = f;
|
||||
continue;
|
||||
}
|
||||
if (sevRank(f.severity) < sevRank(survivor.severity)) survivor = f;
|
||||
const rf = sevRank(f.severity);
|
||||
const rs = sevRank(survivor.severity);
|
||||
if (rf !== rs) {
|
||||
if (rf < rs) survivor = f;
|
||||
continue;
|
||||
}
|
||||
if (isConformance(f) && !isConformance(survivor)) survivor = f;
|
||||
}
|
||||
let severity = survivor.severity;
|
||||
for (const f of group) {
|
||||
|
|
@ -359,7 +375,8 @@ const returned = coordinatorMissing ? merged : coord.findings;
|
|||
// counterpart's severity), and a labelled finding is raised as before.
|
||||
// - the rows' members (before dedup, so an engine finding a reviewer finding
|
||||
// shadowed in its row counts too) for the put-back: (c) each engine finding
|
||||
// in a row whose `rowOf` key no returned finding carries is
|
||||
// in a row whose `rowOf` key no returned finding carries (neither as it
|
||||
// stands nor with the title trimmed as the coordinator trims it) is
|
||||
// put back at its own severity, one copy per row (the higher severity, else
|
||||
// the first seen) (operator decision 23, review ab1b5d9f). So the coordinator's drop,
|
||||
// move, rename or relabel of an engine finding no longer lowers the verdict
|
||||
|
|
@ -427,11 +444,15 @@ for (const f of returned) {
|
|||
}
|
||||
// (c) the put-back (decision 23).
|
||||
const returnedRows = new Set(returned.filter((f) => f && typeof f === 'object').map(rowOf));
|
||||
// A row is returned when a returned finding carries its key, or the key of its
|
||||
// title as the coordinator trims it, so a trimmed title puts back no copy.
|
||||
const rowReturned = (m) => returnedRows.has(rowOf(m)) ||
|
||||
(typeof m.title === 'string' && returnedRows.has(rowOf({ ...m, title: trimmedTitle(m.title) })));
|
||||
// Each row not returned: its engine findings only, so dedupByTriplet keeps the
|
||||
// higher severity, else the first seen: one copy per row, so not split per
|
||||
// source here.
|
||||
const putBack = merged
|
||||
.filter((m) => m && typeof m === 'object' && !returnedRows.has(rowOf(m)))
|
||||
.filter((m) => m && typeof m === 'object' && !rowReturned(m))
|
||||
.flatMap((m) => dedupByTriplet(rowMembers.get(m).filter((f) =>
|
||||
f && typeof f === 'object' && f.engine), { perSource: false }));
|
||||
for (const f of putBack) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue