test(qa-evidence): pin the post-repair edit's mtime to capture 002's completion

The GPT voice's /review run on 51f1e02 failed qa-evidence.test.ts:547 once:
capture 003 reported no required probe remaining, so capture 002 (started
before the edit) counted as post-edit. The test wrote the edit right after
002 returned, a gap of one child run (7-27 ms measured here; the voice's
machine ran this test in 59 ms against 183 ms here, so a few ms there),
while an ext4 mtime comes from the kernel's coarse clock and lags
Date.now() by up to a tick (0.5-4.5 ms measured here).

The edit's mtime is now set to 002's recorded completedAt, so 002 started
strictly before the boundary and 003 starts after it on any machine. The
product boundary is unchanged: tick-scale imprecision only matters when a
capture starts within milliseconds before an edit, and a stricter margin
would refuse a real edit && capture chain.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-18 00:17:22 +00:00
1 parent 47f4543465
commit e6c13866a8
1 file changed
+9 -1
+9 -1
View File
@@ -535,15 +535,23 @@ test('post-repair required probes count only captures started after the last fix
const [duplicate, partial] = [probe('console.log(JSON.stringify({scenario:"duplicate"}))'), probe('console.log(JSON.stringify({scenario:"partial"}))')];
const env = { ...process.env, GSTACK_QA_REQUIRED_PROBES: JSON.stringify([duplicate.join(' '), partial.join(' ')]),
GSTACK_QA_REQUIRED_AFTER_EDIT: JSON.stringify({ paths: [src], since: new Date(Date.now() - 30_000).toISOString() }) };
let last: Record<string, unknown> = {};
const capture = (captureId: string, argv: string[]) => {
const after = captureId === '001' ? [] : ['--after', String(Number(captureId) - 1).padStart(3, '0'), '--hypothesis', 'The previous scenario result shows which invariant the next probe must check.'];
const result = spawnSync(process.execPath, [CLI, 'capture', f.root, captureId, '--timeout-ms', '4000', ...after, '--', ...argv], { cwd: f.root, encoding: 'utf8', timeout: 10_000, env });
expect(result.status, result.stderr).toBe(0);
return receipt(result.stdout).requiredRemaining as string[];
last = receipt(result.stdout);
return last.requiredRemaining as string[];
};
expect(capture('001', duplicate)).toEqual([duplicate.join(' '), partial.join(' ')]);
expect(capture('002', partial)).toEqual([duplicate.join(' '), partial.join(' ')]);
// The edit's mtime is pinned to capture 002's completion: a real write's mtime comes from the kernel's coarse clock,
// which lags Date.now() by up to a tick, while 002 started only one child run (a few ms on a fast machine) earlier;
// the GPT voice's run on 51f1e02 counted 002 as post-edit that way. 002 started before this instant and 003 after it.
const edit = new Date(Date.parse(String(last.completedAt)));
expect(Date.parse(String(last.startedAt))).toBeLessThan(edit.getTime());
fs.writeFileSync(path.join(src, 'worker.ts'), 'export const effect = 2;\n');
fs.utimesSync(path.join(src, 'worker.ts'), edit, edit);
expect(capture('003', duplicate)).toEqual([partial.join(' ')]);
expect(capture('004', partial)).toEqual([]);
const secondEdit = new Date(Date.now() + 60_000);