Files
gstack/test/ship-skip-actor.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

507 lines
33 KiB
TypeScript

import { 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 type { HookCallback, Query, SDKMessage } from '@anthropic-ai/claude-agent-sdk';
import type { QueryProvider } from './helpers/agent-sdk-runner';
import type { EvalTestEntry } from './helpers/eval-store';
import { createShipSkipFixture, runShipSkipActor, shipSkipWorkflow } from './helpers/ship-skip-actor';
import { CAPTURE_MS } from './helpers/eval-budgets';
import { DEFAULT_SHARD_TIMEOUT_MS, retriesForFiles } from '../scripts/test-paid-shards';
type Fault = 'repeat-skip' | 'silent-clear' | 'fake-probe' | 'requeue' | 'missing-skip-ack' | 'decision-forgery' | 'product-write' | 'late-source-read' | 'rate-limit'
| 'empty-workflow' | 'truncated-workflow' | 'empty-source' | 'truncated-source' | 'offset-read' | 'limited-read'
| 'unbound-cached-read' | 'wrong-read-metadata' | 'wrong-cache-receipt' | 'release-waiver' | 'combined-permission'
| 'early-source-read' | 'enriched-evidence';
const quote = (value: string) => `'${value.replaceAll("'", "'\"'\"'")}'`;
function protocol(fault?: Fault, billing?: Array<number | undefined>, controls: { annotations?: boolean; fullTextReread?: boolean } = {}) {
let calls = 0;
let directory = '';
const sessions: Array<{ options: Parameters<QueryProvider>[0]['options']; root: string; closed: number }> = [];
const provider: QueryProvider = ({ prompt, options }) => {
calls++;
const cwd = options!.cwd!;
directory = cwd;
const root = path.dirname(cwd);
const session = { options, root, closed: 0 };
sessions.push(session);
const env = options!.env!;
expect(String(prompt)).toContain(path.join(root, 'assets/workflow.md'));
expect(env).toMatchObject({ HOME: path.join(root, 'home'), GSTACK_HOME: path.join(root, 'state'),
GSTACK_STATE_ROOT: path.join(root, 'state'), CLAUDE_CONFIG_DIR: path.join(root, 'claude-config') });
expect(options!.tools).toEqual(['Read', 'Write', 'Bash', 'AskUserQuestion']);
expect(options!.allowedTools).toEqual([]);
expect(options!.permissionMode).toBe('default');
expect(options!.settingSources).toEqual([]);
const command = (action: string) => `${quote(process.execPath)} ${quote(path.resolve(import.meta.dir, 'helpers/ship-skip-actor.ts'))} --fixture ${quote(root)} ${action}`;
let sequence = 0;
const readFiles = new Set<string>();
const execute = async function* (tool: string, input: Record<string, unknown>): AsyncGenerator<SDKMessage, void> {
const id = `attempt-${calls}-tool-${++sequence}`;
yield { type: 'assistant', message: { content: [{ type: 'tool_use', id, name: tool, input }] } } as SDKMessage;
const hook = options!.hooks!.PreToolUse![0].hooks[0];
const decision = await hook({ hook_event_name: 'PreToolUse', tool_name: tool, tool_input: input,
tool_use_id: id, session_id: 'scripted-free-control', transcript_path: '', cwd,
} as Parameters<HookCallback>[0], id, { signal: new AbortController().signal });
const output = (decision as { hookSpecificOutput: { permissionDecision: string; updatedInput?: Record<string, unknown> } }).hookSpecificOutput;
let content = '';
let nativeResult: unknown;
let failed = output.permissionDecision === 'deny';
const approved = output.updatedInput ?? input;
if (failed) content = 'Registered fixture hook denied this interaction';
else if (tool === 'AskUserQuestion') {
expect(output.permissionDecision).toBe('ask');
const normalized = { questions: (approved.questions as any[]).map(question => ({ question: question.question, header: question.header,
options: question.options, multiSelect: question.multiSelect })) };
const response = await options!.canUseTool!(tool, normalized, { signal: new AbortController().signal, toolUseID: id });
failed = response.behavior !== 'allow';
if (response.behavior === 'allow') {
expect(Object.values(response.updatedInput!.answers as Record<string, string>)).toEqual(['Skip']);
nativeResult = response.updatedInput;
content = `Your questions have been answered: "${normalized.questions[0].question}"="Skip". You can now continue with these answers in mind.`;
} else content = response.message;
if (fault === 'missing-skip-ack') return;
} else if (tool === 'Read') {
const file = approved.file_path as string;
const bytes = fs.readFileSync(file, 'utf8');
const lines = bytes.split('\n');
nativeResult = { type: 'text', file: { filePath: file, content: bytes, numLines: lines.length, startLine: 1, totalLines: lines.length } };
content = lines.map((line, index) => `${index + 1}\t${line}`).join('\n');
if (readFiles.has(file) && !controls.fullTextReread || fault === 'unbound-cached-read' && file.endsWith('/invoice.ts')) {
nativeResult = { type: 'file_unchanged', file: { filePath: file } };
content = fault === 'wrong-cache-receipt' ? 'Claimed unchanged without the native receipt.'
: 'Wasted call — file unchanged since your last Read. Refer to that earlier tool_result instead.';
}
if (file.endsWith('/workflow.md') && fault === 'empty-workflow' || file.endsWith('/invoice.ts') && fault === 'empty-source') content = '';
if (file.endsWith('/workflow.md') && fault === 'truncated-workflow' || file.endsWith('/invoice.ts') && fault === 'truncated-source') content = content.slice(0, 15);
if (fault === 'wrong-read-metadata' && file.endsWith('/workflow.md')) (nativeResult as any).file.content = 'foreign content';
readFiles.add(file);
}
else if (tool === 'Write') {
fs.writeFileSync(approved.file_path as string, approved.content as string, { mode: 0o600 });
content = 'Record written';
} else if (tool === 'Bash') {
expect(approved).toMatchObject({ timeout: 10000, run_in_background: false });
const result = spawnSync('bash', ['-c', approved.command as string], { cwd, env, encoding: 'utf8', timeout: 10000 });
failed = result.status !== 0;
content = result.stdout + result.stderr;
}
yield { type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: id, content, is_error: failed }] },
...(nativeResult ? { tool_use_result: nativeResult } : {}) } as SDKMessage;
};
const ask = () => execute('AskUserQuestion', { questions: [{ header: 'Invoice auth', multiSelect: false,
question: fault === 'release-waiver' ? 'Accept the invoice authorization risk and authorize release?' : 'Fix invoice.ts authorization so only the invoice owner is accepted?',
options: [{ label: 'Fix', description: 'Enforce the owner check.' }, { label: 'Skip', description: fault === 'combined-permission'
? 'Leave the source unchanged and authorize publication.' : 'Leave the source unchanged and retain the unresolved defect.' }] }] });
return {
close() { session.closed++; expect(fs.existsSync(root)).toBe(true); },
async *[Symbol.asyncIterator]() {
if (fault === 'rate-limit' && calls === 2) {
const old = sessions[0];
expect(old.closed).toBe(1);
expect(old.root).not.toBe(root);
expect(fs.existsSync(old.root)).toBe(true);
const prior = fs.readFileSync(path.join(old.root, 'owner-answer.json'), 'utf8');
const denied = await old.options!.canUseTool!('AskUserQuestion', { questions: [] }, { signal: new AbortController().signal, toolUseID: 'late-old-question' });
expect(denied).toMatchObject({ behavior: 'deny', message: 'Attempt is closed or expired' });
const blocked = await old.options!.hooks!.PreToolUse![0].hooks[0]({ hook_event_name: 'PreToolUse', tool_name: 'Write',
tool_input: { file_path: path.join(old.root, 'review-record.json'), content: 'late old attempt' }, tool_use_id: 'late-old-write',
session_id: 'scripted-free-control', transcript_path: '', cwd: old.options!.cwd!,
}, 'late-old-write', { signal: new AbortController().signal });
expect((blocked as any).hookSpecificOutput).toMatchObject({ permissionDecision: 'deny', permissionDecisionReason: 'Attempt is closed or expired' });
expect(fs.existsSync(path.join(root, 'review-record.json'))).toBe(false);
expect(fs.existsSync(path.join(root, 'owner-answer.json'))).toBe(false);
expect(fs.readFileSync(path.join(old.root, 'owner-answer.json'), 'utf8')).toBe(prior);
}
yield* execute('Read', { file_path: path.join(root, 'assets/workflow.md'),
...(fault === 'offset-read' ? { offset: 9999 } : fault === 'limited-read' ? { limit: 1 } : {}) });
yield* execute('Read', { file_path: path.join(root, 'assets/finding.json') });
yield* execute('Read', { file_path: path.join(cwd, 'invoice.ts') });
yield* execute('Bash', { command: command('read') });
if (fault !== 'decision-forgery') yield* ask();
if (fault === 'repeat-skip') yield* ask();
if (fault === 'product-write') yield* execute('Write', { file_path: path.join(cwd, 'invoice.ts'), content: 'cleared' });
if (fault === 'early-source-read') yield* execute('Read', { file_path: path.join(cwd, 'invoice.ts') });
const input = JSON.parse(fs.readFileSync(path.join(root, 'assets/finding.json'), 'utf8'));
const record = { skill: 'review', via: 'ship', status: fault === 'silent-clear' ? 'clean' : 'issues_found',
issues_found: fault === 'silent-clear' ? 0 : 1, critical: fault === 'silent-clear' ? 0 : 1, informational: 0,
completed: fault === 'fake-probe', converged: fault === 'fake-probe', cycles: 0,
findings: fault === 'silent-clear' ? [] : [{ ...input.finding, action: 'skipped',
...(fault === 'enriched-evidence' ? { decision_evidence: { ...input.finding.decision_evidence, unchanged: true } } : {}),
...(controls.annotations ? { decision_note: 'Owner chose Skip; source unchanged; verification remains incomplete.' } : {}) }],
...(fault === 'fake-probe' ? { VERIFY_RESULT: 'pass', probes: [{ id: 'invented', status: 'pass' }] } : {}) };
yield* execute('Write', { file_path: path.join(root, 'review-record.json'), content: JSON.stringify(record) });
yield* execute('Bash', { command: command('persist') });
if (fault === 'rate-limit' && calls === 1) {
if (billing?.[0] !== undefined) yield { type: 'result', subtype: 'error_during_execution', num_turns: sequence,
errors: ['rate limit'], total_cost_usd: billing[0] } as SDKMessage;
throw Object.assign(new Error('rate limit'), { status: 429 });
}
yield* execute('Bash', { command: command('rediscover') });
if (fault !== 'late-source-read' && fault !== 'early-source-read') yield* execute('Read', { file_path: path.join(cwd, 'invoice.ts') });
yield* execute('Bash', { command: command(fault === 'requeue' ? 'repeat' : 'advance') });
if (fault === 'late-source-read') yield* execute('Read', { file_path: path.join(cwd, 'invoice.ts') });
yield { type: 'assistant', message: { content: [{ type: 'text', text: 'Invoice authorization remains unresolved and skipped. No verification was run. Selected the queue boundary only.' }] } } as SDKMessage;
yield { type: 'result', subtype: 'success', num_turns: sequence,
...(billing ? billing[calls - 1] === undefined ? {} : { total_cost_usd: billing[calls - 1] } : { total_cost_usd: 0 }) } as SDKMessage;
},
} as Query;
};
return { provider, directory: () => directory, calls: () => calls, sessions };
}
test('one bounded native case preserves the existing whole-file retry allowance', () => {
const retries = retriesForFiles(['test/skill-e2e-ship-skip.test.ts']);
expect(retries).toBe(1);
expect(CAPTURE_MS * (retries + 1) + 120000).toBeLessThanOrEqual(DEFAULT_SHARD_TIMEOUT_MS);
});
test('fixture declares the exact post-rediscovery Read and canonical evidence contract before launch', () => {
const fixture = createShipSkipFixture('Bounded interface declaration control');
try {
expect(fixture.prompt).toContain(`After the rediscover command's result is acknowledged, issue a new full Read with exactly ${JSON.stringify({ file_path: fixture.product })}`);
expect(fixture.prompt).toContain('Receive its actual tool result before issuing either routing command (advance or repeat), even when the result is file_unchanged');
expect(fixture.prompt).toContain('A Read before rediscovery or a prose claim of unchanged source does not satisfy this step.');
expect(fixture.prompt).toContain(`Copy the canonical decision_evidence object from ${fixture.inputPath} unchanged, with exactly its path and sha256 fields and values; put any annotations outside decision_evidence.`);
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
});
for (const fullTextReread of [false, true]) test(`declared Skip interface accepts ${fullTextReread ? 'full text' : 'native unchanged receipt'} and annotations outside canonical evidence`, async () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
try {
const file = await runShipSkipActor(() => {}, protocol(undefined, undefined, { annotations: true, fullTextReread }).provider, artifacts);
const retained = JSON.parse(fs.readFileSync(file, 'utf8'));
const finding = retained.evidence.persisted.findings[0];
expect(finding.decision_evidence).toEqual(retained.evidence.input.finding.decision_evidence);
expect(Object.keys(finding.decision_evidence).sort()).toEqual(['path', 'sha256']);
expect(finding.decision_note).toBe('Owner chose Skip; source unchanged; verification remains incomplete.');
expect(retained.evidence.product).toBe(retained.evidence.originalProduct);
const events = retained.attempts[0].events;
const results = events.flatMap((event: any, index: number) => event.type === 'user'
? event.message.content.filter((block: any) => block.type === 'tool_result').map((block: any) => ({ index, id: block.tool_use_id, native: event.tool_use_result })) : []);
const calls = events.flatMap((event: any) => event.type === 'assistant'
? event.message.content.filter((block: any) => block.type === 'tool_use') : []);
const rediscover = calls.find((call: any) => call.name === 'Bash' && call.input.command.endsWith(' rediscover'));
const advance = calls.find((call: any) => call.name === 'Bash' && call.input.command.endsWith(' advance'));
const sourceReads = calls.filter((call: any) => call.name === 'Read' && call.input.file_path.endsWith('/invoice.ts'));
expect(sourceReads).toHaveLength(2);
expect(sourceReads[1].input).toEqual({ file_path: sourceReads[0].input.file_path });
const acknowledgment = results.find((result: any) => result.id === sourceReads[1].id);
expect(acknowledgment.native.type).toBe(fullTextReread ? 'text' : 'file_unchanged');
expect(acknowledgment.index).toBeGreaterThan(results.find((result: any) => result.id === rediscover.id).index);
expect(acknowledgment.index).toBeLessThan(events.findIndex((event: any) => event.type === 'assistant'
&& event.message.content.some((block: any) => block.type === 'tool_use' && block.id === advance.id)));
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
test('registered native callback, real logger and routing adapter retain a bound Skip after cleanup', async () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const records: EvalTestEntry[] = [];
const driver = protocol();
try {
const file = await runShipSkipActor(entry => records.push(entry), driver.provider, artifacts);
expect(records).toHaveLength(1);
expect(records[0]).toMatchObject({ passed: true, cost_usd: 0, name: 'ship-skipped-queued-finding' });
const retained = JSON.parse(fs.readFileSync(file, 'utf8'));
expect(retained).toEqual(JSON.parse(records[0].output!));
expect(fs.statSync(file).mode & 0o777).toBe(0o600);
expect(fs.existsSync(path.dirname(driver.directory()))).toBe(false);
expect(retained.evidence.answers).toHaveLength(1);
expect(retained.evidence.answers[0].answers).toEqual({ 'Fix invoice.ts authorization so only the invoice owner is accepted?': 'Skip' });
expect(retained.evidence.persisted.review_binding.state).toBe('incomplete');
expect(retained.evidence.persisted.review_binding.start_wtree).toBe(retained.evidence.start.wtree);
expect(retained.evidence.persisted.review_binding.end_wtree).toBe(retained.evidence.start.wtree);
expect(retained.evidence.persisted.completed).toBe(false);
expect(retained.evidence.product).toBe(retained.evidence.originalProduct);
expect(retained.evidence.receipts.map((row: any) => row.action)).toEqual(['read', 'persist', 'rediscover', 'advance']);
expect(retained.workflow).toContain('Only unmatched or reopened findings remain queued');
expect(retained.attempts[0].events.filter((event: any) => event.type === 'user').length).toBeGreaterThan(0);
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
for (const [fault, message] of [
['repeat-skip', 'undeclared or repeated interaction'],
['silent-clear', 'skipped defect was silently cleared'],
['fake-probe', 'synthetic coverage was marked complete'],
['requeue', 'without requeue'],
['missing-skip-ack', 'native Skip response was not acknowledged'],
['decision-forgery', 'expected exactly one captured owner Skip'],
['product-write', 'undeclared or repeated interaction'],
['late-source-read', 'source not re-read before routing'],
['early-source-read', 'source not re-read before routing'],
['enriched-evidence', 'persisted Skip lost its identity or source evidence'],
['empty-workflow', 'complete workflow was not delivered'],
['truncated-workflow', 'complete workflow was not delivered'],
['empty-source', 'owner decision lacks complete source'],
['truncated-source', 'owner decision lacks complete source'],
['offset-read', 'complete workflow was not delivered'],
['limited-read', 'complete workflow was not delivered'],
['unbound-cached-read', 'owner decision lacks complete source'],
['wrong-read-metadata', 'complete workflow was not delivered'],
['wrong-cache-receipt', 'source not re-read before routing'],
['release-waiver', 'expected exactly one captured owner Skip'],
['combined-permission', 'expected exactly one captured owner Skip'],
] as const) test(`scripted negative control rejects ${fault}`, async () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const records: EvalTestEntry[] = [];
const driver = protocol(fault);
try {
await expect(runShipSkipActor(entry => records.push(entry), driver.provider, artifacts)).rejects.toThrow(message);
expect(records).toHaveLength(1);
expect(records[0].passed).toBe(false);
const retained = JSON.parse(fs.readFileSync(path.join(artifacts, fs.readdirSync(artifacts)[0]), 'utf8'));
expect(retained.error).toContain(message);
if (fault === 'release-waiver' || fault === 'combined-permission') expect(retained.evidence.answers).toHaveLength(0);
expect(retained.attempts.length).toBeGreaterThan(0);
expect(fs.existsSync(path.dirname(driver.directory()))).toBe(false);
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
test('rate-limit retry resets owned decisions and logs while preserving both attempts', async () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const driver = protocol('rate-limit');
try {
const file = await runShipSkipActor(() => {}, driver.provider, artifacts);
const retained = JSON.parse(fs.readFileSync(file, 'utf8'));
expect(driver.calls()).toBe(2);
expect(retained.attempts).toHaveLength(2);
expect(new Set(retained.attempts.map((attempt: any) => attempt.evidence.repo)).size).toBe(2);
expect(retained.attempts[0].lateCallbacks).toEqual(['AskUserQuestion', 'PreToolUse']);
expect(retained.roots.every((root: string) => !fs.existsSync(root))).toBe(true);
expect(driver.sessions.map(session => session.closed)).toEqual([1, 1]);
for (const attempt of retained.attempts) {
expect(attempt).toMatchObject({ active: false, closed: true, drained: true });
expect(attempt.evidence.answers).toHaveLength(1);
expect(attempt.evidence.receipts.filter((row: any) => row.action === 'persist')).toHaveLength(1);
}
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
test('stream closure rejects late callbacks and retains its root until iterator drain', async () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const driver = protocol();
let drained = false;
const provider: QueryProvider = args => {
const stream = driver.provider(args);
return new Proxy(stream, { get(target, property) {
if (property === Symbol.asyncIterator) return () => {
const iterator = target[Symbol.asyncIterator]();
return { next: (...values: any[]) => iterator.next(...values), async return() {
expect(driver.sessions[0].closed).toBe(1);
const root = path.dirname(args.options!.cwd!);
expect(fs.existsSync(root)).toBe(true);
await new Promise(resolve => setTimeout(resolve, 25));
const answer = await args.options!.canUseTool!('AskUserQuestion', {}, { toolUseID: 'during-drain', signal: new AbortController().signal });
expect(answer).toMatchObject({ behavior: 'deny', message: 'Attempt is closed or expired' });
expect(fs.existsSync(root)).toBe(true);
const result = await iterator.return?.();
drained = true;
return result ?? { done: true, value: undefined };
} };
};
const value = Reflect.get(target, property, target);
return typeof value === 'function' ? value.bind(target) : value;
} });
};
try {
const file = await runShipSkipActor(() => {}, provider, artifacts);
expect(drained).toBe(true);
const retained = JSON.parse(fs.readFileSync(file, 'utf8'));
expect(retained.attempts[0]).toMatchObject({ closed: true, drained: true, lateCallbacks: ['AskUserQuestion'] });
expect(retained.retainedRoots).toEqual([]);
expect(fs.existsSync(driver.sessions[0].root)).toBe(false);
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
for (const [label, costs, expected, known] of [
['known retry sums', [0.125, 0.25], 0.375, true],
['missing retry billing', [undefined, 0.25], 0.25, false],
['all billing missing', [undefined, undefined], 0, false],
] as const) test(`billing reports ${label} without inventing missing charges`, async () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const entries: EvalTestEntry[] = [];
try {
const file = await runShipSkipActor(entry => entries.push(entry), protocol('rate-limit', [...costs]).provider, artifacts);
const captured = JSON.parse(fs.readFileSync(file, 'utf8'));
expect(entries[0].cost_usd).toBe(expected);
expect(captured.billing).toMatchObject({ knownCostUsd: expected, costKnown: known, status: known ? 'complete' : 'incomplete' });
expect(captured.billing.attempts.map((attempt: any) => attempt.costUsd)).toEqual(costs.map(cost => cost ?? null));
expect(entries[0].transcript!.filter(event => event.type === 'result')).toHaveLength(costs[0] === undefined ? 1 : 2);
if (!known) expect(entries[0].error).toContain('actual total cost is unknown');
expect(entries[0].passed).toBe(true);
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
test.each(['empty', 'foreign-config'])('ship fixture seeds real commits with %s identity-free HOME', mode => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-ident-'));
try {
const home = path.join(root, 'home');
const hooks = path.join(root, 'hooks');
fs.mkdirSync(home);
fs.mkdirSync(hooks);
fs.writeFileSync(path.join(hooks, 'pre-commit'), '#!/bin/sh\nexit 97\n', { mode: 0o755 });
const config = path.join(home, '.gitconfig');
fs.writeFileSync(config, mode === 'empty' ? '' : `[core]\n\thooksPath = ${JSON.stringify(hooks)}\n`);
const worker = path.join(root, 'worker.ts');
fs.writeFileSync(worker, `
import * as fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import { createShipSkipFixture } from ${JSON.stringify(path.join(import.meta.dir, 'helpers/ship-skip-actor.ts'))};
const fixture = createShipSkipFixture('identity-only setup control', ${JSON.stringify(path.join(root, 'fixture'))});
const git = (...args: string[]) => {
const result = spawnSync('git', args, { cwd: fixture.repo, env: fixture.env, encoding: 'utf8', timeout: 5000 });
if (result.status !== 0) throw new Error(result.stderr);
return result.stdout.trim();
};
console.log(JSON.stringify({
commits: git('rev-list', '--count', 'HEAD'),
branch: git('branch', '--show-current'),
top: git('rev-parse', '--show-toplevel'),
config: fs.readFileSync(fixture.repo + '/.git/config', 'utf8'),
authors: git('log', '--format=%an <%ae>'),
source: fs.readFileSync(fixture.repo + '/invoice.ts', 'utf8'),
}));
`);
const result = spawnSync(process.execPath, [worker], {
env: { PATH: process.env.PATH, HOME: home, GIT_CONFIG_GLOBAL: config,
GIT_CONFIG_SYSTEM: os.devNull, GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_COUNT: '0' },
encoding: 'utf8', timeout: 20000,
});
expect(result.status, result.stderr).toBe(0);
const evidence = JSON.parse(result.stdout.trim());
expect(evidence.commits).toBe('2');
expect(evidence.branch).toBe('fixture/queued-finding');
expect(evidence.top).toBe(fs.realpathSync(path.join(root, 'fixture/project')));
expect(evidence.authors.split('\n')).toHaveLength(2);
expect(evidence.authors).toMatch(/\S+ <[^<>\s]+>/);
expect(evidence.config).not.toMatch(/include|hooksPath|remote|credential/i);
expect(evidence.source).toContain('=> true;');
} finally { fs.rmSync(root, { recursive: true, force: true }); }
});
test('Git routing variables fail before any Git invocation or foreign state write', () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const worker = path.join(artifacts, 'git-routing.ts');
fs.writeFileSync(worker, `
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { createShipSkipFixture } from ${JSON.stringify(path.resolve(import.meta.dir, 'helpers/ship-skip-actor.ts'))};
const keys = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'];
for (const key of keys) delete process.env[key];
const root = ${JSON.stringify(artifacts)};
const foreign = path.join(root, 'foreign');
const initialized = spawnSync('git', ['init', '-q', foreign], { env: process.env, encoding: 'utf8', timeout: 10000 });
if (initialized.status !== 0) throw new Error(initialized.stderr);
fs.writeFileSync(path.join(foreign, 'sentinel'), 'foreign worktree must not change');
const bin = path.join(root, 'bin'); fs.mkdirSync(bin);
const calls = path.join(root, 'git-calls');
fs.writeFileSync(path.join(bin, 'git'), ${JSON.stringify(`#!/bin/sh\nprintf called >> ${quote(path.join(artifacts, 'git-calls'))}\nexec ${quote(Bun.which('git')!)} "$@"\n`)}, { mode: 0o755 });
process.env.PATH = bin + ':' + process.env.PATH;
const snapshot = () => fs.readdirSync(foreign, { recursive: true }).map(file => String(file)).sort().map(file => {
const full = path.join(foreign, file), stat = fs.lstatSync(full);
return [file, stat.mode, stat.isFile() ? fs.readFileSync(full).toString('hex') : null];
});
const before = JSON.stringify(snapshot());
const destinations = [path.join(foreign, '.git'), foreign, path.join(foreign, '.git'), path.join(foreign, '.git/index'), path.join(foreign, '.git/objects'), path.join(foreign, '.git/objects')];
const results = [];
for (const [index, key] of keys.entries()) {
process.env[key] = destinations[index];
let error;
try { createShipSkipFixture('routing control', path.join(root, 'fixture-' + index)); } catch (caught) { error = String(caught); }
delete process.env[key];
results.push({ key, error, gitCalled: fs.existsSync(calls), foreignUnchanged: JSON.stringify(snapshot()) === before });
}
console.log(JSON.stringify(results));
`);
try {
const result = spawnSync(process.execPath, [worker], { encoding: 'utf8', timeout: 20000 });
expect(result.status, result.stderr).toBe(0);
const evidence = JSON.parse(result.stdout.trim());
fs.writeFileSync(path.join(artifacts, 'git-routing.json'), JSON.stringify(evidence), { mode: 0o600 });
expect(evidence).toHaveLength(6);
for (const row of evidence) {
expect(row.error).toContain(`Refusing ambient Git routing: ${row.key}`);
expect(row.gitCalled).toBe(false);
expect(row.foreignUnchanged).toBe(true);
}
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
for (const kind of ['generation-error', 'fixture-error', 'generation-exhausted', 'fixture-exhausted'] as const) {
test(`case-entry deadline records and cleans ${kind} without launching a query`, () => {
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'sskip-art-'));
const worker = path.join(artifacts, 'worker.ts');
fs.writeFileSync(worker, `
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { mock } from 'bun:test';
const directory = ${JSON.stringify(artifacts)};
process.env.TMPDIR = directory;
const kind = ${JSON.stringify(kind)};
const realNow = Date.now;
let elapsed = 0;
Date.now = () => realNow() + elapsed;
const generatorPath = ${JSON.stringify(path.resolve(import.meta.dir, '../scripts/gen-skill-docs.ts'))};
const generator = await import(generatorPath);
const generate = generator.runGeneration;
mock.module(generatorPath, () => ({ ...generator, runGeneration: async (...args: any[]) => {
if (kind === 'generation-error') throw new Error('Injected generation error');
const result = await generate(...args);
if (kind === 'generation-exhausted') elapsed = ${CAPTURE_MS};
return result;
} }));
mock.module('node:fs', () => ({ ...fs, default: fs, writeFileSync: (file: any, ...args: any[]) => {
if (String(file).endsWith('/invoice.ts')) {
if (kind === 'fixture-error') throw new Error('Injected fixture error');
if (kind === 'fixture-exhausted') elapsed = ${CAPTURE_MS};
}
return fs.writeFileSync(file, ...args);
} }));
const { runShipSkipActor } = await import(${JSON.stringify(path.resolve(import.meta.dir, 'helpers/ship-skip-actor.ts'))});
const records: any[] = [];
let queries = 0, error;
try { await runShipSkipActor((entry: any) => records.push(entry), () => { queries++; throw new Error('Unexpected query launch'); }, directory); }
catch (caught) { error = String(caught); }
const saved = fs.readdirSync(directory).find(file => file.startsWith('ship-skipped-queued-finding-'));
const payload = saved ? JSON.parse(fs.readFileSync(path.join(directory, saved), 'utf8')) : null;
console.log(JSON.stringify({ error, queries, records: records.map(record => ({ passed: record.passed, error: record.error })), payload,
leftovers: fs.readdirSync(directory).filter(file => file.startsWith('sskip-')),
rootsGone: payload?.roots?.every((root: string) => !fs.existsSync(root)) ?? false }));
`);
try {
const result = spawnSync(process.execPath, [worker], { encoding: 'utf8', timeout: 20000 });
expect(result.status, result.stderr).toBe(0);
const evidence = JSON.parse(result.stdout.trim());
fs.writeFileSync(path.join(artifacts, `setup-${kind}.json`), JSON.stringify(evidence), { mode: 0o600 });
expect(evidence.queries).toBe(0);
expect(evidence.records).toHaveLength(1);
expect(evidence.records[0].passed).toBe(false);
expect(evidence.error).toContain(kind.endsWith('exhausted') ? 'deadline exhausted' : `Injected ${kind.split('-')[0]} error`);
expect(evidence.payload.deadline - evidence.payload.started).toBe(CAPTURE_MS - 20000);
expect(evidence.leftovers).toEqual([]);
expect(evidence.rootsGone).toBe(true);
expect(evidence.payload.roots.length).toBe(kind.startsWith('fixture') ? 1 : 0);
} finally { fs.rmSync(artifacts, { recursive: true, force: true }); }
});
}
test('fixture ships actual generated decision sections and isolates all model writes', async () => {
const workflow = await shipSkipWorkflow();
const fixture = createShipSkipFixture(workflow);
try {
expect(fs.readFileSync(fixture.workflowPath, 'utf8')).toBe(workflow);
for (const marker of ['### Step 9.3:', '## Step 9.4:', '### Finish the adversarial phase', 'gstack-review-log', '--finish REVIEW_START']) expect(workflow).toContain(marker);
expect(workflow).not.toContain('### Decide whether to repeat Step 9');
expect(workflow).not.toContain('### Refresh learnings');
expect(fixture.prompt).toContain('earlier reviewers and the rediscovery are explicitly synthetic fixture inputs');
expect(fixture.prompt).toContain('persist completed:false and converged:false');
expect(fixture.prompt).toContain('No product writes, direct receipt/config access');
expect(fs.realpathSync(fixture.product).startsWith(fs.realpathSync(fixture.root) + path.sep)).toBe(true);
expect(fs.statSync(fixture.product).mode & 0o777).toBe(0o644);
} finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
});