Files
gstack/test/bootstrap-session-lifecycle.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

89 lines
4.4 KiB
TypeScript

import { describe, 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';
describe.skipIf(process.platform !== 'linux')('bootstrap native session lifecycle hooks', () => {
test('actual runner arms cleanup before registration and bounds rejected or stalled hooks', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'bs-life-'));
try {
const script = path.join(root, 'observe.test.ts');
fs.writeFileSync(script, SCRIPT);
const result = spawnSync(process.execPath, ['test', script, '--timeout', '15000'], {
cwd: root, encoding: 'utf8', timeout: 20000,
env: {
PATH: process.env.PATH, HOME: root, GSTACK_HOME: path.join(root, 'state'), EVALS_HERMETIC: '0',
BOOTSTRAP_SESSION_SOURCE: path.join(import.meta.dir, 'helpers/session-runner.ts'),
},
});
expect(result.status, result.stderr + result.stdout).toBe(0);
const observations = JSON.parse(fs.readFileSync(path.join(root, 'observations.json'), 'utf8'));
expect(observations.length).toBe(5);
for (const row of observations) {
expect(row.handlersArmed).toBe(true);
expect(row.exited).toBe(true);
expect(row.settledCalls).toBe(1);
expect(row.elapsed).toBeLessThan(6500);
}
expect(observations[0].reason).toBe('success');
expect(observations[1].error).toContain('registration fault');
expect(observations[1].receivedPrompt).toBe(false);
expect(observations[2].error).toContain('settlement fault');
expect(observations[3].error).toContain('deadline exceeded');
expect(observations[4].error).toContain('native lifecycle failed');
expect(observations[4].causes).toEqual(['registration fault', 'settlement fault']);
} finally { fs.rmSync(root, { recursive: true, force: true }); }
}, 25000);
});
const SCRIPT = String.raw`
import { mock, test, expect } from 'bun:test';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as cp from 'node:child_process';
const spawn = cp.spawn;
const spawnSync = cp.spawnSync;
let active: any;
mock.module('child_process', () => ({
spawnSync,
spawn(command: string, args: string[], options: any) {
if (command !== 'claude') throw new Error('unexpected executable');
const child = spawn(process.execPath, ['-e',
"await Bun.stdin.text(); require('fs').writeFileSync('received-prompt','yes'); console.log(JSON.stringify({type:'result',subtype:'success',is_error:false,result:'synthetic bootstrap',num_turns:1}));"], options);
active.child = child;
return child;
},
}));
const { runSkillTest } = await import(process.env.BOOTSTRAP_SESSION_SOURCE!);
test('exercise native lifecycle', async () => {
const observations: any[] = [];
for (const scenario of ['success','start-fail','settle-fail','settle-stall','both-fail']) {
const cwd = path.join(process.cwd(), scenario); fs.mkdirSync(cwd);
active = {settledCalls:0};
const began = Date.now();
let result: any, error: any;
try {
result = await runSkillTest({prompt:'no model',workingDirectory:cwd,timeout:1000,startupGraceMs:1000,model:'fixture-no-provider',
nativeLifecycle:{
onSpawn(pid: number) {
active.handlersArmed = active.child.listenerCount('exit') > 0 && active.child.listenerCount('error') > 0;
expect(pid).toBe(active.child.pid);
if (scenario === 'start-fail' || scenario === 'both-fail') throw new Error('registration fault');
},
async onSettled(input: any) {
active.settledCalls++;
active.exited = input.exited && (active.child.exitCode !== null || active.child.signalCode !== null);
expect(input.deadline - Date.now()).toBeLessThanOrEqual(5000);
if (scenario === 'settle-fail' || scenario === 'both-fail') throw new Error('settlement fault');
if (scenario === 'settle-stall') await new Promise(()=>{});
},
},
});
} catch (e) { error=e; }
observations.push({scenario,handlersArmed:active.handlersArmed,settledCalls:active.settledCalls,exited:active.exited,reason:result?.exitReason,error:error?.message,causes:error?.errors?.map((e:any)=>e.message),elapsed:Date.now()-began,receivedPrompt:fs.existsSync(path.join(cwd,'received-prompt'))});
}
fs.writeFileSync('observations.json',JSON.stringify(observations));
},15000);
`;