Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_wire.mjs

28.6 KiB, 1 run

created by r2519314175:813, 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_wire.mjs — the Wire band says what actually goes to the model, on BOTH
2// threads.
3//
4// The Wire is a chip in the chat header that opens the invisible part of every
5// request: the composed system message split by whose each paragraph is, and the
6// tool schemas beside it. Its whole selling point, written into its own commit,
7// is that it CANNOT DRIFT from the request, because one function composes what
8// goes out and what is shown.
9//
10// That was true of a chat and false of a Diamond. A daimon's turn never goes
11// through `run_turn`: `sendUserMessage` hands a record carrying a `diamondId` to
12// `doSteer`, which steers the DIAMOND'S OWN app through `steer_crystal`, and
13// `steer_inner` builds its own tool vector — seventeen tools, none of the nine
14// web tools and no `file_show` for a worker. `renderWire` read
15// `ensureApp(current)`, an ordinary CHAT app whose registry is `Tool::browser()`:
16// twenty-eight tools. So a Diamond's owner was shown a toolbelt his daimon has
17// never held, while he sat asking why it never used one of them.
18//
19// `say` used to be the tool this file told the two belts apart by. It is gone —
20// an answer is written at two depths in the model's own prose now — so the
21// distinguishing tool is `file_show`, and `say` is asserted ABSENT from both.
22//
23// THE ORACLE IS THE MOCK PROVIDER, not the app's opinion of itself. Every check
24// below compares the band on screen with the request the model actually received
25// — `dev/mockllm.mjs` logs the whole payload, tool names and all — so a band and
26// an engine that agree with each other and not with the wire cannot both pass.
27//
28// Four properties:
29//
30// 1. On an ORDINARY CHAT the band's tool list is the chat's real registry, and
31// its system message is the string the provider was sent.
32// 2. On a DAIMON THREAD the band's tool list is the DAIMON'S real registry —
33// and carries no web tool, because the daimon has not got them. This is the
34// check the shipped feature failed. Neither belt carries `say` at all.
35// 3. The token figures the band reports for a daimon are the daimon's: the
36// schema count is the daimon's tool count, the total is not the chat's, and
37// it is the arithmetic the app itself does over the daimon's own bands.
38// 4. The per-turn paragraph — this Diamond's folder, its attachments and its
39// crystal — is drawn under its own heading and has a bounding rectangle.
40// Measured as an area, never as a computed `display`: `display:none` does
41// not cascade, so a parent that is gone leaves a child still "block".
42//
43// node dev/verify_wire.mjs
44// node dev/verify_wire.mjs --break chatapp # the daimon thread reads the chat app again
45// node dev/verify_wire.mjs --break dropshow # the band drops one tool from its list
46// node dev/verify_wire.mjs --break splice # a band spliced out of two distant pieces
47// node dev/verify_wire.mjs --break pretty # the schemas counted as the band prints them
48// node dev/verify_wire.mjs --break chars # the schemas counted in characters, not bytes
49//
50// Needs dev/serve.mjs and dev/mockllm.mjs (dev/world.sh N --up gives both).
51import fs from 'node:fs';
52import path from 'node:path';
53import os from 'node:os';
54import { fileURLToPath } from 'node:url';
55import { open, chat, steerDiamond, scratch, shot, mockLog, clearMockLog, contentText } from './harness.mjs';
56import { whyStaleWasm, refuse } from './staleguard.mjs';
57
58const HERE = path.dirname(fileURLToPath(import.meta.url));
59const ROOT = path.join(HERE, '..');
60const OUT = path.join(os.homedir(), '.cache/daimond/wire-shots');
61fs.mkdirSync(OUT, { recursive: true });
62
63// The composition under test is in the wasm — `wire_json`, and the
64// `compose_daimon` both the turn and the band read — so a stale bundle would
65// measure the defect this run exists to disprove.
66refuse(whyStaleWasm(path.join(ROOT, 'www/pkg/oxedyne_daimond_bg.wasm'), path.join(ROOT, 'src'), {
67 subject: 'The Wire\'s composition',
68 holds: '`wire_json` and `compose_daimon`, which the band and the turn share',
69}));
70
71const BI = process.argv.indexOf('--break');
72const BEQ = process.argv.find(a => a.startsWith('--break='));
73const BREAK = BEQ ? BEQ.split('=')[1] : (BI >= 0 ? (process.argv[BI + 1] || '') : '');
74
75const HOUSE = 'Answer in the fewest words that are still true. RANUNCULUS.';
76
77let failures = 0;
78const check = (cond, msg, detail) => {
79 console.log((cond ? ' ok ' : ' FAIL ') + msg + (detail != null ? ' — ' + detail : ''));
80 if (!cond) failures++;
81};
82
83// ── The breaks ───────────────────────────────────────────────────────────
84//
85// Served, not applied to the tree: the file on disk is never touched, so a run
86// that dies half way leaves nothing behind to confuse the next one.
87const WWW = path.join(ROOT, 'www');
88const BREAKS = {
89 // The defect exactly as it shipped: the band composed from an ordinary chat
90 // app, with no Diamond named, on a thread that steers a daimon.
91 chatapp: [
92 { file: 'js/daimond.js',
93 find: "\t\ttry { app = did ? diamondApp(did) : ensureApp(current); } catch (e) { return; }",
94 with: "\t\ttry { app = ensureApp(current); } catch (e) { return; }" },
95 { file: 'js/daimond.js',
96 find: "\t\t\tw = JSON.parse(await app.wire_system(did,",
97 with: "\t\t\tw = JSON.parse(await app.wire_system('',", },
98 ],
99 // The user's own house rules left inside the safety band, which is what a
100 // first cut of this fix did: harmless on a chat, where the two are adjacent,
101 // and on a Diamond a band made of two pieces with a paragraph missing from
102 // between them -- text that never stood together in the message.
103 splice: [
104 { file: 'js/daimond.js',
105 find: "\t\tvar std = role.indexOf('## Standing instructions from the user');\n"
106 + "\t\tif (std > 0) { standing = role.slice(std); role = role.slice(0, std); }",
107 with: "\t\tvar std = -1;" },
108 ],
109 // The schemas counted in the form the band DRAWS rather than the form the
110 // request carries. This is the defect as it shipped: the figure people quote
111 // from this band -- "9k of the 11k is tool schemas" -- overstated by an eighth,
112 // entirely in whitespace the viewer added itself.
113 pretty: [
114 { file: 'js/daimond.js',
115 find: "\t\tvar schemaTok = Math.round((w.schemas_len || 0) / 4);",
116 with: "\t\tvar schemaTok = wireTok(schemas);" },
117 ],
118 // The schemas counted in CHARACTERS rather than in the bytes the body carries. Not the
119 // same mistake as `pretty`: this copy is the compact array, the one the request really
120 // holds, measured with `String.length` -- UTF-16 code units, so 41,964 where the body
121 // carries 41,998. Thirty-four bytes, and on 2026-08-25 they straddled the 10,500-token
122 // rounding step, so the band drew "11k" and a run measuring the same array in characters
123 // drew "10k" and reported the band as having drifted from the request. It had not.
124 chars: [
125 { file: 'js/daimond.js',
126 find: "\t\tvar schemaTok = Math.round((w.schemas_len || 0) / 4);",
127 with: "\t\tvar schemaTok = Math.round(JSON.stringify(w.schemas || []).length / 4);" },
128 ],
129 // One tool quietly missing from what the band draws, and present in the
130 // request. Here to prove check 1 has teeth: a check that only ever reads a
131 // list that is right proves nothing about a list that is wrong.
132 dropshow: [
133 { file: 'js/daimond.js',
134 find: "\t\tvar schemas = JSON.stringify(w.schemas || [], null, 1);",
135 with: "\t\tvar schemas = JSON.stringify((w.schemas || []).filter(function (d) "
136 + "{ return ((d || {}).function || {}).name !== 'file_show'; }), null, 1);" },
137 ],
138};
139
140if (BREAK && !BREAKS[BREAK]) {
141 console.error(`unknown break '${BREAK}'; one of: ${Object.keys(BREAKS).join(', ')}`);
142 process.exit(2);
143}
144
145/// Every edit a break makes to ONE file, applied to one copy of it.
146///
147/// Grouped by file because `page.route` keeps only the LAST handler registered
148/// for a pattern: two specs against `js/daimond.js` registered separately would
149/// serve the second edit alone, and the run would report a check as proved that
150/// had been asked of working code.
151function damaged(file, specs) {
152 let src = fs.readFileSync(path.join(WWW, file), 'utf8');
153 for (const spec of specs) {
154 const n = src.split(spec.find).length - 1;
155 if (n !== 1) {
156 console.error(`break '${BREAK}': the anchor appears ${n} times in ${file}, `
157 + 'so nothing was broken and the run below would prove nothing.');
158 process.exit(2);
159 }
160 src = src.replace(spec.find, spec.with);
161 }
162 return src;
163}
164
165/// Keep the RAW body of every request to the provider.
166///
167/// The mock logs the payload it parsed, which is enough for names and messages
168/// and not for a SIZE: a re-serialised copy is a second opinion about the very
169/// bytes in question. The string handed to `fetch` is the request, so it is kept
170/// as a string and measured as one.
171async function watchWire(page) {
172 await page.addInitScript(() => {
173 // The engine builds a `Request` and hands THAT to `fetch`, so a hook on `fetch`
174 // alone sees a body that is already a stream. The constructor is where the
175 // string still exists.
176 const keep = (url, init) => {
177 try {
178 if (/chat\/completions/.test(String(url || '')) && init && typeof init.body === 'string') {
179 window.__wireRaw = init.body;
180 }
181 } catch (e) { /* never break a turn to watch one */ }
182 };
183 window.Request = new Proxy(window.Request, {
184 construct(target, args) {
185 const u = args[0];
186 keep(typeof u === 'string' ? u : ((u && u.url) || ''), args[1]);
187 return new target(...args);
188 },
189 });
190 const orig = window.fetch;
191 window.fetch = function (u, init) {
192 keep(typeof u === 'string' ? u : ((u && u.url) || ''), init);
193 return orig.apply(this, arguments);
194 };
195 });
196}
197
198async function serveBreak(page) {
199 await watchWire(page);
200 if (!BREAK) return;
201 const byFile = new Map();
202 for (const spec of BREAKS[BREAK]) {
203 if (!byFile.has(spec.file)) byFile.set(spec.file, []);
204 byFile.get(spec.file).push(spec);
205 }
206 for (const [file, specs] of byFile) {
207 const body = damaged(file, specs);
208 await page.route('**/' + file, r => r.fulfill({
209 status: 200, contentType: 'application/javascript', body }));
210 }
211}
212
213// ── Reading the two sides ────────────────────────────────────────────────
214
215/// The band on screen, in pieces.
216///
217/// The bodies are read as text whether the band is folded open or not — a
218/// `<pre>` with `display:none` still carries its `textContent` — but every
219/// question about whether something is DRAWN is answered with an area.
220const wireDom = (p) => p.evaluate(() => {
221 const box = document.getElementById('wire-head');
222 if (!box) return null;
223 const rect = (el) => {
224 const r = el.getBoundingClientRect();
225 return Math.round(r.width * r.height);
226 };
227 const bands = [...box.querySelectorAll('.wire-band')].map((row) => {
228 const head = row.querySelector('.wire-band-head');
229 const body = row.querySelector('.wire-band-body');
230 return {
231 name: head.querySelector('.wire-band-name').textContent.replace(/^[▸▾]\s*/, ''),
232 why: head.querySelector('.wire-band-why').textContent,
233 tok: head.querySelector('.wire-band-tok').textContent,
234 text: body ? body.textContent : '',
235 area: rect(head),
236 };
237 });
238 return { area: rect(box), title: (box.querySelector('.wire-title') || {}).textContent || '', bands };
239});
240
241// The schema band's heading carries its count -- `Tool schemas (17)` -- so a name
242// is matched whole or up to its bracket.
243const band = (w, name) => (w
244 ? w.bands.find(b => b.name === name || b.name.startsWith(name + ' ('))
245 : null);
246
247/// The tool names the band draws, read out of the schema band it shows.
248///
249/// From the SCHEMAS and not from the tool sentence, deliberately: the schemas are
250/// the bytes on the wire, and the sentence is prose about them.
251function bandTools(w) {
252 const b = band(w, 'Tool schemas');
253 if (!b) return null;
254 try {
255 return JSON.parse(b.text).map(d => (d.function || {}).name).filter(Boolean).sort();
256 } catch (e) { return null; }
257}
258
259/// The last request the provider received, and the system message in it.
260const lastReq = () => { const r = mockLog(); return r.length ? r[r.length - 1] : null; };
261const sysOf = (row) => {
262 const m = (row.messages || []).find(x => x.role === 'system');
263 return m ? contentText(m.content) : '';
264};
265
266/// The app's own arithmetic, so a figure can be checked rather than admired.
267///
268/// BYTES, because that is what the band claims to be reporting and what the engine budgets
269/// with. `String.length` is UTF-16 code units: for the browser toolbelt it says 41,964 where
270/// the body carries 41,998, and on 2026-08-25 those two straddled the 10,500-token rounding
271/// step and drew "10k" and "11k". This run then reported a band that had drifted from the
272/// request, and it had not -- the two figures were the same array in two encodings. Measuring
273/// characters and calling them bytes is the defect this file exists to catch, made by the
274/// file itself.
275const ENC = new TextEncoder();
276const bytes = (s) => ENC.encode(String(s || '')).length;
277const tok = (s) => Math.round(bytes(s) / 4);
278const fmtTok = (n) => (n % 1024 === 0) ? (n / 1024) + 'k'
279 : (n >= 1e6 ? (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M'
280 : (n >= 1000 ? Math.round(n / 1000) + 'k' : '' + n));
281
282/// Turn the band on and wait for it to be drawn.
283async function showWire(p) {
284 const on = await p.evaluate(() => document.getElementById('wire-btn').getAttribute('aria-pressed'));
285 if (on !== 'true') await p.click('#wire-btn', { force: true });
286 await p.waitForTimeout(900);
287}
288
289/// Redraw the band for whatever thread is on screen now.
290///
291/// Off and on again rather than trusting a redraw: the band is rebuilt on every
292/// thread change, and this run wants the one for the thread it is looking at,
293/// not whichever render happened to finish last.
294async function redrawWire(p) {
295 await p.evaluate(() => {
296 const b = document.getElementById('wire-btn');
297 if (b.getAttribute('aria-pressed') === 'true') b.click();
298 });
299 await p.waitForTimeout(300);
300 await p.click('#wire-btn', { force: true });
301 await p.waitForTimeout(1200);
302}
303
304async function makeDiamond(p, name) {
305 await p.evaluate(() => document.getElementById('new-diamond-btn').click());
306 await p.waitForSelector('.dlg-card', { timeout: 8000 });
307 await p.evaluate((nm) => {
308 const card = [...document.querySelectorAll('.dlg-card')]
309 .filter(c => c.getClientRects().length).pop();
310 const inp = card.querySelector('input.dlg-input');
311 inp.value = nm;
312 inp.dispatchEvent(new Event('input', { bubbles: true }));
313 card.querySelector('.dlg-ok').click();
314 }, name);
315 await p.waitForTimeout(1400);
316}
317
318// `route` and not a reload afterwards: the damaged file has to be in place before
319// the page is navigated, or the module has already been imported by the time the
320// route exists and the break is served to nobody.
321const s = await open({ name: 'wire', profile: scratch('pw', 'wire-' + process.pid), route: serveBreak });
322const { page: p } = s;
323try {
324 if (BREAK) console.log(` .. running with --break ${BREAK}`);
325
326 // The user's own house rules, in force before either turn.
327 //
328 // NOT decoration. They are appended after everything the app composes, so on a
329 // Diamond they sit on the FAR SIDE of the paragraph this run is about -- and a
330 // band that lifted that paragraph out without accounting for them would show a
331 // "Safety clause" made of two pieces that never stood together in the message.
332 // Seeded through the store, which is where `Instructions.refresh` reads them.
333 await p.evaluate(async (text) => {
334 const m = await import('/pkg/oxedyne_daimond.js');
335 await m.store_write('DAIMOND.md', text);
336 await window.DaimondInstructions.refresh();
337 }, HOUSE);
338 await p.waitForTimeout(600);
339
340 // ══ 1. An ordinary chat ═══════════════════════════════════════════
341 clearMockLog();
342 await chat(s, 'say hello');
343 const chatReq = lastReq();
344 check(!!chatReq, 'the chat turn reached the provider');
345 const chatSent = chatReq ? (chatReq.tools || []).slice().sort() : [];
346 check(chatSent.includes('file_show') && chatSent.some(n => n.startsWith('web_')),
347 'and a chat really does hold `file_show` and the web tools',
348 `${chatSent.length} tools`);
349 // The tool that is gone. A chat is the actor `say` was FOR, so if any belt anywhere
350 // still carries it, this is the one it would be on.
351 check(!chatSent.includes('say'),
352 'and no longer holds `say`, which is not a tool any more',
353 chatSent.includes('say') ? 'it is still offered one' : '');
354
355 await showWire(p);
356 let w = await wireDom(p);
357 check(!!w && w.area > 0, 'the band is drawn on a chat', w && `area ${w.area}`);
358 const chatShown = bandTools(w);
359 check(!!chatShown, 'and its schema band parses as JSON');
360 check(JSON.stringify(chatShown) === JSON.stringify(chatSent),
361 'THE BAND\'S TOOL LIST IS THE CHAT\'S REAL REGISTRY',
362 `band ${chatShown ? chatShown.length : '?'} vs wire ${chatSent.length}`
363 + (JSON.stringify(chatShown) === JSON.stringify(chatSent) ? '' :
364 `; only in band: [${(chatShown || []).filter(n => !chatSent.includes(n))}]`
365 + `; only sent: [${chatSent.filter(n => !(chatShown || []).includes(n))}]`));
366
367 const chatSys = chatReq ? sysOf(chatReq) : '';
368 const chatBands = [band(w, 'Role prompt'), band(w, 'Safety clause'), band(w, 'Standing instructions'),
369 band(w, 'Tool names'), band(w, 'This computer')].filter(Boolean).map(b => b.text);
370 check(chatBands.length >= 4, 'the chat band has its parts', chatBands.length + ' of them');
371 check(chatBands.every(t => t.length > 0 && chatSys.includes(t.trim())),
372 'and every one of them is VERBATIM in the system message the provider was sent');
373 check(((band(w, 'Standing instructions') || {}).text || '').includes('RANUNCULUS'),
374 'the user\'s own house rules are drawn as theirs');
375 const chatTitle = w ? w.title : '';
376
377 // ── The size it reports is the size that goes ────────────────────
378 const raw = await p.evaluate(() => window.__wireRaw || '');
379 const sentTools = raw ? JSON.parse(raw).tools : null;
380 const compact = sentTools ? JSON.stringify(sentTools) : '';
381 check(!!compact && raw.includes(compact),
382 'the compact serialisation IS the engine\'s own bytes, character for character',
383 compact ? `${bytes(compact)} bytes, found in the body` : 'no body captured');
384 const pretty = sentTools ? JSON.stringify(sentTools, null, 1) : '';
385 check(bytes(pretty) > bytes(compact) * 1.05,
386 'and the printed form is materially bigger, so the two figures are distinguishable',
387 `${bytes(compact)} sent vs ${bytes(pretty)} printed`);
388 const schemaBand = band(w, 'Tool schemas') || {};
389 check(schemaBand.tok === fmtTok(tok(compact)),
390 'THE SCHEMA FIGURE IS THE BYTES THE REQUEST CARRIES',
391 `band says ${schemaBand.tok}, sent is ${fmtTok(tok(compact))}, `
392 + `printed would be ${fmtTok(tok(pretty))}`);
393 check(schemaBand.tok !== fmtTok(tok(pretty)) || fmtTok(tok(pretty)) === fmtTok(tok(compact)),
394 'and not the size of the indenting the band added itself');
395 // The chat's headline as a NUMBER, kept for the daimon's half. The headline itself is
396 // printed to a thousand tokens, and a comparison made on the printed form cannot tell two
397 // threads apart when their true totals fall inside one rounding step -- see the daimon's
398 // half below, which is where that cost a red.
399 const chatTotalTok = tok((band(w, 'Role prompt') || {}).text) + tok((band(w, 'Safety clause') || {}).text)
400 + tok((band(w, 'Standing instructions') || {}).text) + tok((band(w, 'Tool names') || {}).text)
401 + tok((band(w, 'This computer') || {}).text) + tok(compact);
402 // THE THIRD WAY OF BEING WRONG, and the one that fooled this file: the array measured in
403 // UTF-16 code units. It is neither the indented copy nor the sent bytes -- 41,964 against
404 // 41,998 for the browser toolbelt -- and the two draw different headlines whenever they
405 // fall either side of a rounding step, which on 2026-08-25 they did, at 10,500 tokens.
406 // Asserted apart from the `pretty` check above, so a band that regressed to counting
407 // characters cannot pass on the strength of merely not being the indented copy.
408 check(bytes(compact) !== compact.length,
409 'the sent array holds multi-byte characters, so bytes and characters CAN disagree',
410 `${bytes(compact)} bytes over ${compact.length} UTF-16 units`);
411 // They do not always round apart, and a run that pretended otherwise would be claiming a
412 // distinction it had not drawn. So it says which kind of run it is.
413 const charTok = fmtTok(Math.round(compact.length / 4));
414 if (charTok === fmtTok(tok(compact))) {
415 console.log(' .. bytes and characters round alike today (' + charTok
416 + '), so this run cannot tell those two apart');
417 } else {
418 check(schemaBand.tok !== charTok,
419 'and it is not the count of CHARACTERS, which rounds elsewhere today',
420 `characters would say ${charTok}`);
421 }
422 await shot(s, 'chat-wire');
423
424 // ══ 2. A daimon thread ════════════════════════════════════════════
425 await makeDiamond(p, 'Wiremaker');
426 const did = await p.evaluate(() => (DaimondDiamond.current() || {}).id || '');
427 check(!!did, 'a Diamond is open', did);
428 clearMockLog();
429 // The same path a person takes: the Diamond's chat face, its composer, its send
430 // button -- which `sendUserMessage` routes to `doSteer` and nowhere near `run_turn`.
431 await steerDiamond(s, 'note that this happened');
432 await p.waitForTimeout(6000);
433 const dReq = lastReq();
434 check(!!dReq, 'the steering turn reached the provider');
435 const dSent = dReq ? (dReq.tools || []).slice().sort() : [];
436 // THE DAIMON HOLDS THE WEB TOOLS NOW, and this check used to say it must not.
437 //
438 // `Tool::daimon()` never called `Tool::web()`, so a Diamond built for research held no
439 // way to search, fetch or read a page while a chat beside it held nine. The owner
440 // decided on 2026-08-24 that it should, and what stops a tainted turn reaching the
441 // network is the egress gate rather than withholding the tools -- see the comment above
442 // the grant in `src/tools.rs` and `dev/verify_daimonreach.mjs`, which holds that half.
443 //
444 // `say` is still absent, and that has not changed: it was removed outright, and what it
445 // used to enforce moved rather than went.
446 check(dSent.length > 0 && !dSent.includes('say'),
447 'and the daimon does not hold `say`, which no longer exists',
448 `${dSent.length} tools`);
449 check(dSent.some(n => n.startsWith('web_')),
450 'and it DOES hold the web tools, which is the grant of 2026-08-24',
451 dSent.filter(n => n.startsWith('web_')).join(',') || 'none');
452 check(dSent.includes('file_show'),
453 'though it does hold `file_show`, so the check above is not simply an empty belt');
454
455 await redrawWire(p);
456 w = await wireDom(p);
457 check(!!w && w.area > 0, 'the band is drawn on a daimon thread', w && `area ${w.area}`);
458 const dShown = bandTools(w);
459 check(JSON.stringify(dShown) === JSON.stringify(dSent),
460 'THE BAND\'S TOOL LIST IS THE DAIMON\'S REAL REGISTRY',
461 `band ${dShown ? dShown.length : '?'} vs wire ${dSent.length}`
462 + (JSON.stringify(dShown) === JSON.stringify(dSent) ? '' :
463 `; only in band: [${(dShown || []).filter(n => !dSent.includes(n))}]`
464 + `; only sent: [${dSent.filter(n => !(dShown || []).includes(n))}]`));
465 check(!!dShown && !dShown.includes('say'),
466 'the band does not offer him `say` on a daimon thread',
467 dShown && dShown.includes('say') ? 'it does' : '');
468 // The band's own half of the grant of 2026-08-24: a daimon holds the web tools, so the
469 // band must SHOW them. The check above already asserts the band's list is the registry
470 // character for character; this says which way that agreement now falls, so a band that
471 // silently stopped drawing them would not pass on the strength of matching an empty belt.
472 check(!!dShown && dShown.some(n => n.startsWith('web_')),
473 'and the band shows the nine web tools the daimon now holds',
474 dShown ? `[${dShown.filter(n => n.startsWith('web_'))}]` : '');
475
476 const dSys = dReq ? sysOf(dReq) : '';
477 const dParts = [band(w, 'Role prompt'), band(w, 'Safety clause'), band(w, 'This Diamond'),
478 band(w, 'Standing instructions'), band(w, 'Tool names'), band(w, 'This computer')]
479 .filter(Boolean).map(b => b.text);
480 check(dParts.length >= 5, 'the daimon band has its parts', dParts.length + ' of them');
481 check(dParts.length > 0 && dParts.every(t => t.length > 0 && dSys.includes(t.trim())),
482 'AND EVERY PART OF IT IS IN THE SYSTEM MESSAGE THE PROVIDER WAS SENT',
483 dParts.map((t, i) => dSys.includes(t.trim()) ? '' : `part ${i} is not`).filter(Boolean).join('; '));
484
485 // ══ 3. The figures are the daimon's ═══════════════════════════════
486 check((dShown || []).length === dSent.length,
487 'the daimon\'s schema count is the daimon\'s tool count',
488 `${(dShown || []).length} vs ${dSent.length}`);
489 const dTitle = w ? w.title : '';
490 // The claim that the daimon's headline is the daimon's own is asserted below, on the
491 // numbers, once both are in hand.
492 // The daimon's own schemas, off the daimon's own request. `__wireRaw` holds the
493 // last body the page sent, which after the steer is the steering turn's.
494 const dRaw = await p.evaluate(() => window.__wireRaw || '');
495 const dCompact = dRaw ? JSON.stringify(JSON.parse(dRaw).tools) : '';
496 check(!!dCompact && dRaw.includes(dCompact) && bytes(dCompact) < bytes(compact),
497 'the daimon sent its own, smaller, schema array',
498 `${bytes(dCompact)} bytes against the chat's ${bytes(compact)}`);
499 const dSchemaTok = tok(dCompact);
500 check((band(w, 'Tool schemas') || {}).tok === fmtTok(dSchemaTok),
501 'and the band reports THAT, in the daimon\'s figure',
502 `band says ${(band(w, 'Tool schemas') || {}).tok}, sent is ${fmtTok(dSchemaTok)}`);
503 // The whole headline, against the request rather than against itself: the text
504 // bands as drawn, plus the schemas as sent. The bands are trimmed at the seams
505 // where the blob is split, so a few characters go astray either side; the figure
506 // is printed to a thousand tokens, which no seam can move.
507 const sysTok = tok((band(w, 'Role prompt') || {}).text) + tok((band(w, 'Safety clause') || {}).text)
508 + tok((band(w, 'This Diamond') || {}).text) + tok((band(w, 'Standing instructions') || {}).text)
509 + tok((band(w, 'Tool names') || {}).text) + tok((band(w, 'This computer') || {}).text);
510 const want = fmtTok(sysTok + dSchemaTok);
511 check(dTitle.includes(want),
512 'and the headline is those bands plus those bytes',
513 `says "${dTitle}", computed ${want}`);
514 // AND THE TWO THREADS REALLY DO CARRY DIFFERENT TOTALS, which is what keeps the check
515 // above from being satisfiable by a headline carried over from the chat.
516 //
517 // COMPARED AS NUMBERS, NOT AS THE STRINGS THEY ARE PRINTED AS. This read
518 // `dTitle !== chatTitle` until 2026-08-25, and `fmtTok` prints anything over a thousand
519 // tokens to the nearest thousand -- so the moment the daimon's total and the chat's fell
520 // in the same thousand, two correctly measured figures drew the same sentence and this
521 // went red about the app. On the gate of that morning both said "about 13k tokens" while
522 // the arrays behind them were 41,993 and 44,437 bytes: 611 tokens apart, and printed
523 // identically. A check that cannot tell a collision from a carry-over is measuring the
524 // rounding, not the band.
525 const dTotalTok = sysTok + dSchemaTok;
526 check(!!dTitle && dTotalTok !== chatTotalTok,
527 'and it is the daimon\'s own total, not the chat\'s carried across',
528 `daimon ${dTotalTok} tok "${dTitle}" vs chat ${chatTotalTok} tok "${chatTitle}"`);
529
530 // ══ 4. The per-turn paragraph, drawn ══════════════════════════════
531 const local = band(w, 'This Diamond');
532 check(!!local, 'this turn\'s own paragraph has a band of its own');
533 check(!!local && local.area > 0, 'and it is drawn, measured as an area',
534 local && `area ${local.area}`);
535 check(!!local && local.text.includes('diamonds/' + did),
536 'it names THIS Diamond\'s folder', local && local.text.slice(0, 90));
537 check(!!local && local.text.includes('Current crystal.json:'),
538 'and carries the crystal the turn started from');
539 check(!!local && !((band(w, 'Safety clause') || {}).text || '').includes('Current crystal.json:'),
540 'so the crystal is not filed under the safety clause');
541 check(!((band(w, 'Safety clause') || {}).text || '').includes('RANUNCULUS'),
542 'nor the user\'s own house rules, which are drawn under their own heading');
543 check(((band(w, 'Standing instructions') || {}).text || '').includes('RANUNCULUS'),
544 'and they reach a daimon too, on the far side of this turn\'s own paragraph');
545 await shot(s, 'daimon-wire');
546
547} catch (e) {
548 check(false, 'the run finished', String(e && e.message || e));
549 try { await shot(s, 'threw'); } catch {}
550} finally {
551 await s.close();
552}
553
554console.log(failures === 0
555 ? `\nverify_wire: all checks pass.`
556 : `\nverify_wire: ${failures} failed.`);
557process.exit(failures === 0 ? 0 : 1);