fix(review): plan dedup ranks a merged pair by what rule B counts, so reviewer order never lowers a blocker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f70aed5d47
commit
e8ea98747d
4 changed files with 166 additions and 47 deletions
|
|
@ -1110,3 +1110,88 @@ test('29: a lowered_from that is not a severity reaches the gate and counts as b
|
|||
assert.equal(g0.lowering_refused, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- 30 ------------------------------------------------------------------------
|
||||
|
||||
test('30: a merged pair counts the same in either reviewer order, and a blocker claim survives a merge', () => {
|
||||
const p0 = buildPlan();
|
||||
const X = at(p0, S1_CHANGES);
|
||||
const TEXT = 'Step 1 never names the exit code its verify command expects';
|
||||
const CAUGHT = { caught_by: 'tests/mod1.test.mjs' };
|
||||
const cases = [
|
||||
{
|
||||
name: '(a) minor vs a refused blocker lowering',
|
||||
a: f('minor', X, TEXT),
|
||||
b: f('minor', X, TEXT, { lowered_from: 'blocker', ...CAUGHT }),
|
||||
severity: 'blocker', refused: 'rule B never lowers a blocker',
|
||||
},
|
||||
{
|
||||
name: '(b) plain major vs a refused blocker lowering',
|
||||
a: f('major', X, TEXT),
|
||||
b: f('minor', X, TEXT, { lowered_from: 'blocker', ...CAUGHT }),
|
||||
severity: 'blocker', refused: 'rule B never lowers a blocker',
|
||||
},
|
||||
{
|
||||
name: '(c) minor vs a major lowering with no caught_by',
|
||||
a: f('minor', X, TEXT),
|
||||
b: f('minor', X, TEXT, { lowered_from: 'major' }),
|
||||
severity: 'major',
|
||||
},
|
||||
{
|
||||
name: '(d) minor vs a security-keyed major lowering, merged by text',
|
||||
a: f('minor', X, TEXT),
|
||||
b: f('minor', X, TEXT, { rule_key: 'SECURITY_INJECTION', lowered_from: 'major', ...CAUGHT }),
|
||||
severity: 'major', refused: 'rule B never lowers a security finding',
|
||||
},
|
||||
{
|
||||
name: '(e) control: plain major vs a granted major lowering',
|
||||
a: f('major', X, TEXT),
|
||||
b: f('minor', X, TEXT, { lowered_from: 'major', ...CAUGHT }),
|
||||
severity: 'major', refused: undefined,
|
||||
},
|
||||
{
|
||||
name: '(f) minor vs a lowered_from that is not a severity',
|
||||
a: f('minor', X, TEXT),
|
||||
b: f('minor', X, TEXT, { lowered_from: 'high', ...CAUGHT }),
|
||||
severity: 'blocker', refused: 'rule B: lowered_from is not a severity; counted as blocker',
|
||||
},
|
||||
];
|
||||
for (const c of cases) {
|
||||
for (const [order, payload] of [['a first', { pc: [c.a], sg: [c.b] }], ['b first', { pc: [c.b], sg: [c.a] }]]) {
|
||||
const d = dd(payload);
|
||||
assert.equal(d.findings.length, 1, `${c.name}, ${order}: the pair is merged: ${JSON.stringify(d.findings)}`);
|
||||
const v = rec(p0, d).verdict;
|
||||
const g = byText(v, TEXT);
|
||||
assert.equal(g.severity, c.severity, `${c.name}, ${order}: counts ${c.severity}: ${JSON.stringify(g)}`);
|
||||
if ('refused' in c) {
|
||||
assert.equal(g.lowering_refused, c.refused, `${c.name}, ${order}: lowering_refused`);
|
||||
}
|
||||
assert.deepEqual([...g.raised_by].sort(), ['plan-critic', 'scope-guardian'], `${c.name}, ${order}: both raisers kept`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ---- architecture: dedup never imports critic-diff or plan-gate --------------------
|
||||
|
||||
/** Every `from '...'`, `import('...')` and `require('...')` specifier naming critic-diff or plan-gate. */
|
||||
function dedupImportViolations(source) {
|
||||
const re = /\bfrom\s*(['"`])([^'"`]+)\1|\bimport\s*\(\s*(['"`])([^'"`]+)\3\s*\)|\brequire\s*\(\s*(['"`])([^'"`]+)\5\s*\)/g;
|
||||
const out = [];
|
||||
for (const m of String(source).matchAll(re)) {
|
||||
const spec = m[2] ?? m[4] ?? m[6];
|
||||
if (/(^|\/)(critic-diff|plan-gate)\.mjs$/.test(spec)) out.push(spec);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
test('architecture: plan-review-dedup imports neither critic-diff nor plan-gate (planted imports caught)', () => {
|
||||
const DEDUP_SRC = join(ROOT, 'lib', 'review', 'plan-review-dedup.mjs');
|
||||
assert.ok(existsSync(DEDUP_SRC), 'lib/review/plan-review-dedup.mjs exists');
|
||||
const src = readFileSync(DEDUP_SRC, 'utf-8');
|
||||
assert.deepEqual(dedupImportViolations(src), [], 'the real source passes');
|
||||
const plantedDiff = `${src}\nimport { countRound } from './critic-diff.mjs';\n`;
|
||||
assert.deepEqual(dedupImportViolations(plantedDiff), ['./critic-diff.mjs'], 'a planted critic-diff import fails');
|
||||
const plantedGate = `${src}\nawait import('../validators/plan-gate.mjs');\n`;
|
||||
assert.deepEqual(dedupImportViolations(plantedGate), ['../validators/plan-gate.mjs'], 'a planted plan-gate import fails');
|
||||
assert.deepEqual(dedupImportViolations(src), [], 'the real source passes again');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue