feat(qa): helpers answer --help, and the QA eval interfaces declare it

Approved by Garry: asking gstack-qa-evidence or gstack-qa-deadline for usage
is read-only, so both helpers print usage and exit 0 on --help (the evidence
usage now names the annotation shape), and the functional and caller command
allowlists accept exactly 'bun <path>/bin/gstack-qa-{evidence,deadline} --help'.
Two CI runs failed only on that call.
This commit is contained in:
garrytan committed 2026-09-30 19:14:59 +00:00
1 parent 4643cb8550
commit ee929ff710
5 files changed
+37 -3

No files matched your search

+2 -1
View File
@@ -11,7 +11,7 @@ import { runSkillTest, SESSION_DRAIN_GRACE_MS } from './session-runner';
import { CAPTURE_MS } from './eval-budgets';
import { refreshHermeticSkillRuntime } from './hermetic-skill-runtime';
import { seedHermeticGstackHome } from './hermetic-env';
import { observeQAWrites, type QAWriteObservation } from './qa-functional-observer';
import { observeQAWrites, qaHelperUsageCommand, type QAWriteObservation } from './qa-functional-observer';
import { nativeCalls, readQACheckpointFiles, validateQACheckpoints } from './qa-checkpoint-evidence';
import { ownedPath } from './qa-functional-fixture';
import { qaEvidenceCommand, qaNativeCapture, qaProducerReceipt, type QaEvidenceContext } from './qa-evidence-producer';
@@ -214,6 +214,7 @@ function callerDeadlineCommand(command: string, context?: CallerDeadlineContext)
export function qaCallerCommandAllowed(command: string, workflowCommands: string[] = [], deadline?: CallerDeadlineContext): boolean {
const text = command.trim();
if (qaHelperUsageCommand(text)) return true;
if (callerEvidenceCommand(text, deadline)) return true;
if (callerDeadlineCommand(text, deadline)) return true;
if (/\bgstack-qa-(?:deadline|evidence)\b/.test(text) && !literalCallerCLI.test(text)
+6
View File
@@ -281,7 +281,13 @@ export function qaWriteVerdict(observation: QAWriteObservation, mode: QAMode): s
return failures;
}
/** Asking an installed gstack QA helper for its own usage text is read-only and always declared. */
export function qaHelperUsageCommand(command: string): boolean {
return /^bun (?:[\w./-]+\/)?bin\/gstack-qa-(?:evidence|deadline) --help$/.test(command.trim());
}
export function qaCommandAllowed(command: string, root?: string): boolean {
if (qaHelperUsageCommand(command)) return true;
const producer = root ? qaEvidenceCommand(command, { cwd: root, reportRoot: path.join(root, 'qa-reports'), executable: path.join(root, 'bin/gstack-qa-evidence') }) : undefined;
if (producer) {
try { ownedPath(root!, 'bin/gstack-qa-evidence'); } catch { return false; }
+16
View File
@@ -5,6 +5,9 @@ import * as path from 'node:path';
import { spawn, spawnSync } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { qaCommandAllowed } from './helpers/qa-functional-observer';
import { qaCallerCommandAllowed } from './helpers/qa-callers-fixture';
const CLI = path.resolve(import.meta.dir, '../bin/gstack-qa-evidence');
const ROOT = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'qa-evidence-'));
afterAll(() => fs.rmSync(ROOT, { recursive: true, force: true }));
@@ -294,3 +297,16 @@ test('materialize rejects placeholder metadata and same-probe learning with the
expect(selected.status, selected.stderr).toBe(0);
expect(JSON.parse(fs.readFileSync(path.join(f.root, 'evidence.json'), 'utf8')).learning).toEqual([]);
});
test('both QA helpers answer --help with usage and exit 0, and the declared interfaces allow it', () => {
for (const [cli, needle] of [[CLI, 'materialize ROOT ANNOTATIONS'], [path.resolve(import.meta.dir, '../bin/gstack-qa-deadline'), 'status FILE']] as const) {
const result = spawnSync(process.execPath, [cli, '--help'], { encoding: 'utf8', timeout: 10_000 });
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(needle);
}
expect(qaCommandAllowed('bun bin/gstack-qa-evidence --help')).toBe(true);
expect(qaCommandAllowed('bun /abs/runtime/bin/gstack-qa-deadline --help')).toBe(true);
expect(qaCommandAllowed('bun bin/gstack-qa-evidence --help; rm -rf x')).toBe(false);
expect(qaCommandAllowed('bun bin/gstack-qa-evidence --version')).toBe(false);
expect(qaCallerCommandAllowed('bun /abs/host/runtime/bin/gstack-qa-evidence --help')).toBe(true);
});