fix(review): the workflow floor and put-back match each finding to its own row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a5fcb5e8b4
commit
773aa44dc9
2 changed files with 194 additions and 30 deletions
|
|
@ -483,10 +483,11 @@ test('armB brief rule (15) — a tie between a reviewer MAJOR and a /code-review
|
|||
});
|
||||
|
||||
// ---- Decision 23 (operator 13.10, review ab1b5d9f): no engine finding leaves ----
|
||||
// the workflow review unreturned. Every engine finding of the input (before
|
||||
// dedup) with no returned finding at its exact (file, line, rule_key) is put
|
||||
// back at its own severity; a returned finding is never below the lower of its
|
||||
// reviewer counterpart's severity and its catalogue tier.
|
||||
// the workflow review unreturned. Each engine finding in a row (its triplet
|
||||
// (file, line, rule_key) plus its normalized title) that no returned finding
|
||||
// carries is put back at its own severity, one copy per row; a returned finding
|
||||
// is never below the lower of its reviewer counterpart's severity and its
|
||||
// catalogue tier.
|
||||
|
||||
const at = (findings, file, line, rule_key) =>
|
||||
findings.filter((f) => f && f.file === file && f.line === line && f.rule_key === rule_key);
|
||||
|
|
@ -559,7 +560,7 @@ test('decision 23 — a /code-review MAJOR dropped → WARN', async () => {
|
|||
});
|
||||
|
||||
for (const order of ['MAJOR first', 'BLOCKER first']) {
|
||||
test(`decision 23 — two /code-review findings on one triplet (${order}), returned empty → BLOCK, one copy, at BLOCKER`, async () => {
|
||||
test(`decision 23 — two /code-review findings on one triplet (${order}), returned empty → BLOCK, both rows put back, the BLOCKER row present`, async () => {
|
||||
const pair = [F('CODE_REVIEW_DEFECT', 'MAJOR', { title: 'major' }), F('CODE_REVIEW_DEFECT', 'BLOCKER', { title: 'blocker' })];
|
||||
const c = await floorCase({
|
||||
codeFindings: order === 'MAJOR first' ? pair : [pair[1], pair[0]],
|
||||
|
|
@ -568,10 +569,14 @@ for (const order of ['MAJOR first', 'BLOCKER first']) {
|
|||
});
|
||||
assert.equal(c.r.verdict, 'BLOCK');
|
||||
const back = at(c.r.findings, FILE, LINE, 'CODE_REVIEW_DEFECT');
|
||||
assert.equal(back.length, 1, 'exactly one finding at the triplet');
|
||||
assert.equal(back[0].severity, 'BLOCKER', 'the higher-severity copy is put back');
|
||||
assert.equal(back[0].title, 'blocker');
|
||||
assert.equal(back[0].engine, 'code-review');
|
||||
assert.equal(back.length, 2, 'one source under two titles is two rows, and each is put back');
|
||||
const got = back
|
||||
.map((f) => ({ title: f.title, severity: f.severity, engine: f.engine }))
|
||||
.sort((x, y) => x.title.localeCompare(y.title));
|
||||
assert.deepEqual(got, [
|
||||
{ title: 'blocker', severity: 'BLOCKER', engine: 'code-review' },
|
||||
{ title: 'major', severity: 'MAJOR', engine: 'code-review' },
|
||||
], 'each row is put back at its own severity');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -698,3 +703,128 @@ test('[ORD] sibling: workflow — the title classes are placed in alphabetical o
|
|||
], `engine order ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- WF: the floor and the put-back match each returned finding to its own row ----
|
||||
//
|
||||
// A row is a merged finding; its key is its triplet plus its normalized title.
|
||||
// A returned finding is floored against the row with its own key; with no title
|
||||
// match, against the only row on its triplet, else the strictest one there,
|
||||
// never a lower one. Every row whose key was not returned has its engine
|
||||
// findings put back at their own severity.
|
||||
|
||||
test('[WF] two /code-review MAJORs A and B on one triplet: A returned legally lowered with ALLOW → WARN, B put back at MAJOR', async () => {
|
||||
const K = 'CODE_REVIEW_DEFECT';
|
||||
const c = await floorCase({
|
||||
codeFindings: [F(K, 'MAJOR', { title: 'A' }), F(K, 'MAJOR', { title: 'B' })],
|
||||
returned: [F(K, 'MINOR', { title: 'A', contradicts_brief: MARK, brief_lowered_from: 'MAJOR' })],
|
||||
verdict: 'ALLOW',
|
||||
});
|
||||
assert.equal(c.merged.length, 2, 'one source under two titles is two rows');
|
||||
assert.equal(c.r.verdict, 'WARN');
|
||||
assert.equal(c.r.findings[0].severity, 'MINOR', 'the legal lowering of A stands');
|
||||
assert.equal(c.r.findings[0].engine, 'code-review');
|
||||
const b = at(c.r.findings, FILE, LINE, K).filter((f) => f.title === 'B');
|
||||
assert.equal(b.length, 1, 'B is put back');
|
||||
assert.equal(b[0].severity, 'MAJOR');
|
||||
assert.equal(b[0].engine, 'code-review');
|
||||
});
|
||||
|
||||
test('[WF] two /code-review MAJORs A and B on one triplet: A returned at MAJOR with WARN → B present at MAJOR', async () => {
|
||||
const K = 'CODE_REVIEW_DEFECT';
|
||||
const c = await floorCase({
|
||||
codeFindings: [F(K, 'MAJOR', { title: 'A' }), F(K, 'MAJOR', { title: 'B' })],
|
||||
returned: [F(K, 'MAJOR', { title: 'A' })],
|
||||
verdict: 'WARN',
|
||||
});
|
||||
assert.equal(c.r.verdict, 'WARN');
|
||||
const titles = at(c.r.findings, FILE, LINE, K).map((f) => `${f.title}:${f.severity}`).sort();
|
||||
assert.deepEqual(titles, ['A:MAJOR', 'B:MAJOR'], 'B is present beside the returned A');
|
||||
});
|
||||
|
||||
test('[WF] a reviewer MAJOR returned as MINOR next to engine rows is floored against its own row and keeps engine unset', async () => {
|
||||
const K = 'CODE_REVIEW_DEFECT';
|
||||
const c = await floorCase({
|
||||
reviewerFindings: [F(K, 'MAJOR', { title: 'R' })],
|
||||
codeFindings: [F(K, 'MINOR', { title: 'E1' }), F(K, 'MINOR', { title: 'E2' })],
|
||||
returned: [F(K, 'MINOR', { title: 'R' })],
|
||||
verdict: 'ALLOW',
|
||||
});
|
||||
// Rows: {E1, R} kept as the reviewer's R at MAJOR, and {E2}, an engine row at MINOR.
|
||||
const rows = c.merged.map((m) => `${m.title}:${m.severity}:${m.engine ?? 'none'}`).sort();
|
||||
assert.deepEqual(rows, ['E2:MINOR:code-review', 'R:MAJOR:none']);
|
||||
assert.equal(c.r.findings[0].title, 'R');
|
||||
assert.equal(c.r.findings[0].severity, 'MAJOR', 'floored against its own row, not the engine row E2');
|
||||
assert.equal(c.r.findings[0].engine, undefined, 'engine comes from its own row only');
|
||||
assert.equal(c.r.verdict, 'WARN');
|
||||
const e2 = at(c.r.findings, FILE, LINE, K).filter((f) => f.title === 'E2');
|
||||
assert.equal(e2.length, 1, 'the engine row E2 was not returned and is put back');
|
||||
assert.equal(e2[0].severity, 'MINOR');
|
||||
});
|
||||
|
||||
test('[WF] a returned finding whose title matches no row is floored against the strictest row on its triplet, never a lower one', async () => {
|
||||
const K = 'CODE_REVIEW_DEFECT';
|
||||
const blocker = () => F(K, 'BLOCKER', { title: 'B' });
|
||||
const minor = () => F(K, 'MINOR', { title: 'A' });
|
||||
// Both input orders: the strictest row is first in one and last in the other.
|
||||
for (const [name, codeFindings] of [['BLOCKER first', [blocker(), minor()]], ['BLOCKER last', [minor(), blocker()]]]) {
|
||||
const c = await floorCase({
|
||||
codeFindings,
|
||||
returned: [F(K, 'MINOR', { title: 'renamed' })],
|
||||
verdict: 'ALLOW',
|
||||
});
|
||||
assert.equal(c.r.findings[0].title, 'renamed', name);
|
||||
assert.equal(c.r.findings[0].severity, 'BLOCKER', `the strictest row on the triplet, not the first or last one (${name})`);
|
||||
assert.equal(c.r.verdict, 'BLOCK', name);
|
||||
}
|
||||
});
|
||||
|
||||
// Sample titles, each pair with whether the title rule calls them the same.
|
||||
const TITLE_PAIRS = [
|
||||
['Foo', 'Foo', true],
|
||||
['Foo', 'foo', true],
|
||||
[' Foo bar ', 'foo BAR', true],
|
||||
['Foo\t', 'FOO', true],
|
||||
['Foo', 'Bar', false],
|
||||
['foo bar', 'foo bar', false],
|
||||
['foo-bar', 'foo bar', false],
|
||||
];
|
||||
|
||||
test('[WF] the row key reads titles as sameTitle does: on sample titles a returned finding matches a row exactly where the grouping calls the titles the same', async () => {
|
||||
const K = 'CODE_REVIEW_DEFECT';
|
||||
for (const [a, b, same] of TITLE_PAIRS) {
|
||||
const name = `${JSON.stringify(a)} / ${JSON.stringify(b)}`;
|
||||
// sameTitle in the grouping: one source under titles a and b is one row exactly when they are the same.
|
||||
const g = await floorCase({
|
||||
codeFindings: [F(K, 'MAJOR', { title: a }), F(K, 'MAJOR', { title: b })],
|
||||
returned: [F(K, 'MAJOR', { title: a })],
|
||||
verdict: 'WARN',
|
||||
});
|
||||
assert.equal(g.merged.length, same ? 1 : 2, `grouping, ${name}`);
|
||||
// The row key: a row titled a, returned titled b, is matched (nothing put back) exactly when they are the same.
|
||||
const k = await floorCase({
|
||||
codeFindings: [F(K, 'MAJOR', { title: a })],
|
||||
returned: [F(K, 'MAJOR', { title: b })],
|
||||
verdict: 'WARN',
|
||||
});
|
||||
assert.equal(at(k.r.findings, FILE, LINE, K).length, same ? 1 : 2, `row key, ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Architecture rule: the workflow script imports nothing (it runs inside the Workflow tool) ----
|
||||
|
||||
// Every module specifier a source text imports, statically or dynamically.
|
||||
function importSpecifiers(text) {
|
||||
const found = [];
|
||||
for (const m of text.matchAll(/^\s*import\s*(?:[\w*{][^;]*?\sfrom\s*)?['"]([^'"]+)['"]/gm)) found.push(m[1]);
|
||||
for (const m of text.matchAll(/\bimport\s*\(\s*['"`]([^'"`]+)['"`]/g)) found.push(m[1]);
|
||||
return found;
|
||||
}
|
||||
|
||||
test('arch: scripts/trekreview-armB.workflow.mjs holds no import statement, and the scan reports a planted import (control)', () => {
|
||||
const raw = readFileSync(join(ROOT, 'scripts', 'trekreview-armB.workflow.mjs'), 'utf-8');
|
||||
assert.deepEqual(importSpecifiers(raw), []);
|
||||
const planted = importSpecifiers(`${raw}\nimport { sameTitle } from '../lib/review/coordinator-contract.mjs';\n`);
|
||||
assert.deepEqual(planted, ['../lib/review/coordinator-contract.mjs'], 'control: the scan does not report a planted import');
|
||||
const dynamic = importSpecifiers(`${raw}\nconst m = await import('node:fs');\n`);
|
||||
assert.deepEqual(dynamic, ['node:fs'], 'control: the scan does not report a planted dynamic import');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue