mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
* 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
244 lines
12 KiB
TypeScript
244 lines
12 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { findFilesBySuffix, gitArgvIn } from './helpers/scratch-repo';
|
|
import { canReuseSharedLibsAdvisory, sharedLibsFingerprint } from '../lib/review-evidence';
|
|
|
|
const ROOT = resolve(import.meta.dir, '..');
|
|
let repo: string;
|
|
let home: string;
|
|
|
|
function cli(name: string, args: string[] = [], cwd = repo) {
|
|
return execFileSync(join(ROOT, 'bin', name), args, {
|
|
cwd, env: { ...process.env, GSTACK_HOME: home }, encoding: 'utf8', timeout: 10_000,
|
|
}).trim();
|
|
}
|
|
|
|
function git(...args: string[]) {
|
|
const result = gitArgvIn(repo, args, 10_000);
|
|
if (result.error || result.status !== 0)
|
|
throw new Error(`Fixture git failed: ${result.error?.message ?? result.stderr.toString()}`);
|
|
return result.stdout.toString().trim();
|
|
}
|
|
|
|
function log(token?: string, overrides: Record<string, any> = {}) {
|
|
const record = {
|
|
skill: 'review', status: 'clean', timestamp: new Date().toISOString(),
|
|
commit: git('rev-parse', '--short', 'HEAD'), completed: true, converged: true, cycles: 0,
|
|
...overrides,
|
|
};
|
|
cli('gstack-review-log', [JSON.stringify(record), ...(token ? ['--finish', token] : [])]);
|
|
return rows().at(-1)!;
|
|
}
|
|
|
|
function rows() {
|
|
return cli('gstack-review-read').split('---CONFIG---')[0].trim().split('\n').map(line => JSON.parse(line));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
repo = mkdtempSync(join(tmpdir(), 'review-start-repo-'));
|
|
home = mkdtempSync(join(tmpdir(), 'review-start-state-'));
|
|
git('init', '-q', '-b', 'main');
|
|
writeFileSync(join(repo, 'source.ts'), 'export const value = 1;\n');
|
|
git('add', 'source.ts');
|
|
git('commit', '-qm', 'initial');
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(repo, { recursive: true, force: true });
|
|
rmSync(home, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('review start/end binding (#2803)', () => {
|
|
test('a matching core snapshot preserves incomplete coverage and a saved native tree exposes later untracked edits', () => {
|
|
const core = log(cli('gstack-review-log', ['--start', 'review']), {
|
|
status: 'issues_found', completed: false, converged: false,
|
|
});
|
|
const native = log(cli('gstack-review-log', ['--start', 'adversarial-review']), {
|
|
skill: 'adversarial-review', source: 'in-host',
|
|
});
|
|
expect(core.review_binding.state).toBe('incomplete');
|
|
expect(core.wtree).toBeUndefined();
|
|
expect(native.review_binding.state).toBe('verified');
|
|
expect(core.review_binding.start_wtree).toBe(native.wtree);
|
|
expect(core.review_binding.end_wtree).toBe(native.wtree);
|
|
expect(git('diff', native.wtree, cli('gstack-wtree'))).toBe('');
|
|
writeFileSync(join(repo, 'new.ts'), 'export const changed = true;\n');
|
|
expect(git('diff', '--name-only', native.wtree, cli('gstack-wtree'))).toBe('new.ts');
|
|
const saved = rows()[0];
|
|
expect(saved.completed).toBe(false);
|
|
expect(saved.converged).toBe(false);
|
|
expect(saved.review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
test('unchanged completed review is current, including an identical-content commit', () => {
|
|
writeFileSync(join(repo, 'source.ts'), 'export const value = 2;\n');
|
|
writeFileSync(join(repo, 'new.ts'), 'export {};\n');
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
git('add', 'source.ts', 'new.ts');
|
|
git('commit', '-qm', 'reviewed content');
|
|
const row = log(token);
|
|
expect(row.review_binding.state).toBe('verified');
|
|
expect(row.review_binding.start_wtree).toBe(row.wtree);
|
|
expect(row.review_binding.end_wtree).toBe(row.wtree);
|
|
expect(row.review_binding.started_at).toMatch(/^\d{4}-/);
|
|
expect(row.review_binding.branch_id).toBe('0d6e4079e36703ebd37c00722f5891d28b0e2811dc114b129215123adcce3605');
|
|
expect(row.review_freshness.status).toBe('CURRENT');
|
|
git('commit', '--amend', '--no-edit');
|
|
expect(rows()[0].review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
for (const file of ['source.ts', 'untracked.ts']) {
|
|
test(`mid-review ${file} edit is stale even at zero commit distance`, () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
writeFileSync(join(repo, file), 'export const unreviewed = true;\n');
|
|
const row = log(token);
|
|
expect(git('rev-list', '--count', `${row.commit}..HEAD`)).toBe('0');
|
|
expect(row.wtree).toBeUndefined();
|
|
expect(row.review_binding.state).toBe('changed');
|
|
expect(row.review_binding.start_wtree).not.toBe(row.review_binding.end_wtree);
|
|
expect(row.review_freshness.status).toBe('STALE');
|
|
});
|
|
}
|
|
|
|
test('fix commits do not certify the final tree until a new unchanged pass', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
writeFileSync(join(repo, 'source.ts'), 'export const fixed = true;\n');
|
|
git('commit', '-qam', 'fix: review finding');
|
|
expect(log(token, { cycles: 3, converged: false }).review_freshness.status).toBe('STALE');
|
|
const rerun = cli('gstack-review-log', ['--start', 'review']);
|
|
expect(log(rerun, { cycles: 3 }).review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
test('log-only forged binding cannot certify current content', () => {
|
|
const wtree = cli('gstack-wtree');
|
|
const row = log(undefined, {
|
|
wtree, review_binding: { state: 'verified', start_wtree: wtree, end_wtree: wtree, branch_id: 'forged' },
|
|
review_freshness: { status: 'CURRENT' },
|
|
});
|
|
expect(row.wtree).toBeUndefined();
|
|
expect(row.review_binding.state).toBe('uncaptured');
|
|
expect(row.review_binding.branch_id).toBeUndefined();
|
|
expect(row.review_freshness.status).toBe('UNVERIFIED');
|
|
expect(log(wtree).review_freshness.status).toBe('UNVERIFIED');
|
|
expect(log('../forged').review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
test('a valid start supplies the branch digest and discards a caller-forged digest', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
const row = log(token, { review_binding: { branch_id: 'forged' } });
|
|
expect(row.review_binding.branch_id).toBe('0d6e4079e36703ebd37c00722f5891d28b0e2811dc114b129215123adcce3605');
|
|
expect(row.review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
test('colliding log filenames preserve advisory metadata without reusing another raw branch decision', () => {
|
|
writeFileSync(join(repo, 'second.ts'), 'export const other = 2;\n');
|
|
const finding = {
|
|
advisory: true, severity: 'INFORMATIONAL', action: 'skipped',
|
|
evidence_paths: ['source.ts', 'second.ts'], helper_target: { path: 'lib/shared.ts', symbol: 'readValue' },
|
|
};
|
|
const priorFinding = {
|
|
...finding, fingerprint: sharedLibsFingerprint(finding),
|
|
snapshot_covered_paths: [...finding.evidence_paths],
|
|
};
|
|
git('checkout', '-qb', 'feature/a');
|
|
const first = log(cli('gstack-review-log', ['--start', 'review']), { findings: [priorFinding] });
|
|
git('checkout', '-qb', 'feature-a');
|
|
const second = log(cli('gstack-review-log', ['--start', 'review']));
|
|
expect(findFilesBySuffix(home, '-reviews.jsonl')).toHaveLength(1);
|
|
expect(rows()).toHaveLength(2);
|
|
expect(first.findings[0]).toEqual(priorFinding);
|
|
expect(first.findings[0].snapshot_covered_paths).toEqual(['source.ts', 'second.ts']);
|
|
expect(first.wtree).toBe(second.wtree);
|
|
expect(first.review_binding.branch_id).toMatch(/^[0-9a-f]{64}$/);
|
|
expect(first.review_binding.branch_id).not.toBe(second.review_binding.branch_id);
|
|
const currentSnapshot = {
|
|
wtree: second.wtree, branch_id: second.review_binding.branch_id, covered_paths: finding.evidence_paths,
|
|
};
|
|
expect(canReuseSharedLibsAdvisory(first.findings[0], finding, first, currentSnapshot)).toBe(false);
|
|
expect(canReuseSharedLibsAdvisory(first.findings[0], finding, first, {
|
|
...currentSnapshot, branch_id: first.review_binding.branch_id,
|
|
})).toBe(true);
|
|
});
|
|
|
|
test('start receipt is single-use and scoped to the reviewer and branch', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
expect(log(token).review_freshness.status).toBe('CURRENT');
|
|
expect(log(token).review_freshness.status).toBe('UNVERIFIED');
|
|
const wrongSkill = cli('gstack-review-log', ['--start', 'adversarial-review']);
|
|
expect(log(wrongSkill).review_freshness.status).toBe('UNVERIFIED');
|
|
const wrongBranch = cli('gstack-review-log', ['--start', 'review']);
|
|
git('checkout', '-qb', 'other');
|
|
expect(log(wrongBranch).review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
for (const flags of [
|
|
{ completed: false }, { completed: undefined }, { converged: false }, { converged: undefined },
|
|
{ status: 'unavailable' }, { status: 'issues_found', critical: 7, issues_found: 51 },
|
|
{ critical: 7, issues_found: 51 },
|
|
]) {
|
|
test(`incomplete, nonconverged or unresolved result is not current: ${JSON.stringify(flags)}`, () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
expect(log(token, flags).review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
}
|
|
|
|
test('post-log untracked edits invalidate a previously current review', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
log(token);
|
|
writeFileSync(join(repo, 'later.ts'), 'export {};\n');
|
|
expect(rows()[0].review_freshness.status).toBe('STALE');
|
|
});
|
|
|
|
test('a Codex pass needs a genuine unchanged rerun after fixes', () => {
|
|
const original = cli('gstack-review-log', ['--start', 'codex-review']);
|
|
writeFileSync(join(repo, 'source.ts'), 'export const fixed = true;\n');
|
|
expect(log(original, { skill: 'codex-review' }).review_freshness.status).toBe('STALE');
|
|
const rerun = cli('gstack-review-log', ['--start', 'codex-review']);
|
|
expect(log(rerun, { skill: 'codex-review' }).review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
for (const [findings, findings_fixed, freshness] of [
|
|
[2, 0, 'UNVERIFIED'],
|
|
[2, 1, 'UNVERIFIED'],
|
|
[2, 2, 'CURRENT'],
|
|
[0, 0, 'CURRENT'],
|
|
] as const) {
|
|
test(`Codex gate pass with ${findings_fixed}/${findings} findings resolved grades ${freshness}`, () => {
|
|
const token = cli('gstack-review-log', ['--start', 'codex-review']);
|
|
const row = log(token, { skill: 'codex-review', status: 'clean', gate: 'pass', findings, findings_fixed });
|
|
expect(row.review_binding.state).toBe('verified');
|
|
expect(row.gate).toBe('pass');
|
|
expect(row.review_freshness.status).toBe(freshness);
|
|
});
|
|
}
|
|
|
|
test('legacy diff rows cannot use log-time wtree or HEAD; plan evidence is unchanged', () => {
|
|
log(undefined, { skill: 'plan-eng-review', completed: undefined, converged: undefined });
|
|
const file = findFilesBySuffix(home, '-reviews.jsonl')[0];
|
|
const plan = JSON.parse(readFileSync(file, 'utf8').trim());
|
|
const legacy = { ...plan, skill: 'review', review_freshness: { status: 'CURRENT' } };
|
|
writeFileSync(file, [plan, legacy, { ...legacy, wtree: undefined }].map(r => JSON.stringify(r)).join('\n') + '\n');
|
|
const read = rows();
|
|
expect(read[0]).toEqual(plan);
|
|
expect(read[0].wtree).toBe(cli('gstack-wtree'));
|
|
expect(read[1].review_freshness.status).toBe('UNVERIFIED');
|
|
expect(read[2].review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
for (const skill of ['adversarial-review', 'codex-review', 'design-review-lite', 'ship']) {
|
|
test(`${skill} cannot fall through to legacy plan handling`, () => {
|
|
expect(log(undefined, { skill }).review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
}
|
|
|
|
test('ship metrics cannot impersonate a completed review pass', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'ship']);
|
|
const row = log(token, { skill: 'ship' });
|
|
expect(row.review_freshness.status).toBe('UNVERIFIED');
|
|
expect(row.review_freshness.reason).toContain('telemetry');
|
|
});
|
|
});
|