mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-14 20:37:14 +01:00
fix(review-depth): gate-class Step 11 voices run before any cap stop; --grant runs the owner's fix-check (GST-967)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
1022f4fc51
commit
4cfa424c58
15 files changed
+173
-18
No files matched your search
@@ -70,6 +70,46 @@ describe('nextStep: the transition table', () => {
|
||||
test('a clean fix-check is done', () => expect(nextStep({ fullPasses: 1, last: 'fix-check' }).step).toBe('done'));
|
||||
});
|
||||
|
||||
// Value: protects=on gate class Step 11's adversarial, outside and targeted voices run before any owner exit the round cap reaches, and a recorded owner grant dispatches its one scoped fix-check after round 2, once; fails_when=a gate-class round-2 blocker fix stops before Step 11 (C6-36 after-opus: next=stop, no voice ran), or a recorded grant still stops (C6-36 after-opus-grant: the lane hand-dispatched the fix-check);
|
||||
// why_new=GST-967; seam=nextStep, planReview; no_claim=the voices' or the fix-check's verdicts
|
||||
describe('GST-967: the round cap limits fix rounds, not the gate-class voices; a recorded grant runs its fix-check', () => {
|
||||
const capStop = { fullPasses: FULL_PASS_CAP, last: 'full', fixedBlocking: true } as const;
|
||||
test('a gate-class round-2 blocker fix runs Step 11\'s voices before the owner stop, then stops', () => {
|
||||
expect(nextStep({ ...capStop, gate: true })).toMatchObject({ step: 'voices', reason: expect.stringContaining("Step 11's voices") });
|
||||
for (const o of [{ blockingOpen: true }, { fixedBlocking: false, fixedBelow: true, touchedGate: true }, { fullPasses: 1, last: 'fix-check' as const }, { fullPasses: 1, blockingOpen: true }]) expect(nextStep({ ...capStop, gate: true, ...o }).step).toBe('voices');
|
||||
expect(nextStep({ ...capStop, gate: true, adversarialRan: true }).step).toBe('stop');
|
||||
expect(nextStep(capStop).step).toBe('stop');
|
||||
});
|
||||
test('C6-36 run 1 replanned: the plan lists Step 11\'s three voices, and once they ran the stop stands', () => {
|
||||
const h = { ...capStop, touchedGate: true };
|
||||
const p = planReview(base({ gateTagged: 1, targeted: { classes: ['gate'] }, history: h }));
|
||||
expect([p.class, p.next.step, roles(p)]).toEqual(['gate', 'voices', ['adversarial-native:adversarial:full', 'outside-voice:outside-voice:host', 'targeted:targeted:full']]);
|
||||
expect(planReview(base({ gateTagged: 1, adversarialRan: true, targeted: { classes: ['gate'] }, history: h })).next.step).toBe('stop');
|
||||
});
|
||||
test('a recorded grant dispatches one scoped fix-check after round 2, after the gate-class voices, and never a second', () => {
|
||||
const g = { ...capStop, granted: 'a-feb4aba31633' };
|
||||
expect(nextStep(g)).toMatchObject({ step: 'fix-check', reason: expect.stringContaining('a-feb4aba31633') });
|
||||
expect(roles(planReview(base({ history: g })))).toEqual(['fix-check:fix-check:full']);
|
||||
expect(nextStep({ ...g, gate: true }).step).toBe('voices');
|
||||
expect(nextStep({ ...g, gate: true, adversarialRan: true }).step).toBe('fix-check');
|
||||
expect(nextStep({ ...g, fixedBlocking: false, fixedBelow: true, touchedGate: true }).step).toBe('fix-check');
|
||||
expect(nextStep({ fullPasses: 1, last: 'fix-check', fixedBlocking: true, granted: 'a-1', fixChecks: 1 }).step).toBe('fix-check');
|
||||
expect(nextStep({ ...g, blockingOpen: true }).step).toBe('stop');
|
||||
expect(nextStep({ fullPasses: FULL_PASS_CAP, last: 'fix-check', granted: 'a-1' }).step).toBe('done');
|
||||
expect(nextStep({ fullPasses: FULL_PASS_CAP, last: 'fix-check', fixedBlocking: true, granted: 'a-1' }).step).toBe('stop');
|
||||
expect(nextStep({ fullPasses: 1, last: 'fix-check', fixedBlocking: true, granted: 'a-1', fixChecks: 2 }).step).toBe('stop');
|
||||
});
|
||||
test('docs/review-policy.md\'s transitions table and rule 3 say both', () => {
|
||||
const doc = fs.readFileSync(path.join(ROOT, 'docs', 'review-policy.md'), 'utf8');
|
||||
const table = doc.slice(doc.indexOf('<a id="transitions"></a>'), doc.indexOf('A **fix-check** is one reviewer'));
|
||||
expect(table).toContain("| a stop (any row above) on gate class before Step 11's voices ran | Step 11's voices (adversarial, outside, targeted), then ask again with `--adversarial-ran` |");
|
||||
expect(table).toContain('| a round-2 stop for a fixed blocking finding or a gate-class edit, with the owner\'s recorded grant (`--grant <approval id>`) | one scoped fix-check (round 3), after Step 11 on gate class |');
|
||||
const rule3 = doc.slice(doc.indexOf('3. **Two rounds is the cap**'), doc.indexOf('<a id="transitions"></a>')).replace(/\s+/g, ' ');
|
||||
expect(rule3).toContain("The cap limits fix rounds, not Step 11's voices");
|
||||
expect(rule3).toContain('`gstack-review-plan --grant <approval id>`');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scanNumbers', () => {
|
||||
test('finds claims and fingerprints them', () => {
|
||||
const a = scanNumbers({ 'docs/bench.md': ['Median dropped 48% to 1.2s', 'no numbers here'] });
|
||||
|
||||
@@ -740,6 +740,25 @@ describe('Capy pass program (fake host, real bins)', () => {
|
||||
expect(h.creates.find(c => c.title === 'ship x21p2 red-team')!.prompt).toContain('DIFF_BASE=' + rangeBase);
|
||||
}, 240_000);
|
||||
|
||||
// Value: protects=the pass program dispatches the owner's granted fix-check from the plan, as decision 3.5 runs any fix-check, instead of PASS: none; fails_when=a recorded grant still prints PASS: none next=stop and the lane renders, sends and collects the fix-check by hand (C6-36 after-opus-grant);
|
||||
// why_new=GST-967; seam=the pass program; no_claim=the fix-check reviewer's verdict
|
||||
test('GST-967: a recorded grant after round 2 runs its one scoped fix-check through the program', async () => {
|
||||
const { dir, env } = repo();
|
||||
const add = spawnSync(path.join(BIN, 'gstack-approvals'), ['add', '--ask', 'Owner grant: one scoped fix-check after round 2', '--words', 'one scoped fix-check after round 2', '--by', 'GStack Minder', '--scope', 'this /ship invocation', '--json'], { cwd: dir, env: { ...env, GSTACK_APPROVALS_MIRROR: '0' }, encoding: 'utf8', timeout: 60_000 });
|
||||
expect(add.status, add.stderr).toBe(0);
|
||||
const id = JSON.parse(add.stdout).added[0] as string;
|
||||
const files = path.join(dir, '.state/gfc');
|
||||
fs.mkdirSync(files, { recursive: true });
|
||||
const fc: Record<string, string> = {};
|
||||
for (const [k, v] of [['fix_list', '1. src/a.ts: guard v80\n'], ['hunks', 'diff --git a/src/a.ts b/src/a.ts\n+export const v81 = 81;\n'], ['tests', 'bun test: 3 pass, 0 fail\n'], ['callers', 'none\n']]) { fc[k] = path.join(files, k + '.txt'); fs.writeFileSync(fc[k]!, v!); }
|
||||
const h = host(dir, env);
|
||||
const res = await runPass(h, { base: 'main', out: path.join(dir, '.state/run/gfc'), pass: 'x30gfc', planFlags: ['--pass', '2', '--last', 'full', '--fixed-blocking', '--grant', id], fixCheck: fc, logStart: ['review-fix-check'] });
|
||||
expect(res).not.toContain('PASS: none');
|
||||
expect(res).toContain(`NEXT: fix-check (the owner's grant ${id}`);
|
||||
expect(h.creates.map(c => c.title)).toEqual(['ship x30gfc fix-check']);
|
||||
expect(res).toContain('NEXT: judge (Phase 4)');
|
||||
}, 240_000);
|
||||
|
||||
// Value: protects=the Capy pass program runs a scoped repeat with no fix-check files, dispatches its Red Team and leaves the fix-check to Step 3.5 as GST-619 does on the CLI; fails_when=the program stops a scoped plan for missing files or creates a fix-check reader in the scoped manifest;
|
||||
// why_new=#3670 Step 11 (adversarial 1, red team 1), re-scoped to GST-619's model in the merge of main; seam=none; no_claim=the Step 3.5 fix-check itself
|
||||
test('a scoped repeat runs without fixCheck files and leaves its fix-check to Step 3.5 (GST-619)', async () => {
|
||||
|
||||
@@ -62,6 +62,29 @@ describe('gstack-review-plan', () => {
|
||||
expect(r.stdout).not.toContain('NEXT: stop');
|
||||
expect(run(repo({ 'src/a.ts': 'export const a = 1;\n' }), ['--pass', '1', '--last', 'fix-check', '--fixed-below', '--blocking-open']).stdout).toContain('NEXT: stop');
|
||||
});
|
||||
// Value: protects=a grant the owner recorded with gstack-approvals is something the plan can use: --grant <id> plans the one scoped fix-check after round 2, a fix-check already counted keeps it from repeating, and an id the record does not hold is refused; fails_when=the CLI ignores the grant and prints NEXT: stop (C6-36 after-opus-grant), accepts an unrecorded id, or grants a second fix-check;
|
||||
// why_new=GST-967; seam=bin/gstack-review-plan; no_claim=who may grant (the owner's words are the approval record's)
|
||||
test('GST-967: --grant <id> names a recorded approval and plans its one scoped fix-check after round 2; an unrecorded id is refused', () => {
|
||||
const r = repo({ 'src/a.ts': 'export const a = 1;\n' });
|
||||
const env: NodeJS.ProcessEnv = { ...process.env, GSTACK_STATE_ROOT: r.state, GSTACK_APPROVALS_MIRROR: '0' };
|
||||
delete env.GSTACK_APPROVALS_DIR;
|
||||
const add = spawnSync(path.join(path.dirname(BIN), 'gstack-approvals'), ['add', '--ask', 'Owner grant: one scoped fix-check after round 2', '--words', 'one scoped fix-check after round 2', '--by', 'GStack Minder', '--scope', 'this /ship invocation', '--json'], { cwd: r.dir, encoding: 'utf8', timeout: 60_000, env });
|
||||
expect(add.status, add.stderr).toBe(0);
|
||||
const id = JSON.parse(add.stdout).added[0] as string;
|
||||
const plan = (a: string[]) => spawnSync(BIN, ['--base', 'main', '--gate-tagged', '0', ...a], { cwd: r.dir, encoding: 'utf8', timeout: 60_000, env });
|
||||
const capStop = ['--pass', '2', '--last', 'full', '--fixed-blocking'];
|
||||
expect(plan(capStop).stdout).toContain('NEXT: stop');
|
||||
const granted = plan([...capStop, '--grant', id]);
|
||||
expect(granted.status, granted.stderr).toBe(0);
|
||||
expect(granted.stdout).toContain(`NEXT: fix-check (the owner's grant ${id}`);
|
||||
expect(granted.stdout).toContain('DISPATCH: fix-check fix-check effort=full');
|
||||
expect(plan(['--pass', '2', '--last', 'fix-check', '--fixed-blocking', '--grant', id, '--fix-checks', '1']).stdout).toContain('NEXT: stop');
|
||||
const uncounted = plan(['--pass', '1', '--last', 'fix-check', '--fixed-blocking', '--grant', id]);
|
||||
expect([uncounted.status, uncounted.stderr]).toEqual([2, expect.stringContaining('--fix-checks')]);
|
||||
const unknown = plan([...capStop, '--grant', 'a-000000000000']);
|
||||
expect([unknown.status, unknown.stdout]).toEqual([3, '']);
|
||||
expect(unknown.stderr).toContain('no approved row a-000000000000');
|
||||
});
|
||||
test('--fix-check-prompt renders the fix-check reviewer prompt from the parent\'s files and refuses a missing one', () => {
|
||||
const r = repo({ 'docs/howto.md': '# x\n' });
|
||||
const w = (n: string, t: string) => { const f = path.join(r.state, n); fs.writeFileSync(f, t); return f; };
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"bytes": 417,
|
||||
"why": "GST-967: Step 11 voices before any gate-class stop (NEXT: voices) and the owner's recorded grant (--grant) in the repeat decision and Step 11"
|
||||
}
|
||||
Reference in new issue
Block a user