mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 00:44:07 +01:00
fix(review): a stated Severity must be a severity to lower to, and a native heading keeps same-paragraph fields (GST-1406 outside-voice fixes)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
72b1a1041a
commit
ed44a7c305
4 files changed
+17
-2
No files matched your search
@@ -1,3 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- **Lead judgment can lower a native adversarial finding to the Severity its reader stated beside `Blocks: no` (GST-1406).** When the native reader wrote its `Severity` and `Blocks: no` after prose (`… so the docs are wrong. Severity: Medium. Blocks: no.`), collect kept the heading's FIXABLE as CRITICAL. Judge refused any lowering, so the native record never resolved and /ship's Step 11.5 couldn't bind. Collect still can't tell those fields from another subject's written under the same heading, so it keeps the label. Now `gstack-review-dispatch judge` lets lead judgment lower such a finding to its stated Severity, never below, with a reason. It's recorded as `dismissed`, and the ledger's blocker count follows it. Other readers, a finding that states `Blocks: yes` or no `Blocks`, or a lowering below the stated Severity or with no reason are still refused as downgrades. See docs/review-policy.md.
|
||||
- **Lead judgment can lower a native adversarial finding to the Severity its reader stated beside `Blocks: no` (GST-1406).** When the native reader wrote its `Severity` and `Blocks: no` after prose (`… so the docs are wrong. Severity: Medium. Blocks: no.`), collect kept the heading's FIXABLE as CRITICAL. Judge refused any lowering, so the native record never resolved and /ship's Step 11.5 couldn't bind. Collect still can't tell those fields from another subject's written under the same heading, so it keeps the label. Now `gstack-review-dispatch judge` lets lead judgment lower such a finding to its stated Severity, never below, with a reason. It's recorded as `dismissed`, and the ledger's blocker count follows it. When the native reader writes those fields after prose in the heading's own paragraph, collect now keeps them on the heading's record (at its label) instead of splitting them into a second record. Other readers, a finding that states `Blocks: yes`, no `Blocks` or a Severity that isn't a severity (such as `Severity: FIXABLE`), and a lowering below the stated Severity or with no reason are still refused as downgrades. See docs/review-policy.md.
|
||||
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- **Code a reviewer quotes in a fence no longer turns into findings (GST-1409).** When a reader quoted a probe input or a checklist inside a ``` or ~~~ fence, `gstack-review-dispatch collect` parsed each quoted line. A quoted `### 1. FIXABLE: …` heading or a quoted `Blocks: yes` became a phantom CRITICAL that lead judgment couldn't lower, and the real finding lost its own Severity and Blocks fields. Now a fence that opens inside a finding is that finding's body: its lines collect nothing, and the record notes `(code block: <n> lines)`. A JSON finding line inside a fence still collects, and a fence with no finding open parses as before. Checked against 334 stored reader outputs (668 comparisons): 664 collect the same, and the 4 that change each drop only fenced quotes.
|
||||
@@ -89,7 +89,7 @@ const BLOCKS_FIELD = /(?<![A-Za-z0-9])Blocks\**\s*:\s*\**\s*(yes|no)(?![A-Za-z0-
|
||||
/** A native voice's finding whose reader stated `Blocks: no` and one Severity (GST-1031 ruling A): lead judgment may lower it to that Severity, never below, on record with a reason (GST-1406). */
|
||||
const statedLowering = (reader: string, raw: string, to: number) => {
|
||||
const ls = [...raw.matchAll(SEVERITY_FIELD)].flatMap(m => (m[1] ?? m[2]!).match(FIELD_LABELS)!);
|
||||
return reader.split('/')[0] === 'adversarial-native' && statesNoBlocks(raw) && ls.length === 1 && to >= (rank(ls[0]!) ?? Infinity);
|
||||
return reader.split('/')[0] === 'adversarial-native' && statesNoBlocks(raw) && ls.length === 1 && to >= (rank(ls[0]!) || Infinity);
|
||||
};
|
||||
export const statesNoBlocks = (raw: string) => { if (raw.startsWith('{') || BLOCKS_YES.test(raw)) return false; const said = [...raw.matchAll(BLOCKS_FIELD)].map(m => m[1]!.toLowerCase()); return said.length > 0 && !said.includes('yes'); };
|
||||
/**
|
||||
@@ -243,6 +243,12 @@ export function parseFindings(reader: string, output: string, fixableRaises = tr
|
||||
}
|
||||
const heading = LABEL_HEADING.exec(t);
|
||||
if (heading) { section = fixableRaises ? named(heading[1]!) : heading[1]!; current = null; head = ''; continue; }
|
||||
if (role === 'adversarial-native' && current?.heading && !current.fields.size && !blocks && fieldOf(t) !== undefined && statesNoBlocks(t) && !LEAD_LABEL.test(t) && !LIST_ITEM.test(t) && !HEADING.test(t) && !NUMBERED_HEAD.test(t)) {
|
||||
current.fields.add('severity');
|
||||
current.label = stronger(current.label, label!);
|
||||
current.raw += ` | ${t}`;
|
||||
continue;
|
||||
}
|
||||
if (current?.heading && !current.fields.size && !label && !blocks && !/^#{1,6}\s/.test(t) && !NUMBERED_HEAD.test(t) && !t.startsWith('{')) { current.raw += ` | ${t}`; continue; }
|
||||
if (!indented && HEADING.test(t)) { section = null; current = null; }
|
||||
if (t.startsWith('{')) {
|
||||
|
||||
@@ -116,6 +116,12 @@ describe('lead judgment may lower a native finding to the Severity its reader st
|
||||
expect(run('adversarial-native', OUT.replace('Blocks: no', 'Blocks: yes'), 'Medium', 'r')).toMatchObject({ ok: false, dismissed: 0 });
|
||||
expect(run('adversarial-native', OUT.replace(' Blocks: no, because a reader still reads it.', ''), 'Medium', 'r')).toMatchObject({ ok: false, dismissed: 0 });
|
||||
expect(run('red-team', '**1. Auth bypass lets any user read admin data (CRITICAL).**\n\nThe earlier triage filed it lower. Severity: Low. Blocks: no.\n', 'Low', 'r')).toMatchObject({ ok: false, dismissed: 0 });
|
||||
expect(run('adversarial-native', OUT.replace('Severity: Medium', 'Severity: FIXABLE'), 'Low', 'r')).toMatchObject({ ok: false, dismissed: 0 });
|
||||
});
|
||||
test('fields written in the heading paragraph after prose stay on the heading record, which keeps its label, so it can be lowered', () => {
|
||||
const same = '**1. Title (FIXABLE).** body in lib/x.ts.\nMore prose. Severity: Medium. Blocks: no.\n';
|
||||
expect(run('adversarial-native', same, 'Medium', 'its reader stated Severity: Medium, Blocks: no')).toEqual({ labels: ['CRITICAL'], ok: true, dismissed: 1, blockers: 0 });
|
||||
expect(run('red-team', '**1. Title (CRITICAL).** body in lib/x.ts.\nMore prose. Severity: Low. Blocks: no.\n', 'CRITICAL')).toMatchObject({ labels: ['CRITICAL', 'Low'] });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user