Files
gstack/test/qa-browser-deadline-evidence.test.ts
T
Garry Tan dcaea52800 v1.91.7.0 feat: add functional QA and pre-publication docs checks (#2983)
* feat: add surface-aware exploratory QA and ship documentation gates

* test: preserve delegated QA setup authority after main integration

* fix(qa): clarify exploration order and preserve report artifacts

* test(qa): follow the shared setup reference directly

* refactor(ship): make verification and recovery routes explicit

* test(ship): align evidence and review guards with explicit routes

* fix(workflows): clarify ship recovery and functional QA evidence

* fix(workflows): clarify approval recovery and full QA coverage

* refactor(workflows): order review transactions and clarify ship state

* fix(ship): clarify final verification and fail closed at publication

* fix(evals): attribute native atomic documentation writes

* fix(ship): clarify recovery and documentation lifecycle guidance

* fix(test): preserve observed native placeholder styling in CI

* fix(codex): report watchdog timeouts without a process-exit race

* Checkpoint functional QA implementation and workflow validation repairs

* Fix documentation and shared-review fixture contracts

* docs: clarify judge reuse and evaluation supervision

* test: align review evidence and selected case contracts

* test: verify append-only documentation checkpoints and recovery

* fix: qualify QA workflows and CI validation repairs

* fix: launch shared-libs fixture scripts on Windows

* fix: qualify QA deadlines, fixture isolation, and shard cleanup

* fix: preserve qualified QA and cancellation repairs

* fix: enforce functional fixture authority and share strict event decoding

* fix: retain free-test evidence and explain recovery

* fix: reject malformed native evidence after decoder consolidation

* test: use reliable capture for telemetry privacy filters

* test: refresh measured quick coverage and document validation costs

* Fix native fixture receipts and preserve VM validation evidence

* Align negative judge controls with upstream clarity policy

* Fix report-only QA preparation and public evidence handling

* Clarify QA-only preparation and current-report preservation

* Stream Ship quality judgments with an explicit 64k response contract

* Validate compact judge reasoning locally with supported wire schema

* Align functional QA fixture instructions with evidence acceptance

* Bind native browser diagnostics to execution evidence and align review verdicts

* Preserve native diagnostic line boundaries

* Serialize functional QA evidence from native captures

* Keep large QA evidence fixture payload out of Windows argv
2026-09-29 06:07:35 -07:00

276 lines
18 KiB
TypeScript

import { afterEach, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { spawnSync } from 'node:child_process';
import { assertQaBrowserDeadline, assertQaBrowserPreparation, assertQaBrowserCheckpoints, qaDeadlineShellPolicy } from './helpers/qa-browser-deadline-evidence';
import capturedPreparation from './fixtures/qa-only-charter-public.json';
import capturedObservation from './fixtures/qa-only-observation-public.json';
const ROOT = path.resolve(import.meta.dir, '..');
const guard = path.join(ROOT, 'bin/gstack-qa-deadline');
const browse = process.execPath;
const directories: string[] = [];
const quote = (value: string) => `'${value.replaceAll("'", `'"'"'`)}'`;
afterEach(() => { for (const directory of directories.splice(0)) fs.rmSync(directory, { recursive: true, force: true }); });
function fixture(expectedBudgetMs = 30000) {
const started = Date.now();
const directory = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'qa-gate-'));
directories.push(directory);
fs.mkdirSync(path.join(directory, 'qa/sections'), { recursive: true });
fs.copyFileSync(path.join(ROOT, 'qa/sections/browser-setup.md'), path.join(directory, 'qa/sections/browser-setup.md'));
fs.mkdirSync(path.join(directory, 'qa-reports/screenshots'), { recursive: true });
const policy = qaDeadlineShellPolicy(directory, guard, browse);
const calls: Array<{ tool: string; input: any; output: string }> = [];
const invoke = (args: string[], expected = 0, timeout = 5000) => {
const command = ['bun', guard, ...args].map(quote).join(' ');
const result = spawnSync('bash', ['-c', command], { cwd: directory, encoding: 'utf8', timeout });
expect(result.error).toBeUndefined();
expect(result.status, result.stderr).toBe(expected);
calls.push({ tool: 'Bash', input: { command }, output: result.stdout + result.stderr });
};
invoke(['start', policy.file, String(expectedBudgetMs / 1000)]);
const run = () => invoke(['run', policy.file, '--', browse, '--version']);
const check = (ended = Date.now()) => assertQaBrowserDeadline(calls, { directory, guard, browse, started, ended, expectedBudgetMs });
return { directory, policy, calls, invoke, run, check, started };
}
test.each(['captured-rewrite', 'verbatim-text', 'decoded-json', 'wrapped-json', 'missing-ack', 'late-write', 'disk-mismatch', 'wrong-command', 'receipt-in-observed'])
('browser checkpoints bind actual public results and acknowledgments: %s', scenario => {
const f = fixture();
const events = JSON.parse(JSON.stringify(capturedObservation.events)
.replaceAll('__QA_FIXTURE__', f.directory).replaceAll('__QA_GUARD__', guard).replaceAll('__QA_BROWSE__', browse));
const baseline = events[1].message.content[0];
const write = events[2].message.content[0];
const note = JSON.parse(write.input.content);
const lines = baseline.content.split('\n');
const first = lines.findIndex((line: string) => line.startsWith('QA_DEADLINE '));
const last = lines.findLastIndex((line: string) => line.startsWith('QA_DEADLINE '));
if (scenario !== 'captured-rewrite') note.observed = lines.slice(first + 1, last).join('\n');
if (scenario === 'decoded-json' || scenario === 'wrapped-json') {
baseline.content = lines[first] + '\n{"nested":{"value":7}}\n\n' + lines[last];
note.observed = scenario === 'decoded-json' ? { nested: { value: 7 } } : { result: { nested: { value: 7 } } };
}
if (scenario === 'receipt-in-observed') note.observed += lines[last];
if (scenario === 'wrong-command') note.observationCommand = 'another command';
write.input.content = JSON.stringify(note);
fs.writeFileSync(write.input.file_path, scenario === 'disk-mismatch' ? '{}' : write.input.content);
if (scenario === 'missing-ack') events.splice(3, 1);
if (scenario === 'late-write') events.push(...events.splice(2, 2));
const check = () => assertQaBrowserCheckpoints(events, { directory: f.directory, guard });
if (scenario === 'verbatim-text' || scenario === 'decoded-json') expect(check).not.toThrow();
else expect(check).toThrow();
});
test('R70 retained public ordering has no completed report Write before its clock/baseline', () => {
const f = fixture();
const events = JSON.parse(JSON.stringify(capturedPreparation.events)
.replaceAll('__QA_FIXTURE__', f.directory).replaceAll('__QA_GUARD__', guard));
expect(capturedPreparation.sourceEventIndices).toEqual([461, 465, 546, 550, 1041, 1045, 1965, 1969]);
expect(events[5].message.content[0].content[0]).toMatchObject({ type: 'image', source: { type: 'base64', media_type: 'image/jpeg' } });
expect(() => assertQaBrowserPreparation(events, { directory: f.directory, guard })).toThrow('report Write must complete before guard start/baseline');
});
test.each(['early', 'failed', 'unacknowledged', 'wrong-path', 'after-start', 'delayed-ack', 'subagent',
'crossed-parent', 'duplicate-use', 'duplicate-result', 'wrong-result-id', 'empty', 'malformed-content', 'malformed-result'])
('preparation credit requires an owned acknowledged nonempty Write: %s', scenario => {
const f = fixture();
const captured = JSON.parse(JSON.stringify(capturedPreparation.events)
.replaceAll('__QA_FIXTURE__', f.directory).replaceAll('__QA_GUARD__', guard));
const write = captured.at(-2), receipt = captured.at(-1);
write.message.content[0].input.content = 'Dashboard load: observe the rendered controls and record any incomplete checks.';
let events = [write, receipt, ...captured.slice(0, -2)];
if (scenario === 'failed') receipt.message.content[0].is_error = true;
if (scenario === 'unacknowledged') events.splice(1, 1);
if (scenario === 'wrong-path') write.message.content[0].input.file_path = path.join(f.directory, 'qa-reports/other.md');
if (scenario === 'after-start') events = [...events.slice(2, 4), write, receipt, ...events.slice(4)];
if (scenario === 'delayed-ack') events = [write, ...events.slice(2, 4), receipt, ...events.slice(4)];
if (scenario === 'subagent') write.parent_tool_use_id = receipt.parent_tool_use_id = 'child';
if (scenario === 'crossed-parent') receipt.parent_tool_use_id = 'child';
if (scenario === 'duplicate-use') events.unshift(structuredClone(write));
if (scenario === 'duplicate-result') events.splice(2, 0, structuredClone(receipt));
if (scenario === 'wrong-result-id') receipt.message.content[0].tool_use_id = 'unrelated';
if (scenario === 'empty') write.message.content[0].input.content = ' \n\t';
if (scenario === 'malformed-content') write.message.content[0].input.content = { text: 'not a Write string' };
if (scenario === 'malformed-result') receipt.message.content[0].content = [{ type: 'image', source: { type: 'base64', data: 'not a Write receipt' } }];
const check = () => assertQaBrowserPreparation(events, { directory: f.directory, guard });
if (scenario === 'early') expect(check).not.toThrow();
else expect(check).toThrow('QA preparation:');
});
test('native literal argv, status, scripts, readiness and artifact bookkeeping are accepted', () => {
const f = fixture();
const start = f.calls.shift()!;
for (const command of f.policy.allowed) f.calls.push({ tool: 'Bash', input: { command }, output: 'readiness/bookkeeping' });
f.calls.push(start);
f.run();
f.invoke(['status', f.policy.file]);
f.invoke(['run', f.policy.file, '--', 'bash', '-c', `${quote(browse)} --version | cat; printf '%s\n' 'done'`]);
f.calls.push({ tool: 'Write', input: { file_path: path.join(f.directory, 'qa-reports/report.md') }, output: '' });
expect(f.check()).toEqual({ launchedRuns: 2, completedRuns: 2, refusedRuns: 0, timedOutRuns: 0 });
});
test.each(['extra-option', 'suffix-option', 'operator', 'newline', 'foreign-cwd', 'missing-cwd'])
('metadata exception stays exact, fixture-bound and unbatched: %s', scenario => {
const f = fixture();
f.run();
const metadata = `git -C ${quote(f.directory)} rev-parse HEAD`;
const commands = {
'extra-option': `git -C ${quote(f.directory)} -c color.ui=false rev-parse HEAD`,
'suffix-option': metadata + ' --git-dir',
operator: metadata + '; ' + metadata,
newline: metadata + '\n' + metadata,
'foreign-cwd': `git -C ${quote(path.dirname(f.directory))} rev-parse HEAD`,
'missing-cwd': 'git rev-parse HEAD',
};
f.calls.push({ tool: 'Bash', input: { command: commands[scenario as keyof typeof commands] }, output: 'metadata' });
expect(() => f.check()).toThrow('QA deadline:');
});
test('quoted question marks stay literal but bare glob expansion is rejected', () => {
const f = fixture();
fs.writeFileSync(path.join(f.directory, 'x'), 'glob target');
f.run();
f.invoke(['run', f.policy.file, '--', '/usr/bin/printf', '%s', '?']);
const call = f.calls.at(-1)!;
expect(call.output.startsWith('?')).toBe(true);
expect(f.check().completedRuns).toBe(2);
call.input.command = call.input.command.replace("'?'", '?');
const expanded = spawnSync('bash', ['-c', call.input.command], { cwd: f.directory, encoding: 'utf8', timeout: 5000 });
expect(expanded.error).toBeUndefined();
expect(expanded.status, expanded.stderr).toBe(0);
expect(expanded.stdout).toBe('x');
call.output = expanded.stdout + expanded.stderr;
expect(() => f.check()).toThrow('unsupported outer shell composition');
});
test.each(['missing', 'bare', 'wrong-guard', 'direct-shebang', 'wrong-runtime', 'outer-tail', 'outer-newline', 'substitution', 'prefix', 'redirect', 'status-only', 'no-browser'])
('fails closed for absent/unsupported guard coverage: %s', scenario => {
const f = fixture();
f.run();
const call = f.calls[1];
if (scenario === 'missing') f.calls.length = 0;
if (scenario === 'bare') call.input.command = `${quote(browse)} --version`;
if (scenario === 'wrong-guard') call.input.command = call.input.command.replace(guard, '/tmp/gstack-qa-deadline');
if (scenario === 'direct-shebang') call.input.command = call.input.command.slice("'bun' ".length);
if (scenario === 'wrong-runtime') call.input.command = call.input.command.replace("'bun'", "'/tmp/bun'");
if (scenario === 'outer-tail') call.input.command += `; ${quote(browse)} --version`;
if (scenario === 'outer-newline') call.input.command += `\n${quote(browse)} --version`;
if (scenario === 'substitution') call.input.command += ' "$(echo unguarded)"';
if (scenario === 'prefix') call.input.command = 'true; ' + call.input.command;
if (scenario === 'redirect') call.input.command += ' 2>/dev/null';
if (scenario === 'status-only') { f.calls.pop(); f.invoke(['status', f.policy.file]); }
if (scenario === 'no-browser') { f.calls.pop(); f.invoke(['run', f.policy.file, '--', '/bin/true']); }
expect(() => f.check()).toThrow('QA deadline:');
});
test.each(['missing-finish', 'forged-start', 'forged-finish', 'duplicate', 'late-launch', 'early-refusal', 'state-reset', 'state-rewrite', 'wrong-budget', 'state-write', 'state-link', 'artifact-link', 'source-write'])
('rejects unauthenticated or inconsistent evidence: %s', scenario => {
const f = fixture();
f.run();
const receipts = f.calls[1].output.split('\n').filter(line => line.startsWith('QA_DEADLINE ')).map(line => JSON.parse(line.slice(12)));
const state = JSON.parse(fs.readFileSync(f.policy.file, 'utf8'));
let ended = Date.now();
if (scenario === 'missing-finish') receipts.pop();
if (scenario === 'forged-start') receipts[0].budgetMs = 90000;
if (scenario === 'forged-finish') receipts[1].deadlineAt = new Date(Date.parse(state.deadlineAt) + 30000).toISOString();
if (scenario === 'duplicate') receipts.push(receipts[1]);
if (scenario === 'late-launch') {
receipts[0].observedAt = state.deadlineAt; receipts[0].remainingMs = 0; receipts[0].expired = true;
ended = Date.parse(state.deadlineAt) + 1000;
}
if (scenario === 'early-refusal') { receipts[0].event = 'expired'; receipts.pop(); }
f.calls[1].output = receipts.map(receipt => 'QA_DEADLINE ' + JSON.stringify(receipt)).join('\n');
if (scenario === 'state-reset') {
fs.unlinkSync(f.policy.file);
f.invoke(['start', f.policy.file, '30']);
}
if (scenario === 'state-rewrite') {
fs.chmodSync(f.policy.file, 0o600);
fs.writeFileSync(f.policy.file, JSON.stringify(state) + '\n');
fs.chmodSync(f.policy.file, 0o400);
}
if (scenario === 'wrong-budget') {
fs.unlinkSync(f.policy.file);
f.calls.length = 0;
f.invoke(['start', f.policy.file, '90']);
f.run();
}
if (scenario === 'state-write') f.calls.push({ tool: 'Write', input: { file_path: f.policy.file }, output: '' });
if (scenario === 'state-link') { fs.renameSync(f.policy.file, f.policy.file + '.real'); fs.symlinkSync(f.policy.file + '.real', f.policy.file); }
if (scenario === 'artifact-link') {
fs.symlinkSync(path.join(f.directory, 'qa'), path.join(f.directory, 'qa-reports/linked'));
f.calls.push({ tool: 'Write', input: { file_path: path.join(f.directory, 'qa-reports/linked/source.md') }, output: '' });
}
if (scenario === 'source-write') f.calls.push({ tool: 'Write', input: { file_path: path.join(f.directory, 'source.ts') }, output: '' });
expect(() => f.check(ended)).toThrow();
});
test('child-emitted receipt forgery and direct state reset scripts are rejected', () => {
const f = fixture();
f.run();
const output = f.calls[1].output;
f.invoke(['run', f.policy.file, '--', 'bash', '-c', `printf '%s\n' ${quote(output)}; ${quote(browse)} --version`]);
expect(() => f.check()).toThrow('reserved deadline evidence');
f.calls.pop();
f.calls.push({ tool: 'Bash', input: { command: ['bun', guard, 'run', f.policy.file, '--', 'bash', '-c', `rm ${quote(f.policy.file)}`].map(quote).join(' ') }, output });
expect(() => f.check()).toThrow('reserved deadline evidence');
});
test('short fixture deadlines require an explicit contract; the native default remains exactly 30s', () => {
const native = fixture();
native.run();
expect(assertQaBrowserDeadline(native.calls, { directory: native.directory, guard, browse,
started: native.started, ended: Date.now() })).toEqual({ launchedRuns: 1, completedRuns: 1, refusedRuns: 0, timedOutRuns: 0 });
const f = fixture(500);
f.run();
expect(f.check()).toEqual({ launchedRuns: 1, completedRuns: 1, refusedRuns: 0, timedOutRuns: 0 });
expect(() => assertQaBrowserDeadline(f.calls, { directory: f.directory, guard, browse,
started: f.started, ended: Date.now() })).toThrow('expected 30000ms deadline');
for (const expectedBudgetMs of [0, -1, 0.5, 2_147_483_648, NaN, Infinity]) {
expect(() => assertQaBrowserDeadline(f.calls, { directory: f.directory, guard, browse,
started: f.started, ended: Date.now(), expectedBudgetMs })).toThrow('invalid expected deadline budget');
}
f.calls[0].input.command = f.calls[0].input.command.replace("'0.5'", "'30'");
expect(() => f.check()).toThrow('missing or repeated native start');
});
test('native timeout and subsequent refusal get no completed-run credit; bare late work still fails', async () => {
const f = fixture(500);
const pidFile = path.join(f.directory, 'child.pid');
const lateMarker = path.join(f.directory, 'late-work');
const refusalMarker = path.join(f.directory, 'refused-work');
f.invoke(['run', f.policy.file, '--', 'bash', '-c', `printf '%s' "$$" > ${quote(pidFile)}; sleep 2; ${quote(browse)} --version > ${quote(lateMarker)}`], 124);
const pid = Number(fs.readFileSync(pidFile, 'utf8'));
expect(Number.isInteger(pid) && pid > 0).toBe(true);
const reapedBy = performance.now() + 500;
let reaped = false;
while (performance.now() < reapedBy) {
try { process.kill(pid, 0); }
catch (error) {
expect((error as NodeJS.ErrnoException).code).toBe('ESRCH');
reaped = true;
break;
}
await Bun.sleep(10);
}
expect(reaped).toBe(true);
expect(fs.existsSync(lateMarker)).toBe(false);
f.invoke(['run', f.policy.file, '--', 'bash', '-c', `touch ${quote(refusalMarker)}; ${quote(browse)} --version`], 124);
expect(fs.existsSync(refusalMarker)).toBe(false);
expect(f.check()).toEqual({ launchedRuns: 1, completedRuns: 0, refusedRuns: 1, timedOutRuns: 1 });
f.calls.push({ tool: 'Bash', input: { command: `${quote(browse)} --version` }, output: 'late bare work' });
expect(() => f.check()).toThrow('unguarded');
});
test.each([
`B="/workspace/gstack/browse/dist/browse"\n$B js "(async () => { const links = [...new Set([...document.querySelectorAll('a[href]')].map(a => a.href))].filter(h => new URL(h).origin === location.origin && !/logout|signout|delete|remove|cancel|unsubscribe/i.test(h)); const out = []; for (const l of links) { const r = await fetch(l, { method: 'HEAD' }).catch(e => ({ status: 'ERR ' + e.message })); out.push('LINK ' + r.status + ' ' + l); } return out.join('\\n'); })()" 2>&1\necho "=== ALL HREFS ==="; $B links 2>&1`,
`echo "CLOCK=$(date -u +%Y-%m-%dT%H:%M:%SZ)"\nB="/workspace/gstack/browse/dist/browse"\ntimeout 20 $B js "(async()=>{const links=[...new Set([...document.querySelectorAll('a[href]')].map(a=>a.href))].filter(h=>new URL(h).origin===location.origin&&!/logout|signout|delete|remove|cancel|unsubscribe/i.test(h));const out=[];for(const l of links){const r=await fetch(l,{method:'HEAD'}).catch(e=>({status:'ERR '+e.message}));out.push('LINK '+r.status+' '+l);}const imgs=[...document.images].map(i=>'IMG '+(i.complete&&i.naturalWidth>0?'ok':'broken')+' '+i.src);return out.concat(imgs).join('\\\\n');})()" 2>&1\necho "CLOCK_END=$(date -u +%Y-%m-%dT%H:%M:%SZ)"`,
])('R65/R66 public late-link commands fail even after genuine guarded work: %#', command => {
const f = fixture();
f.run();
f.calls.push({ tool: 'Bash', input: { command }, output: 'CLOCK=2026-09-27T14:33:12Z' });
expect(() => f.check()).toThrow('QA deadline:');
});