Files
gstack/test/docsync-atomic-writes.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

185 lines
13 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { DOC_PATH, fixtureDocs } from './helpers/docsync-fixture';
import { docsWriteFailures, observeDocsWrites } from './helpers/docsync-observer';
import { decodeQAInotify, qaWriteVerdict, type QAWriteObservation } from './helpers/qa-functional-observer';
import type { SkillTestResult } from './helpers/session-runner';
function nativeResult(fixture: ReturnType<typeof fixtureDocs>) {
const result = { exitReason: 'success', transcript: [], toolCalls: [] } as unknown as SkillTestResult;
return {
result,
replace(source: string, content: string, name: 'Edit' | 'Write' = 'Edit', parent: string | null = null) {
const file = path.join(fixture.repo, DOC_PATH);
const original = fs.readFileSync(file, 'utf8');
const id = `native-${result.toolCalls.length}`;
const input = name === 'Edit' ? { file_path: file, old_string: original, new_string: content, replace_all: false }
: { file_path: file, content };
result.transcript.push({ type: 'assistant', parent_tool_use_id: parent, message: { content: [{ type: 'tool_use', id, name, input }] } });
const fd = fs.openSync(path.join(fixture.repo, source), 'wx', 0o600);
fs.writeFileSync(fd, content);
fs.fchmodSync(fd, fs.statSync(file).mode & 0o777);
fs.closeSync(fd);
fs.renameSync(path.join(fixture.repo, source), file);
const output = 'Native callback result text is not mutation authority.';
result.toolCalls.push({ tool: name, input, output });
result.transcript.push({ type: 'user', parent_tool_use_id: parent,
message: { content: [{ type: 'tool_result', tool_use_id: id, content: output }] },
tool_use_result: name === 'Edit' ? { filePath: file, oldString: original, newString: content, replaceAll: false, originalFile: original, userModified: false }
: { type: 'update', filePath: file, content, originalFile: original, userModified: false },
});
},
};
}
const sibling = (name: string) => path.join(path.dirname(DOC_PATH), name);
type Capture = { fixture: ReturnType<typeof fixtureDocs>; result: SkillTestResult; observation: QAWriteObservation };
let captured: Capture;
(process.platform === 'linux' ? describe : describe.skip)('native docs atomic replacement attribution', () => {
beforeAll(async () => {
const fixture = fixtureDocs('updated');
const observer = await observeDocsWrites(fixture);
const native = nativeResult(fixture);
native.replace(sibling('arbitrary-sibling'), 'First native content.\n');
observer.drain();
captured = { fixture, result: native.result, observation: observer.stop() };
});
afterAll(() => captured?.fixture.clean());
const verdict = (capture: Capture) => docsWriteFailures(capture.observation, [DOC_PATH], capture);
const copy = (): Capture => ({ fixture: captured.fixture, result: structuredClone(captured.result), observation: structuredClone(captured.observation) });
test('decodes unsigned kernel cookies without changing event fields', () => {
const bytes = Buffer.alloc(32);
bytes.writeInt32LE(7, 0); bytes.writeUInt32LE(0x40, 4); bytes.writeUInt32LE(0xfedcba98, 8); bytes.writeUInt32LE(16, 12);
bytes.write('sibling', 16);
expect(decodeQAInotify(bytes)).toEqual([{ wd: 7, mask: 0x40, cookie: 0xfedcba98, name: 'sibling' }]);
});
test('attributes a real kernel lifecycle without authorizing its filename', () => {
expect(captured.observation.complete).toBe(true);
expect(verdict(captured)).toEqual([]);
expect(docsWriteFailures(captured.observation, [DOC_PATH])).toEqual([`forbidden docs write: ${sibling('arbitrary-sibling')}`]);
expect(captured.observation.before[sibling('arbitrary-sibling')]).toBeUndefined();
expect(captured.observation.after[sibling('arbitrary-sibling')]).toBeUndefined();
const moves = captured.observation.events.filter(event => event.mask === 0x40 || event.mask === 0x80);
expect(moves).toHaveLength(2);
expect(moves[0].cookie).toBeGreaterThan(0);
expect(moves[0].cookie).toBe(moves[1].cookie);
expect(captured.observation.changed.sort()).toEqual(['.qa-state/.observer-check', DOC_PATH].sort());
});
test('does not mutate raw evidence or broaden either QA verdict', () => {
const original = structuredClone(captured.observation);
const legacy = { ...original, events: original.events.map(({ cookie, ...event }) => event) } as QAWriteObservation;
verdict(captured);
expect(captured.observation).toEqual(original);
for (const mode of ['qa', 'qa-only'] as const) {
expect(qaWriteVerdict(captured.observation, mode)).toEqual(qaWriteVerdict(legacy, mode));
expect(qaWriteVerdict(captured.observation, mode)).toContain(`forbidden ${mode} write: ${sibling('arbitrary-sibling')}`);
}
});
test('read-only and actor source permissions never authorize an atomic document write', () => {
expect(docsWriteFailures(captured.observation, [], captured).length).toBeGreaterThan(0);
expect(docsWriteFailures(captured.observation, ['app.ts'], captured).length).toBeGreaterThan(0);
expect(docsWriteFailures(captured.observation, [DOC_PATH], { ...captured, readOnly: true }).length).toBeGreaterThan(0);
});
const corruptions: Record<string, (capture: Capture) => void> = {
'missing cookies': c => { c.observation.events.forEach(e => { delete (e as any).cookie; }); },
'zero cookies': c => { c.observation.events.forEach(e => { e.cookie = 0; }); },
'mismatched cookie': c => { c.observation.events.find(e => e.mask === 0x80)!.cookie++; },
'reused cookie': c => { c.observation.events.push({ ...c.observation.events.find(e => e.mask === 0x40)! }); },
'missing rename source': c => { c.observation.events = c.observation.events.filter(e => e.mask !== 0x40); },
'missing rename destination': c => { c.observation.events = c.observation.events.filter(e => e.mask !== 0x80); },
'rename to other path': c => { c.observation.events.find(e => e.mask === 0x80)!.path = 'app.ts'; },
'protected destination': c => { c.observation.events.find(e => e.mask === 0x80)!.path = '.git/config'; },
'cross-directory source': c => { c.observation.events.filter(e => e.path === sibling('arbitrary-sibling')).forEach(e => { e.path = 'elsewhere'; }); },
'preexisting sibling': c => { c.observation.before[sibling('arbitrary-sibling')] = c.observation.before[DOC_PATH]; },
'surviving sibling': c => { c.observation.after[sibling('arbitrary-sibling')] = c.observation.after[DOC_PATH]; },
'missing create': c => { c.observation.events = c.observation.events.filter(e => e.path !== sibling('arbitrary-sibling') || e.mask !== 0x100); },
'missing modify': c => { c.observation.events = c.observation.events.filter(e => e.path !== sibling('arbitrary-sibling') || e.mask !== 0x2); },
'missing close': c => { c.observation.events = c.observation.events.filter(e => e.path !== sibling('arbitrary-sibling') || e.mask !== 0x8); },
'post-close sibling write': c => { const at = c.observation.events.findIndex(e => e.mask === 0x40); c.observation.events.splice(at, 0, { ...c.observation.events[at], mask: 2, cookie: 0 }); },
'forged directory source': c => { c.observation.events.find(e => e.path === sibling('arbitrary-sibling'))!.mask |= 0x40000000; },
'unrelated syscall': c => { c.observation.events.push({ path: '.git/config', mask: 2, cookie: 0, at: 0 }); },
'target write-restore': c => { c.observation.events.push({ path: DOC_PATH, mask: 2, cookie: 0, at: 0 }); },
'target chmod-restore': c => { c.observation.events.push({ path: DOC_PATH, mask: 4, cookie: 0, at: 0 }); },
'mode changed': c => { c.observation.after[DOC_PATH] = c.observation.after[DOC_PATH].replace(/^\d+:/, '384:'); },
'incomplete observation': c => { c.observation.complete = false; },
'kernel failure': c => { c.observation.failures.push('kernel queue overflow'); },
'missing transcript': c => { c.result.transcript = []; },
'missing completion': c => { c.result.transcript.pop(); },
'failed completion': c => { c.result.transcript[1].message.content[0].is_error = true; },
'malformed error flag': c => { c.result.transcript[1].message.content[0].is_error = 'false'; },
'different parent scope': c => { c.result.transcript[1].parent_tool_use_id = 'other-parent'; },
'duplicate tool identity': c => { c.result.transcript.unshift(structuredClone(c.result.transcript[0])); },
'duplicate completion': c => { c.result.transcript.push(structuredClone(c.result.transcript[1])); },
'unrelated successful edit': c => { c.result.transcript[0].message.content[0].input.file_path = path.join(c.fixture.repo, 'README.md'); },
'forged success prose': c => { c.result.transcript[1].message.content[0].content = 'updated successfully'; delete c.result.transcript[1].tool_use_result; },
'forged result path': c => { c.result.transcript[1].tool_use_result.filePath = path.join(c.fixture.repo, 'app.ts'); },
'forged original content': c => { c.result.transcript[1].tool_use_result.originalFile += 'forged'; },
'forged replacement payload': c => { c.result.transcript[1].tool_use_result.newString += 'forged'; },
'unbound final content': c => { c.observation.after[DOC_PATH] = c.observation.before[DOC_PATH]; },
'user-modified payload': c => { c.result.transcript[1].tool_use_result.userModified = true; },
'unsettled capture': c => { c.result.exitReason = 'timeout'; },
'shell outside authority': c => { c.result.toolCalls.push({ tool: 'Bash', input: { command: 'echo forged > hidden' }, output: '' }); },
};
for (const [name, corrupt] of Object.entries(corruptions)) {
test(`rejects ${name}`, () => {
const control = copy();
expect(verdict(control)).toEqual([]);
corrupt(control);
expect(verdict(control).length).toBeGreaterThan(0);
});
}
test.each(['ordered', 'overlap', 'restore', 'reuse', 'extra-rename', 'chain-mismatch', 'write-payload', 'write-type'])('binds multiple real replacements: %s', async fault => {
const fixture = fixtureDocs('updated');
const observer = await observeDocsWrites(fixture);
const original = fs.readFileSync(path.join(fixture.repo, DOC_PATH), 'utf8');
const native = nativeResult(fixture);
try {
native.replace(sibling('first'), 'first\n', 'Edit', 'native-child');
observer.drain();
native.replace(sibling(fault === 'reuse' ? 'first' : 'second'), fault === 'restore' ? original : 'second\n', 'Write', 'native-child');
observer.drain();
const observation = observer.stop();
if (fault === 'overlap') [native.result.transcript[1], native.result.transcript[2]] = [native.result.transcript[2], native.result.transcript[1]];
if (fault === 'extra-rename') native.result.transcript.splice(2);
if (fault === 'chain-mismatch') native.result.transcript[3].tool_use_result.originalFile = original;
if (fault === 'write-payload') native.result.transcript[3].tool_use_result.content = 'forged';
if (fault === 'write-type') native.result.transcript[3].tool_use_result.type = 'create';
const failures = docsWriteFailures(observation, [DOC_PATH], { fixture, result: native.result });
if (fault === 'ordered') expect(failures).toEqual([]);
else expect(failures.length).toBeGreaterThan(0);
} finally { fixture.clean(); }
});
test.each(['write-restore', 'rename-other', 'survive', 'mode', 'hardlink', 'symlink', 'protected', 'preexisting', 'read-only'])('rejects actual filesystem %s', async fault => {
const fixture = fixtureDocs('updated');
const source = sibling('actual-sibling');
const target = path.join(fixture.repo, DOC_PATH);
if (fault === 'preexisting') fs.writeFileSync(path.join(fixture.repo, source), 'already here');
const observer = await observeDocsWrites(fixture);
const native = nativeResult(fixture);
try {
if (fault === 'preexisting') fs.unlinkSync(path.join(fixture.repo, source));
native.replace(source, 'changed\n');
observer.drain();
if (fault === 'write-restore') { fs.writeFileSync(target, 'unauthorized'); fs.writeFileSync(target, 'changed\n'); }
if (fault === 'rename-other') { fs.renameSync(target, path.join(fixture.repo, 'other')); fs.renameSync(path.join(fixture.repo, 'other'), target); }
if (fault === 'survive') fs.writeFileSync(path.join(fixture.repo, source), 'survived');
if (fault === 'mode') fs.chmodSync(target, 0o600);
if (fault === 'hardlink') fs.linkSync(target, path.join(fixture.repo, source));
if (fault === 'symlink') fs.symlinkSync(target, path.join(fixture.repo, source));
if (fault === 'protected') fs.appendFileSync(path.join(fixture.repo, '.git/config'), '\n');
const observation = observer.stop();
expect(docsWriteFailures(observation, [DOC_PATH], { fixture, result: native.result, readOnly: fault === 'read-only' }).length).toBeGreaterThan(0);
} finally { fixture.clean(); }
});
});