mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 15:27:56 +01:00
* fix(flaky): plan-design focus header, shared-libs revalidation sources, .capy leak-scan skip, fixture state root (GSTA-253) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: register the GSTA-253 focus capture in FREE_FIXTURES Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
179 lines
11 KiB
TypeScript
179 lines
11 KiB
TypeScript
// Split from test/shared-libs-checker-interface-evidence.test.ts (GSTA-151): the cases that replay the declared-protocol captures.
|
|
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { createHash } from 'node:crypto';
|
|
import { sharedLibsFingerprint } from '../lib/review-evidence';
|
|
import { hasTrustedSharedLibsCheck, hasTrustedReviewStartRead } from './helpers/shared-libs-review-start-evidence';
|
|
import {
|
|
fixtureGit, fixtureWorkingTree, reviewRecords, SHARED_LIBS_ROOT, toolCommandTrace, type SharedLibsFixture,
|
|
} from './helpers/shared-libs-eval-fixture';
|
|
import {
|
|
preparePathEligibilityFixture, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt,
|
|
} from './helpers/shared-libs-path-fixture';
|
|
import { fixtures, captures, pathKinds, capture, replay, call, result, pathCallback } from './helpers/shared-libs-checker-evidence';
|
|
|
|
const source = fs.readFileSync(path.join(import.meta.dir, 'skill-e2e-shared-libs.test.ts'), 'utf8');
|
|
|
|
beforeAll(async () => {
|
|
for (const change of ['unchanged', 'secondary', 'branch', 'filtered']) captures.set(`declared-${change}`, await capture(change, undefined, true));
|
|
for (const kind of pathKinds) captures.set(`declared-path-${kind}`, await capture(`path-${kind}`, preparePathEligibilityFixture(kind), true));
|
|
}, 120_000);
|
|
afterAll(() => { for (const fixture of fixtures) fs.rmSync(fixture.root, { recursive: true, force: true }); });
|
|
|
|
describe('native executable shared-code checker evidence', () => {
|
|
test('the actual path callback consumes the current synthetic prerequisite result without claiming native coverage', async () => {
|
|
const run = replay('declared-path-assume-unchanged');
|
|
run.kind = 'assume-unchanged';
|
|
let verified = 0;
|
|
const adapter = pathCallback(run, { hasPathReviewPrerequisiteReceipt: (events: any[], command: string, expected: any) => {
|
|
verified++;
|
|
expect(command).toBe(run.prepared.resumed.checkCommand);
|
|
expect(expected).toMatchObject({ synthetic: true, native_coverage: false, settled: true, current: true });
|
|
expect(expected.context.binding.repo).toBe(run.f.repo);
|
|
expect(expected.context.binding.state).toBe(run.f.state);
|
|
expect(expected.context.binding.index).toMatch(/^h /m);
|
|
return hasPathReviewPrerequisiteReceipt(events, command, expected);
|
|
} });
|
|
await adapter.invoke();
|
|
expect(verified).toBe(1);
|
|
expect(adapter.rows[0].row.passed).toBe(true);
|
|
expect(adapter.rows[0].row.transcript[0]).toMatchObject({
|
|
prerequisite_source: 'synthetic-fixture-input', prerequisite_native_coverage: false,
|
|
});
|
|
const refused = pathCallback(run, { hasPathReviewPrerequisiteReceipt: () => false });
|
|
await expect(refused.invoke()).rejects.toThrow('consume current synthetic prerequisites');
|
|
expect(refused.rows[0].row.passed).toBe(false);
|
|
});
|
|
|
|
test.each(['missing', 'failed', 'unpaired', 'assistant-only', 'caption', 'false', 'foreign state', 'after finish'])(
|
|
'the registered path callback rejects %s prerequisite receipts even with a completed record', async invalid => {
|
|
const run = replay('declared-path-ignored');
|
|
run.kind = 'ignored';
|
|
const at = run.events.findIndex((event: any) => event.type === 'assistant'
|
|
&& event.message.content[0].input?.command === run.prepared.resumed.checkCommand);
|
|
expect(at).toBeGreaterThan(run.checkAt);
|
|
if (invalid === 'missing') run.events.splice(at, 2);
|
|
if (invalid === 'failed') result(run, at).is_error = true;
|
|
if (invalid === 'unpaired') result(run, at).tool_use_id = 'another-call';
|
|
if (invalid === 'assistant-only') run.events[at + 1].type = 'assistant';
|
|
if (invalid === 'caption') call(run, at).input = { command: 'true', description: run.prepared.resumed.checkCommand };
|
|
if (invalid === 'false' || invalid === 'foreign state') {
|
|
const receipt = JSON.parse(result(run, at).content);
|
|
if (invalid === 'false') receipt.settled = false;
|
|
else receipt.context.binding.state = '/another-fixture/state';
|
|
result(run, at).content = JSON.stringify(receipt);
|
|
}
|
|
if (invalid === 'after finish') run.events.push(...run.events.splice(at, 2));
|
|
const adapter = pathCallback(run);
|
|
await expect(adapter.invoke()).rejects.toThrow('consume current synthetic prerequisites');
|
|
expect(adapter.rows[0].row.passed).toBe(false);
|
|
});
|
|
|
|
test.each(['raw source', 'supplied context'])('the registered callback rejects late changes to %s', async changed => {
|
|
const run = replay('declared-path-assume-unchanged');
|
|
run.kind = 'assume-unchanged';
|
|
const file = changed === 'raw source' ? path.join(run.f.repo, 'src/retry-route.ts') : run.prepared.resumed.input;
|
|
const before = fs.readFileSync(file, 'utf8');
|
|
const adapter = pathCallback(run, { runSharedInteractive: async () => {
|
|
fs.writeFileSync(file, before + '\n');
|
|
return { questions: run.questions, result: { exitReason: 'success', output: '', events: run.events,
|
|
toolCalls: run.events.filter((event: any) => event.type === 'assistant')
|
|
.map((event: any) => ({ tool: event.message.content[0].name, input: event.message.content[0].input })) } };
|
|
} });
|
|
try {
|
|
await expect(adapter.invoke()).rejects.toThrow('prerequisite state must remain unchanged');
|
|
expect(adapter.rows[0].row.passed).toBe(false);
|
|
} finally { fs.writeFileSync(file, before); }
|
|
});
|
|
|
|
test.each(['unchanged', 'secondary', 'branch', 'filtered'])('declared receipt commands satisfy the real %s revalidation callback', change => {
|
|
const run = replay(`declared-${change}`);
|
|
expect(result(run, 0).content.trim()).toBe(run.token);
|
|
expect(JSON.parse(result(run).content)).toEqual(run.receipt);
|
|
expect(call(run, run.finishAt).input.command).toContain(`--finish ${run.token} && '${SHARED_LIBS_ROOT}/bin/gstack-review-read'`);
|
|
expect(run.questions).toHaveLength(change === 'unchanged' ? 0 : 1);
|
|
const scenario = source.slice(source.indexOf("test('shared-libs-review-revalidation'"));
|
|
const marker = '}, result => {';
|
|
const start = scenario.indexOf(marker) + marker.length;
|
|
const body = scenario.slice(start, scenario.indexOf('\n });', start));
|
|
const verify = new Function('deps', 'result', new Bun.Transpiler({ loader: 'ts' }).transformSync(`const {
|
|
change, questions, expect, toolCommandTrace, reviewRecords, createHash, path, f, current, sources,
|
|
fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, hasTrustedSharedLibsCheck, SHARED_LIBS_ROOT,
|
|
resumed, prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt
|
|
} = deps; ${body}`));
|
|
const native = { events: run.events, toolCalls: run.events.filter((event: any) => event.type === 'assistant')
|
|
.map((event: any) => ({ tool: event.message.content[0].name, input: event.message.content[0].input })) };
|
|
let checks = 0;
|
|
verify({ change, questions: run.questions, expect, toolCommandTrace, reviewRecords, createHash, path,
|
|
f: run.f, current: run.current, sources: run.expected.sources, fixtureGit, fixtureWorkingTree, hasTrustedReviewStartRead, SHARED_LIBS_ROOT,
|
|
resumed: run.resumed, prerequisites: run.prerequisites, checkPathReviewPrerequisites, hasPathReviewPrerequisiteReceipt,
|
|
hasTrustedSharedLibsCheck: (events: unknown[], expected: any) => {
|
|
checks++;
|
|
expect(events).toBe(run.events);
|
|
expect(expected).toEqual(run.expected);
|
|
return hasTrustedSharedLibsCheck(events, expected);
|
|
} }, native);
|
|
expect(checks).toBe(1);
|
|
});
|
|
|
|
test.each(pathKinds)('declared receipt commands satisfy the real %s path callback after an actual actor decision', async kind => {
|
|
const run = replay(`declared-path-${kind}`);
|
|
run.kind = kind;
|
|
expect(result(run, 0).content.trim()).toBe(run.token);
|
|
expect(JSON.parse(result(run).content)).toEqual(run.receipt);
|
|
expect(run.receipt.reusable).toBe(false);
|
|
expect(run.questions).toHaveLength(1);
|
|
let checks = 0;
|
|
const adapter = pathCallback(run, { hasTrustedSharedLibsCheck: (events: unknown[], expected: any) => {
|
|
checks++;
|
|
expect(expected).toEqual(run.expected);
|
|
return hasTrustedSharedLibsCheck(events, expected);
|
|
} });
|
|
await adapter.invoke();
|
|
expect(checks).toBe(1);
|
|
expect(adapter.rows).toMatchObject([{ scenario: kind, row: { passed: true } }]);
|
|
});
|
|
|
|
test.each(['revised-only identity', 'unsupported finding', 'unfinished review', 'missing explicit decision'])(
|
|
'canonical transport cannot waive %s in the real ignored-path callback', async failure => {
|
|
const run = replay('declared-path-ignored');
|
|
run.kind = 'ignored';
|
|
if (failure === 'missing explicit decision') run.questions = [];
|
|
const adapter = pathCallback(run, { reviewRecords: (fixture: SharedLibsFixture) => {
|
|
const records = reviewRecords(fixture);
|
|
const final = records.at(-1);
|
|
if (failure === 'revised-only identity') {
|
|
const revised = { ...final.findings[0], evidence_paths: [
|
|
'src/retry-worker.ts', 'src/scheduler.ts', 'src/retry-route.ts', 'lib/retry-after.ts',
|
|
] };
|
|
revised.fingerprint = sharedLibsFingerprint(revised);
|
|
revised.snapshot_covered_paths = [...revised.evidence_paths];
|
|
expect(revised.fingerprint).not.toBe(run.current.fingerprint);
|
|
final.findings = [revised];
|
|
}
|
|
if (failure === 'unsupported finding') final.findings = [];
|
|
if (failure === 'unfinished review') final.completed = false;
|
|
return records;
|
|
} });
|
|
await expect(adapter.invoke()).rejects.toThrow();
|
|
expect(adapter.rows).toMatchObject([{ scenario: 'ignored', row: { passed: false } }]);
|
|
});
|
|
|
|
test.each(['start batching', 'mixed checker stdout', 'finish metadata prelude', 'loop-only caller reads'])(
|
|
'the unchanged detector still rejects %s after protocol declaration', shape => {
|
|
const run = replay('declared-unchanged');
|
|
if (shape === 'start batching') call(run, 0).input.command = `echo start; ${call(run, 0).input.command}; git diff origin/main`;
|
|
if (shape === 'mixed checker stdout') result(run).content = `checker receipt:\n${result(run).content}\nexit=0`;
|
|
if (shape === 'finish metadata prelude') call(run, run.finishAt).input.command = `TS=$(date -u); ${call(run, run.finishAt).input.command}`;
|
|
if (shape === 'loop-only caller reads') {
|
|
for (const event of run.events) for (const block of event.message.content) {
|
|
if (block.type === 'tool_use' && block.name === 'Bash' && block.input.command.startsWith('cat ')) {
|
|
block.input.command = `for f in ${block.input.command.slice(4)}; do cat "$f"; done`;
|
|
}
|
|
}
|
|
}
|
|
expect(hasTrustedSharedLibsCheck(run.events, run.expected)).toBe(false);
|
|
});
|
|
});
|