fix(review): plan dedup keeps a lowered_from that is not a severity, so rule B counts it as blocker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
30ca7078e9
commit
f70aed5d47
2 changed files with 39 additions and 3 deletions
|
|
@ -20,7 +20,9 @@
|
|||
// top-level `marks: [{agent, id, resolved}]` in input order; a malformed mark
|
||||
// is dropped, never thrown on, and `sources` keeps exactly {agent, present}.
|
||||
// A finding keeps an integer `line_end` ≥ `line`, rule B's `lowered_from`
|
||||
// (normalized) and a non-empty `caught_by`, and carries `rule_keys`: the union
|
||||
// (normalized when it is a severity, absent when blank, and kept as given
|
||||
// otherwise, so rule B refuses it and counts it as blocker) and a non-empty
|
||||
// `caught_by`, and carries `rule_keys`: the union
|
||||
// of the keys merged into it, so a security key survives a merge into a
|
||||
// differently-keyed survivor. Two defects are fixed. A `line` that is not a
|
||||
// finite integer becomes null and an empty `file` or `rule_key` becomes null,
|
||||
|
|
@ -188,8 +190,9 @@ function annotate(finding, agent) {
|
|||
if (line !== null && Number.isInteger(finding.line_end) && finding.line_end >= line) {
|
||||
out.line_end = finding.line_end;
|
||||
}
|
||||
if (finding.lowered_from !== undefined && finding.lowered_from !== null) {
|
||||
out.lowered_from = normalizeSeverity(finding.lowered_from);
|
||||
const lf = finding.lowered_from;
|
||||
if (lf !== undefined && lf !== null && !(typeof lf === 'string' && lf.trim() === '')) {
|
||||
out.lowered_from = normalizeSeverity(finding.lowered_from) ?? finding.lowered_from;
|
||||
}
|
||||
if (typeof finding.caught_by === 'string' && finding.caught_by.trim() !== '') {
|
||||
out.caught_by = finding.caught_by;
|
||||
|
|
|
|||
|
|
@ -1077,3 +1077,36 @@ test('28: the clauses with short texts (fewer than 6 content words): at its plac
|
|||
assert.equal(hit[0].severity, 'major');
|
||||
}
|
||||
});
|
||||
|
||||
// ---- 29 ------------------------------------------------------------------------
|
||||
|
||||
test('29: a lowered_from that is not a severity reaches the gate and counts as blocker; a blank one is no lowering', () => {
|
||||
const p0 = buildPlan();
|
||||
const TEXT = 'Run one never states what it prints when the input file is missing';
|
||||
const one = extra => [f('minor', at(p0, S1_CHANGES), TEXT, { caught_by: 'tests/mod1.test.mjs', ...extra })];
|
||||
|
||||
for (const bad of ['high', 'critical', 3, true]) {
|
||||
const d = dd({ pc: one({ lowered_from: bad }) });
|
||||
assert.equal(d.findings.length, 1);
|
||||
assert.equal(d.findings[0].lowered_from, bad, `dedup keeps lowered_from ${JSON.stringify(bad)} as given`);
|
||||
const r1 = rec(p0, d);
|
||||
const v = r1.verdict;
|
||||
const g0 = byText(v, TEXT);
|
||||
assert.equal(g0.severity, 'blocker', `lowered_from ${JSON.stringify(bad)} counts as blocker`);
|
||||
assert.equal(g0.lowering_refused, 'rule B: lowered_from is not a severity; counted as blocker');
|
||||
assert.equal(v.verdict, 'BLOCK');
|
||||
const g = gatePlan(r1.text);
|
||||
assert.equal(g.code, 1);
|
||||
assert.ok(g.reasons.some(x => x.code === 'PLAN_GATE_OPEN_BLOCKER'), JSON.stringify(g.reasons));
|
||||
}
|
||||
|
||||
for (const blank of ['', ' ']) {
|
||||
const d = dd({ pc: one({ lowered_from: blank }) });
|
||||
assert.equal(d.findings.length, 1);
|
||||
assert.equal('lowered_from' in d.findings[0], false, `a blank lowered_from ${JSON.stringify(blank)} leaves no key`);
|
||||
const v = rec(p0, d).verdict;
|
||||
const g0 = byText(v, TEXT);
|
||||
assert.equal(g0.severity, 'minor');
|
||||
assert.equal(g0.lowering_refused, undefined);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue