Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_normalwrite.mjs

2.9 KiB, 1 run

created by r2519314175:551, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// The stale-write guard must not get in the way of ordinary work: a new file, a
2// read, and then the agent's own update of what it read, all in one chat.
3//
4// THE PATHS ARE THE CHAT'S OWN SCRATCH, and must stay that way. Until 2026-08-12
5// this wrote `n.txt` at the workspace ROOT. Since the chat fence landed, every
6// chat is confined to `chats/<id>/work` (`scopeChatTo`, www/js/daimond.js), and
7// `Tool::guard` (src/tools.rs:5490) refuses a root path BEFORE the `Tool::FileWrite`
8// arm at :5894 ever runs. The refusal comes back as an ordinary tool result: nothing
9// is written, nothing throws, and the OPFS read below then died on a file that had
10// never been created. Nothing about the stale-write guard was measured at all.
11import { open, chat, newChat, errors } from './harness.mjs';
12
13const ok = [], bad = [];
14const check = (name, pass, detail) => {
15 (pass ? ok : bad).push(name + (detail ? ' — ' + detail : ''));
16 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
17};
18
19const s = await open({ name: 'normalwrite' });
20await newChat(s);
21
22// Where this chat may write, asked of the app rather than composed here: the fence
23// is built from `chatScratch`, so anything else is a second opinion about it.
24const dir = await s.page.evaluate(() => {
25 const f = window.DaimondAttach.focus();
26 return f && f.id ? window.DaimondAttach.chatScratch(f.id) : '';
27});
28check('the chat has a scratch folder to work in', !!dir, dir || '(no chat in focus)');
29const N = dir + '/n.txt';
30
31const first = await chat(s, `@tool file_write {"path":"${N}","content":"first"}`);
32check('a new file inside the workspace is written, not refused',
33 !/Refused/.test(first), first.slice(-160).replace(/\n/g, ' | '));
34
35await chat(s, `@tool file_read {"path":"${N}"}`); // read: records the hash
36const out = await chat(s, `@tool file_write {"path":"${N}","content":"second"}`); // its own update
37
38const after = await s.page.evaluate(async (p) => {
39 const parts = p.split('/');
40 let d = await navigator.storage.getDirectory();
41 for (const seg of parts.slice(0, -1)) d = await d.getDirectoryHandle(seg);
42 const f = await d.getFileHandle(parts[parts.length - 1]);
43 return await (await f.getFile()).text();
44}, N).catch((e) => '(' + String(e).split('\n')[0] + ')');
45
46console.log('normal write chain result:', JSON.stringify(after), '| refused?', /changed on disk/.test(out));
47check('NORMAL WRITES UNAFFECTED — the agent\'s own update of a file it read lands',
48 after === 'second', JSON.stringify(after));
49check('and the stale-write guard said nothing, because nothing changed underneath',
50 !/changed on disk/.test(out), out.slice(-160).replace(/\n/g, ' | '));
51
52const errs = errors(s).filter(e => !/502|Bad Gateway/.test(e));
53check('nothing threw', errs.length === 0, errs.slice(0, 2).join(' | '));
54
55await s.close();
56console.log(`\n${ok.length} passed, ${bad.length} failed`);
57if (bad.length) { bad.forEach(b => console.log(' FAILED: ' + b)); process.exit(1); }