Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_diamondcwd.mjs

8.8 KiB, 1 run

created by r2519314175:359, 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// verify_diamondcwd.mjs — where a Diamond's command starts, now that the
2// Diamond itself is not on the machine.
3//
4// A Diamond lives in the browser's storage under `diamonds/<id>` whatever
5// workspace root is open (`is_store_path`, src/tools.rs). `default_cwd` used to
6// hand `Tool::Run` that very path, and `fence_spec` turned it into
7// `<granted-root>/diamonds/<id>` — a directory on the user's disk that Daimond
8// no longer writes and that in general does not exist. The hand refuses a `cwd`
9// it cannot canonicalise, so every command in a Diamond with no explicit `cwd`
10// would have been refused, for ever, with "cannot be resolved to a directory on
11// this machine": true, unhelpful, and pointing at a path the user never chose.
12//
13// What is pinned:
14// * a Diamond's command starts in its first ATTACHED folder, which is a real
15// place on the machine and inside the fence;
16// * a read-only attachment is still somewhere to start;
17// * a Diamond with nothing attached is refused in words that name the thing to
18// do about it, and the hand is never asked;
19// * an explicit `cwd` from the model is still honoured;
20// * an ordinary, unscoped turn still starts at the granted root.
21//
22// The hand is a stand-in that records what it was sent — the question here is
23// which `cwd` the ENGINE composes, and a real hand would answer it by refusing
24// a directory that does not exist on this machine, which is a different fact.
25// dev/verify_scope.mjs drives the real binary for what the fence contains.
26//
27// Run with dev/serve.mjs (DAIMOND_PORT, default 8777) up.
28//
29// node dev/verify_diamondcwd.mjs
30import { open } from './harness.mjs';
31
32const ok = [], bad = [];
33const check = (name, pass, detail) => {
34 (pass ? ok : bad).push(name);
35 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
36};
37
38const ROOT = '/home/somebody/project';
39
40const s = await open({ name: 'diamondcwd', connect: false });
41const p = s.page;
42await p.waitForTimeout(1500);
43
44await p.evaluate(async (root) => {
45 const mod = await import('../pkg/oxedyne_daimond.js');
46 window.__d = { mod, root };
47 // A stand-in hand: it says it can fence, and it writes down every spec.
48 window.__sent = [];
49 window.DaimondHand = {
50 status: () => Promise.resolve(JSON.stringify({
51 paired: true, transport: 'machine', machine: 'test', os: 'linux',
52 root: root, caps: ['fence:linux', 'landlock:abi-8'],
53 })),
54 run: (spec) => {
55 window.__sent.push(JSON.parse(spec));
56 return Promise.resolve(JSON.stringify({
57 t: 'exec_result', exit: 0, stdout: 'ran', stderr: '',
58 out_bytes: 3, err_bytes: 0,
59 }));
60 },
61 };
62 window.__app = (scope) => {
63 const app = new mod.DaimondApp(
64 'http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true);
65 if (scope) {
66 app.set_diamond_scope(scope.own, JSON.stringify(scope.attached || []),
67 JSON.stringify(scope.read_only || []), JSON.stringify(scope.toolkits || []));
68 }
69 return app;
70 };
71 // One `run`, and what the hand was sent for it.
72 window.__run = async (scope, args) => {
73 window.__sent = [];
74 const app = __app(scope);
75 const said = await app.run_tool('run', JSON.stringify(args));
76 return { said: said, sent: window.__sent };
77 };
78}, ROOT);
79
80const run = (scope, args) => p.evaluate(([sc, a]) => __run(sc, a), [scope, args]);
81
82// ── A Diamond with a folder attached ────────────────────────────────────
83
84const attached = await run(
85 { own: 'diamonds/d1', attached: ['src/api'] }, { argv: ['ls'] });
86check('a Diamond with a folder attached runs its command there',
87 attached.sent.length === 1 && attached.sent[0].cwd === ROOT + '/src/api',
88 JSON.stringify(attached.sent.map((x) => x.cwd)) + ' | ' + attached.said.slice(0, 80));
89check('and not in its own directory, which is not on this machine at all',
90 !JSON.stringify(attached.sent).includes('diamonds/d1"') || attached.sent[0].cwd !== ROOT + '/diamonds/d1',
91 attached.sent.length ? attached.sent[0].cwd : '(nothing was sent)');
92
93const readOnly = await run(
94 { own: 'diamonds/d2', attached: [], read_only: ['refs'] }, { argv: ['ls'] });
95check('a read-only attachment is still somewhere to start',
96 readOnly.sent.length === 1 && readOnly.sent[0].cwd === ROOT + '/refs',
97 JSON.stringify(readOnly.sent.map((x) => x.cwd)) + ' | ' + readOnly.said.slice(0, 80));
98
99// ── A Diamond with nothing attached ─────────────────────────────────────
100
101const bare = await run({ own: 'diamonds/d3', attached: [] }, { argv: ['ls'] });
102check('a Diamond with nothing attached is refused',
103 /^Refused/.test(bare.said), bare.said.slice(0, 120));
104check('and told what to do about it, in the user\'s own vocabulary',
105 /attach/i.test(bare.said) && /Workspace panel/i.test(bare.said)
106 && /cwd/.test(bare.said),
107 bare.said.slice(0, 200));
108check('and the hand was never asked, so nothing ran',
109 bare.sent.length === 0, JSON.stringify(bare.sent).slice(0, 120));
110
111// ── What the model asked for still wins ─────────────────────────────────
112
113const named = await run(
114 { own: 'diamonds/d4', attached: ['src/api'] }, { argv: ['ls'], cwd: 'src/api/inner' });
115check('a cwd the model named is still honoured',
116 named.sent.length === 1 && named.sent[0].cwd === ROOT + '/src/api/inner',
117 JSON.stringify(named.sent.map((x) => x.cwd)));
118
119// ── "." is not a place, it is "here" ────────────────────────────────────
120//
121// A daimon writes `cwd: "."` because it is the most ordinary working directory
122// there is. Taken literally it normalises to the workspace ROOT, which a scoped
123// turn may not run in, so it met "'.' is not in this chat's workspace" — correct
124// and useless, and one call spent every time. Three live self-development runs on
125// 2026-08-24 were lost to it. It is now the same request as no `cwd` at all.
126const dot = await run(
127 { own: 'diamonds/d6', attached: ['src/api'] }, { argv: ['ls'], cwd: '.' });
128check('a command asked for in "." runs in the folder the user marked',
129 dot.sent.length === 1 && dot.sent[0].cwd === ROOT + '/src/api',
130 JSON.stringify(dot.sent.map((x) => x.cwd)) + ' | ' + dot.said.slice(0, 100));
131
132// The empty spellings of the same thing, so the answer cannot be one branch that
133// happens to catch a full stop.
134const dotSlash = await run(
135 { own: 'diamonds/d7', attached: ['src/api'] }, { argv: ['ls'], cwd: './' });
136check('and so does one asked for in "./"',
137 dotSlash.sent.length === 1 && dotSlash.sent[0].cwd === ROOT + '/src/api',
138 JSON.stringify(dotSlash.sent.map((x) => x.cwd)));
139
140// A Diamond with nothing on the machine gets the sentence that says what to do,
141// because "." now takes the same branch an absent cwd takes. It used to get a
142// sentence about ".", which names nothing anybody can act on.
143const dotBare = await run({ own: 'diamonds/d8', attached: [] }, { argv: ['ls'], cwd: '.' });
144check('a Diamond with nothing attached is refused in the words that name the fix',
145 /^Refused/.test(dotBare.said) && /attach/i.test(dotBare.said)
146 && /Workspace panel/i.test(dotBare.said) && dotBare.sent.length === 0,
147 dotBare.said.slice(0, 160));
148
149// NOTHING IS WIDENED. The two refusals either side of "." are untouched: a named
150// place outside the Diamond, and an absolute path.
151const dotAbs = await run(
152 { own: 'diamonds/d9', attached: ['src/api'] }, { argv: ['ls'], cwd: '/etc' });
153check('an absolute cwd is still refused, and told which convention it met',
154 /^Refused/.test(dotAbs.said) && /is absolute/.test(dotAbs.said)
155 && /relative to the workspace/.test(dotAbs.said) && dotAbs.sent.length === 0,
156 dotAbs.said.slice(0, 140));
157
158// A Diamond may not name a cwd outside its own workspace, which is a different
159// refusal and must not be swallowed by the new one.
160const outside = await run(
161 { own: 'diamonds/d5', attached: ['src/api'] }, { argv: ['ls'], cwd: 'elsewhere' });
162check('and one outside the Diamond is still refused as out of scope',
163 /^Refused/.test(outside.said) && /not in this Diamond's workspace/.test(outside.said)
164 && outside.sent.length === 0,
165 outside.said.slice(0, 120));
166
167// ── The ordinary turn is untouched ──────────────────────────────────────
168
169const plain = await run(null, { argv: ['ls'] });
170check('an unscoped turn still starts at the granted root',
171 plain.sent.length === 1 && plain.sent[0].cwd === ROOT,
172 JSON.stringify(plain.sent.map((x) => x.cwd)) + ' | ' + plain.said.slice(0, 80));
173
174const noise = s.errs.filter((e) =>
175 !/favicon|ERR_ABORTED|net::ERR|Failed to load resource/i.test(e));
176check('the page threw nothing along the way', noise.length === 0, noise.slice(0, 3).join(' | '));
177
178await s.close();
179console.log('\n' + ok.length + ' ok, ' + bad.length + ' failed');
180process.exit(bad.length ? 1 : 0);