Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_reflux.mjs

22.6 KiB, 1 run

created by r2519314175:633, 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_reflux.mjs — the door a daimon reaches `dev/reflux.mjs` through.
2//
3// `dev/BLOCKERS.md` B13, in the two sentences two lanes wrote it in:
4//
5// A daimon spends the user's own key on its own turn; there is no tool that lets
6// it hold a budget and send a request outside it.
7//
8// It drives a headed browser under xvfb. `run` cannot: the fence has no display
9// and the harness's own `displayFault()` refuses a forwarded one, correctly.
10//
11// Together those put `dev/reflux.mjs` -- the instrument OBJECTIVES §1 is scored on
12// -- outside a daimon's reach, so a daimon can fix its own blockers, land the
13// change, and never see the number move. This file is the reach.
14//
15// ── Why a verifier, and not a command and not another machine ────────
16//
17// `run` is not a door and cannot be made into one. `hand/src/verify.rs` says why
18// in its own opening: `listen()` is refused by `crate::seccomp`, a browser needs
19// the display server's unix socket and `seccomp::Unix::Refuse` takes that away.
20// A browser under the command fence is not a thing that is missing a display; it
21// is a thing the fence exists to prevent. So no amount of display arranging
22// reaches `reflux` through `run`.
23//
24// Reaching it over ssh on a machine with no seat -- gilgamesh -- would work, and
25// it needs a chromium and an xvfb installed on a machine that is not this one, a
26// built wasm bundle over there, and an ssh private key within a daimon's reach.
27// Three new things to trust, to get somewhere this tree already goes.
28//
29// `verify` already goes there. It runs a TRACKED script outside the command
30// fence, deliberately and on the record -- `fence:none` in the journal, the
31// bytes checked against the commit before every spawn -- with the network open
32// and a budget up to four hours. That is the trust class this measurement wants
33// and it was built for exactly this reason. What it did not have was a display
34// it could hand on and a key of its own, and those are the two halves below.
35//
36// ── The display this file starts for itself ─────────────────────────
37//
38// `verify` spawns with the hand's own environment. Start the hand from a desktop
39// session and that environment carries `DISPLAY=:0`, which `displayFault` allowed
40// -- rightly, for a person watching their own run, and disastrously for a run
41// nobody asked for. So this file starts an Xvfb of its OWN, on a display number
42// it picked because nothing held it, and hands the child that and nothing it
43// inherited. `dev/display.mjs` refuses the difference: see `UNATTENDED_VAR`
44// there, and `dev/verify_harness.mjs`'s `oldguard` break for the same guard with
45// the rule cut back out of it. Spelled without the flag ON PURPOSE: the hand reads
46// break declarations out of a verifier's own source by scanning for the flag, and it
47// cannot tell a file naming its own break from a file naming somebody else's -- so a
48// cross-reference written the obvious way made `verify` offer this file a break it
49// does not have, which it refused, ending the run with no checks at all. Found by
50// running the verb over this tree rather than by reading either file.
51//
52// ── The key this file spends from ───────────────────────────────────
53//
54// NOT the owner's. `~/.config/oxedyne/daimond/openrouter.key` is his; the daimon
55// holds its own beside its own ssh key, at
56//
57// ~/.config/oxedyne/daimond-hand/openrouter.key 0600, in a 0700 directory
58//
59// OpenRouter enforces a limit per key, so a key of its own IS a budget of its own
60// -- the first half of B13 is a second key and always was. Outside `~/usr`
61// because that tree is a Syncthing folder and a candidate for `ore init`, and a
62// credential inside it is copied to another machine whether or not any source
63// ever holds its value. Nothing in this repository holds the value, here or as a
64// fallback: an example that runs with no configuration is how a live key reached
65// a public repository from this tree on 2026-07-10 and was used by somebody else
66// nine days later. Absent, this file SKIPS and prints the one line that fixes it.
67//
68// node dev/verify_reflux.mjs
69// node dev/verify_reflux.mjs --break nodisplay # 1: no display at all, which is B13's own world
70// node dev/verify_reflux.mjs --break inheritseat # 2: the child is handed the seat it inherited
71// node dev/verify_reflux.mjs --break ownerkey # 3: no key of its own, so reflux reaches for his
72//
73// DAIMOND_REFLUX_BUDGET dollars for the paid run (default 0.35)
74// DAIMOND_REFLUX_TASK which reflux tasks to run (default homerun)
75// DAIMOND_REFLUX_WORLD world number, ports 8777+n and 9700+n (default 6)
76//
77// The paid run is ONE task by default and not the whole table. A verifier is run
78// by a suite as well as by a daimon, and a file that spent a dollar every time
79// anybody typed its name would be turned off within the week. `dev/run_all.sh`
80// skips it outright unless `DAIMOND_REFLUX_PAID` is set; a daimon asking for it by
81// name gets the run.
82
83import fs from 'node:fs';
84import os from 'node:os';
85import path from 'node:path';
86import { spawn, spawnSync } from 'node:child_process';
87import { fileURLToPath, pathToFileURL } from 'node:url';
88
89import {
90 displayFault,
91 cleanDisplayEnv,
92 INHERITED_ENV,
93 UNATTENDED_VAR,
94 OWNED_VAR,
95 SEAT_DISPLAY,
96} from './display.mjs';
97
98const HERE = path.dirname(fileURLToPath(import.meta.url));
99const ROOT = path.join(HERE, '..');
100
101/// Every break this file declares, so an unknown one is refused by name.
102const BREAKS = ['nodisplay', 'inheritseat', 'ownerkey'];
103
104const BREAK = (() => {
105 const i = process.argv.indexOf('--break');
106 const b = i >= 0 && process.argv[i + 1] ? String(process.argv[i + 1]) : '';
107 if (b && !BREAKS.includes(b)) {
108 console.log(`no such break: ${b}. This file declares: ${BREAKS.join(', ')}.`);
109 process.exit(1);
110 }
111 return b;
112})();
113
114/// Where the daimon's own provider key lives.
115///
116/// Beside its own ssh key, in the directory that is already the machine hand's
117/// and already 0700. Absolute, and built from the home directory rather than
118/// written down twice, so a scratch home in a break resolves to a scratch path.
119function daimonKeyFile(home = os.homedir()) {
120 return path.join(home, '.config/oxedyne/daimond-hand/openrouter.key');
121}
122
123/// Where the OWNER's key lives, which is the one this file must never reach for.
124///
125/// Named only to be compared against. Nothing here opens it.
126function ownerKeyFile(home = os.homedir()) {
127 return path.join(home, '.config/oxedyne/daimond/openrouter.key');
128}
129
130const SCRATCH = path.join(process.env.DAIMOND_SCRATCH
131 || path.join(os.homedir(), '.cache/daimond'), 'refluxdoor');
132const BUDGET = Number(process.env.DAIMOND_REFLUX_BUDGET || '0.35');
133const TASK = process.env.DAIMOND_REFLUX_TASK || 'homerun';
134const WORLD = Number(process.env.DAIMOND_REFLUX_WORLD || '6');
135
136let ok = 0, bad = 0;
137
138/// One check, named as a property rather than as a step.
139function check(name, pass, detail) {
140 if (pass) { ok++; console.log(` ok ${name}${detail ? ` — ${detail}` : ''}`); }
141 else { bad++; console.log(` FAIL ${name}${detail ? ` — ${detail}` : ''}`); }
142}
143
144function note(s) { console.log(` · ${s}`); }
145
146// ── The display ─────────────────────────────────────────────────────
147
148/// Is an X lock file the record of a server that is still there?
149///
150/// A LOCK IS NOT A HOLD. An X server writes its process id into `/tmp/.X<n>-lock`
151/// and removes the file on the way out -- and a process killed with `SIGKILL` gets
152/// no way out, so the file stays and the number is burned until somebody notices.
153/// Nine of them in one session, on 2026-08-25, from this very file: the first
154/// version of `stopXvfb` reached for `SIGKILL` and every run cost a display. Both
155/// halves are fixed, and this is the half that survives somebody else's crash.
156///
157/// # Arguments
158/// * `n` - The display number.
159function lockIsLive(n) {
160 let pid;
161 try { pid = Number(fs.readFileSync(`/tmp/.X${n}-lock`, 'utf8').trim()); }
162 catch { return false; } // no lock at all
163 if (!Number.isInteger(pid) || pid <= 0) return true; // unreadable: leave it alone
164 // `/proc` rather than `kill(0)`, which answers yes for a zombie and for a
165 // process this one may not signal.
166 return fs.existsSync(`/proc/${pid}`);
167}
168
169/// A display number nothing on this machine holds.
170///
171/// The socket is asked about as well as the lock, because either one alone leaves
172/// a race with the other half of an X server that is still coming up, and two
173/// verifiers starting at once is ordinary. From 90 rather than from 99, so a run
174/// of this file does not fight `xvfb-run -a`, which starts at 99 and counts up.
175function freeDisplay() {
176 for (let n = 90; n < 99; n++) {
177 if (lockIsLive(n)) continue;
178 // A socket outlives its server too, and the same reasoning applies: it is
179 // evidence of a lock, and the lock has already been read.
180 if (!fs.existsSync(`/tmp/.X${n}-lock`) && fs.existsSync(`/tmp/.X11-unix/X${n}`)) continue;
181 return n;
182 }
183 return -1;
184}
185
186/// An Xvfb this process started, and the display it answers on.
187///
188/// Killed on every way out of this file -- an ordinary return, a throw, and the
189/// signals the hand's `kill_on_drop` sends when a `verify` budget is spent. An X
190/// server left behind holds its display number against the next run, which is the
191/// same collision `freeDisplay` walks past nine times before giving up.
192async function startXvfb() {
193 const n = freeDisplay();
194 if (n < 0) return { display: '', proc: null, why: 'displays :90 to :98 are all held' };
195 const sock = `/tmp/.X11-unix/X${n}`;
196 const p = spawn('Xvfb', [`:${n}`, '-screen', '0', '1500x950x24', '-nolisten', 'tcp'],
197 { stdio: ['ignore', 'ignore', 'pipe'] });
198 let died = '';
199 p.on('error', (e) => { died = String(e && e.message); });
200 p.stderr.on('data', (d) => { died += String(d); });
201 for (let i = 0; i < 100; i++) {
202 if (fs.existsSync(sock)) return { display: `:${n}`, proc: p, why: '' };
203 if (p.exitCode !== null) break;
204 await new Promise((r) => setTimeout(r, 100));
205 }
206 try { p.kill('SIGKILL'); } catch { /* already gone */ }
207 return { display: '', proc: null, why: `Xvfb :${n} did not come up. ${died.trim()}` };
208}
209
210let XVFB = null;
211
212/// Ends this run's X server and takes its lock with it.
213///
214/// SIGTERM FIRST, and the difference is a display number. Xvfb removes its own
215/// lock file on a clean exit and cannot on a `SIGKILL`, so the impatient version of
216/// this function burned nine numbers in one session before `freeDisplay` ran out
217/// and the clean run failed. The lock is removed here as well, because this
218/// function is also called from a signal handler where there is no waiting.
219function stopXvfb() {
220 if (!XVFB || !XVFB.proc) { XVFB = null; return; }
221 const { proc, display } = XVFB;
222 XVFB = null;
223 try { proc.kill('SIGTERM'); } catch { /* already gone */ }
224 // Synchronous, because `process.on('exit')` runs nothing asynchronous.
225 const until = Date.now() + 2000;
226 while (proc.exitCode === null && proc.signalCode === null && Date.now() < until) {
227 try { spawnSync('sleep', ['0.05']); } catch { break; }
228 }
229 try { proc.kill('SIGKILL'); } catch { /* already gone */ }
230 const n = display.replace(':', '');
231 // Ours by construction: `freeDisplay` would not have offered the number if
232 // anything else had held it, and nothing else can have taken it since.
233 for (const f of [`/tmp/.X${n}-lock`, `/tmp/.X11-unix/X${n}`]) {
234 try { fs.rmSync(f, { force: true }); } catch { /* not ours to remove */ }
235 }
236}
237process.on('exit', stopXvfb);
238for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
239 process.on(sig, () => { stopXvfb(); process.exit(1); });
240}
241
242/// The environment a child of this file is given.
243///
244/// The single place the two halves are decided, so a check can be put to the
245/// decision rather than to a copy of it -- and so a break changes the world and
246/// not the assertion about it.
247///
248/// # Arguments
249/// * `display` - The display this file started, or `''` where it started none.
250/// * `keyFile` - The key file to name, or `''` to name none and let `reflux` pick.
251function childEnv(display, keyFile) {
252 const env = cleanDisplayEnv(process.env);
253 // Whatever this process inherited, gone, and only then this file's own. A
254 // deletion rather than an assignment because the assignment alone reads as a
255 // default, and the difference is the seat.
256 delete env.DISPLAY;
257 if (BREAK === 'inheritseat') {
258 // B13's world before the guard could see it: a run nobody asked for, given
259 // whatever display the hand happened to hold.
260 env.DISPLAY = (INHERITED_ENV.DISPLAY || '').trim() || SEAT_DISPLAY;
261 } else if (display) {
262 env.DISPLAY = display;
263 }
264 env[UNATTENDED_VAR] = '1';
265 if (display) env[OWNED_VAR] = display;
266 // A key IN THE ENVIRONMENT is a key this file did not choose. `readKey` reads
267 // this name before it reads any file, so leaving it would let whatever started
268 // the hand decide what a daimon spends.
269 delete env.DAIMOND_PROBE_KEY;
270 if (keyFile) env.DAIMOND_PROBE_KEY_FILE = keyFile;
271 else delete env.DAIMOND_PROBE_KEY_FILE;
272 env.DAIMOND_SCRATCH = SCRATCH;
273 return env;
274}
275
276// ── The run ─────────────────────────────────────────────────────────
277
278console.log('\n== verify_reflux: the door a daimon reaches the instrument through ==');
279if (BREAK) console.log(` BREAK ${BREAK} — checks below are EXPECTED to fail`);
280
281fs.mkdirSync(SCRATCH, { recursive: true });
282
283console.log('\n── A display this file started for itself ────────────');
284
285XVFB = BREAK === 'nodisplay'
286 ? { display: '', proc: null, why: 'the break started none, which is B13\'s own world' }
287 : await startXvfb();
288const DISPLAY = XVFB.display;
289
290check('an Xvfb of this run\'s own is up, on a display number nothing else held',
291 !!DISPLAY, DISPLAY || XVFB.why);
292
293const KEY_FILE = BREAK === 'ownerkey' ? '' : daimonKeyFile();
294const ENV = childEnv(DISPLAY, KEY_FILE);
295
296// ASKED OF WHAT IS HANDED ON, not of what was started. The two differ by exactly
297// one accident -- a display inherited from whatever launched the hand, quietly
298// winning over the one this file went to the trouble of starting -- and that
299// accident is B13's whole second half.
300check('the display handed to the child is the one this file started, and neither the '
301 + 'seat nor what it inherited',
302 !!DISPLAY && ENV.DISPLAY === DISPLAY && DISPLAY !== SEAT_DISPLAY
303 && DISPLAY !== (INHERITED_ENV.DISPLAY || '').trim(),
304 `started ${DISPLAY || '<none>'}, handed ${ENV.DISPLAY || '<none>'}, `
305 + `inherited ${(INHERITED_ENV.DISPLAY || '').trim() || '<none>'}`);
306
307{
308 const said = displayFault(ENV);
309 check('the environment this file hands on is one the guard accepts',
310 said === null, JSON.stringify(said));
311}
312
313{
314 // The refusal put to the REAL module with the REAL environment, differing from
315 // the one above in the display alone. Not a launch: a browser started to find
316 // out where a browser would appear is a check nobody dares run.
317 const said = displayFault({ ...ENV, DISPLAY: SEAT_DISPLAY });
318 check('the same environment carrying the seat instead is refused, and the seat named',
319 typeof said === 'string' && said.includes('OWN SEAT'), JSON.stringify(said));
320}
321
322
323// A real headed browser, on the display this file started, and on no other. This
324// is the one check that asks the X server rather than the guard: a headed Chromium
325// with nowhere to paint does not start at all, so a browser that answers is a
326// browser that connected to THIS display.
327if (DISPLAY && BREAK !== 'inheritseat') {
328 let why = '';
329 let landed = false;
330 try {
331 // The same playwright and the same Chrome `dev/harness.mjs` uses, taken from
332 // it rather than written down again: a browser proved to land on this display
333 // is only evidence if it is the browser everything else here launches.
334 const { PW, CHROME } = await import('./harness.mjs');
335 const { chromium } = await import(pathToFileURL(PW).href);
336 const prof = path.join(SCRATCH, 'guard-profile');
337 fs.rmSync(prof, { recursive: true, force: true });
338 const b = await chromium.launchPersistentContext(prof, {
339 executablePath: CHROME,
340 headless: false,
341 env: ENV,
342 args: ['--no-sandbox', '--no-first-run'],
343 viewport: { width: 800, height: 600 },
344 });
345 const pg = b.pages()[0] || await b.newPage();
346 await pg.goto('about:blank');
347 landed = true;
348 await b.close();
349 } catch (e) {
350 why = String(e && e.message).split('\n')[0];
351 }
352 check('a real headed Chromium starts on that display and on nothing else',
353 landed, why || `DISPLAY=${DISPLAY}`);
354} else if (BREAK === 'inheritseat') {
355 // NOT LAUNCHED, AND THIS IS THE ONE PLACE THAT MATTERS. The break's whole
356 // content is a display that belongs to somebody else -- on this machine, over
357 // an ssh forward to the owner's laptop. Starting a browser to watch a break
358 // work would put the window on his screen, which is the accident the guard
359 // exists to prevent.
360 note('the browser was not launched: the break\'s display is somebody else\'s');
361} else {
362 check('a real headed Chromium starts on that display and on nothing else',
363 false, XVFB.why || 'there is no display to start it on');
364}
365
366console.log('\n── A key of its own ──────────────────────────────────');
367
368const NAMED = ENV.DAIMOND_PROBE_KEY_FILE || '';
369
370check('the key named to the child is the daimon\'s, not the owner\'s',
371 !!NAMED && NAMED !== ownerKeyFile() && /daimond-hand/.test(NAMED),
372 NAMED || '<none named, so reflux picks -- and its default is his>');
373
374check('and it lives outside ~/usr, which is replicated and is a candidate for ore init',
375 !!NAMED && !NAMED.startsWith(path.join(os.homedir(), 'usr')) && !NAMED.startsWith(ROOT),
376 NAMED || '<none named>');
377
378// The child is told WHERE the key is and never what it is. `readKey` reads
379// `DAIMOND_PROBE_KEY` before it reads any file, so a value left in the environment
380// would let whatever started the hand decide what a daimon spends -- and an
381// environment is copied into every child of every child.
382check('the child is handed a path to the key and never the key itself',
383 ENV.DAIMOND_PROBE_KEY === undefined && !!NAMED,
384 `DAIMOND_PROBE_KEY ${ENV.DAIMOND_PROBE_KEY === undefined ? 'unset' : 'SET'}, `
385 + `DAIMOND_PROBE_KEY_FILE ${ENV.DAIMOND_PROBE_KEY_FILE || '<unset>'}`);
386
387// What `reflux` actually does when the key it is pointed at is not there. The real
388// file, the real `readKey`, and a home directory of this run's own so that neither
389// the owner's key nor the daimon's is anywhere near the answer.
390{
391 const home = path.join(SCRATCH, 'home');
392 fs.mkdirSync(home, { recursive: true });
393 const env = childEnv(DISPLAY, KEY_FILE ? daimonKeyFile(home) : '');
394 env.HOME = home;
395 const r = spawnSync(process.execPath, ['dev/reflux.mjs', '--task', TASK, '--no-build'],
396 { cwd: ROOT, encoding: 'utf8', env, timeout: 60_000 });
397 const said = `${r.stdout || ''}${r.stderr || ''}`;
398 check('with no key of its own, reflux stops rather than spending, and names the file',
399 r.status !== 0 && said.includes(daimonKeyFile(home)),
400 (said.split('\n').find((l) => l.includes('openrouter.key')) || said.split('\n')[0] || '')
401 .trim().slice(0, 160));
402 check('and the file it names is not the owner\'s',
403 !said.includes(ownerKeyFile(home)),
404 said.includes(ownerKeyFile(home)) ? 'it reached for the owner\'s key' : 'it did not');
405}
406
407// ── The number ──────────────────────────────────────────────────────
408
409console.log('\n── The run, and the number it comes back with ────────');
410
411const HAVE_KEY = !!KEY_FILE && fs.existsSync(KEY_FILE);
412let paid = false;
413
414if (BREAK) {
415 note(`no paid run under a break: a break spends money to watch a check redden, `
416 + 'and every check this file declares reddens for free.');
417} else if (!DISPLAY) {
418 note('no paid run: there is no display to run it on.');
419} else if (!HAVE_KEY) {
420 // A CHECK AND NOT A SKIP, and the difference is what a daimon is told.
421 //
422 // The tree's idiom for "cannot be given what it needs" is exit 2 and a SKIPPED
423 // line -- `verify_droots_real` does exactly that. It is right for `run_all.sh`,
424 // which reads exit codes, and it is silent through `verify`: the hand's report
425 // carries the check lines and the FAIL lines and nothing else, so this file
426 // exiting 2 with ten green checks reached a daimon as "10 checks passed, 0
427 // failed, 3 breaks confirmed red" -- a clean bill of health for a run that never
428 // happened. Measured on 2026-08-25 by conducting this file through the verb.
429 //
430 // So the missing key is a check, it is red because it is false, and the whole of
431 // what to do about it rides in the detail, which is the part `fails` keeps whole.
432 check('the run behind this door has a key of its own to spend',
433 false,
434 `there is no key at ${KEY_FILE}. Only the owner can mint one: a NEW key at `
435 + 'openrouter.ai/settings/keys with a credit limit of its own, then `install -d '
436 + '-m 700 ~/.config/oxedyne/daimond-hand && install -m 600 /dev/stdin '
437 + '~/.config/oxedyne/daimond-hand/openrouter.key`. This file WILL NOT reach for '
438 + 'his. The door above is proved; the run behind it did not run.');
439 console.log('');
440 console.log(' The checks above are real. The run is not run, and that is not a pass.');
441 console.log('');
442} else {
443 const args = ['dev/reflux.mjs', '--task', TASK, '--budget', BUDGET.toFixed(2),
444 '--world', String(WORLD)];
445 note(`reflux ${args.slice(1).join(' ')} on ${DISPLAY}, from ${KEY_FILE}`);
446 const r = spawnSync(process.execPath, args,
447 { cwd: ROOT, encoding: 'utf8', env: ENV, maxBuffer: 64 * 1024 * 1024 });
448 const said = `${r.stdout || ''}${r.stderr || ''}`;
449 for (const line of said.split('\n')) {
450 if (/^(task|TOTAL|\s{2}\S+\s+(pass|FAIL))/.test(line)) console.log(` | ${line}`);
451 }
452 const total = said.split('\n').find((l) => l.startsWith('TOTAL')) || '';
453 const spent = Number((total.match(/\$([0-9.]+)/) || [])[1] || 'NaN');
454 check('reflux ran to its end inside the fence-free door and reported',
455 r.status === 0, `exit ${r.status}` + (r.status === 0 ? '' : `: ${said.slice(-400)}`));
456 check('it came back with a number, and the number is inside the budget it was given',
457 Number.isFinite(spent) && spent <= BUDGET,
458 total.trim() || said.split('\n').slice(-3).join(' | ').slice(0, 200));
459 paid = true;
460}
461
462stopXvfb();
463
464console.log(`\n${ok} ok, ${bad} failed.`);
465if (bad) { console.log('failed checks above.'); process.exit(1); }
466if (!paid && !BREAK) {
467 // Reached only where there IS a key and the run was skipped for another reason
468 // -- no display, which is already a red above. Kept so that no path out of this
469 // file can report a pass without the run having happened.
470 console.log('The door is proved; the run behind it was not run. Exit 2 is not a pass.');
471 process.exit(2);
472}