Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_crystalmemory.mjs

7.8 KiB, 1 run

created by r2519314175:2139, 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_crystalmemory.mjs — a Diamond's MEMORY is visible and editable, and an edit
2// reaches the next daimon's system message.
3//
4// THE FEATURE THIS IS WRITTEN FROM, in the owner's words: "I used to be able to click to
5// expand and view the memory part of a crystal, I can't see that anymore." The crystal is
6// drawn by a page it owns (see verify_crystalpage), and the page RENDERS the memory but
7// gave no way to see or edit the memory itself. This restores a click-to-expand raw view
8// of `crystal.json`, with an inline editor, on the crystal face.
9//
10// THE PROPERTIES:
11//
12// 1. The crystal face carries a Memory disclosure, and its editor holds the memory as
13// it sits on disk — the raw `crystal.json`, not a rendering of it.
14// 2. An edit made there and SAVED persists: `read_crystal_data` returns the new bytes.
15// This is the check that matters — a disclosure that showed the memory and dropped
16// an edit would look right and lose the user's words.
17// 3. The saved memory reaches a fresh daimon's system message: `wire_system` composes
18// the "Diamond" band from `crystal.json` as it stands, so the edited fact is IN the
19// `local` string the next turn is sent. Persisting to a file nothing reads would be
20// the same nothing as before.
21//
22// EACH CHECK PROVED AGAINST BROKEN CODE FIRST:
23//
24// node dev/verify_crystalmemory.mjs --break nopanel # 1: the disclosure is not drawn
25// node dev/verify_crystalmemory.mjs --break nosave # 2, 3: Save writes nothing
26// node dev/verify_crystalmemory.mjs # and then, clean
27//
28// `nopanel` turns check 1 red and leaves the rest unreachable; `nosave` leaves the
29// disclosure standing and turns the persistence and wire checks red — which is what
30// pins the memory→prompt path to the SAVE and not to the mere presence of a box.
31//
32// Needs the live gate: the dev server at :8777 and the mock provider, like every harness
33// verifier here.
34import fs from 'node:fs';
35import path from 'node:path';
36import { fileURLToPath } from 'node:url';
37import { open, signInAs, connectMock, errors } from './harness.mjs';
38
39const HERE = path.dirname(fileURLToPath(import.meta.url));
40const WWW = path.join(HERE, '..', 'www');
41
42const BREAK = (() => {
43 const i = process.argv.indexOf('--break');
44 return i > 0 ? String(process.argv[i + 1] || '') : '';
45})();
46
47const BREAKS = {
48 // The disclosure is never appended, so the crystal face carries no memory view at all.
49 nopanel: {
50 file: 'js/daimond.js',
51 find: "\t\tcrystalBody.appendChild(crystalMemoryPanel(id, text));",
52 with: "\t\tvoid crystalMemoryPanel;",
53 },
54 // Save resolves without writing, so the box is there and an edit vanishes on Save.
55 nosave: {
56 file: 'js/daimond.js',
57 find: "\t\t\ttry { await diamondApp().write_crystal_data(id, JSON.stringify(d, null, 2)); }",
58 with: "\t\t\ttry { await Promise.resolve(void d); }",
59 },
60};
61
62const ok = [], bad = [];
63const check = (name, pass, detail) => {
64 (pass ? ok : bad).push(name);
65 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
66};
67
68// A crystal with a fact the editor can find and a summary the box must show verbatim.
69const CRYSTAL = {
70 title: 'Memory',
71 summary: 'A crystal to read and edit through the Memory disclosure.',
72 facts: [{ k: 'Seed', v: 'the value that was there before the edit' }],
73};
74// The words the edit adds. Distinctive, so finding them in the wire proves the path.
75const ADDED = 'edited-memory-marker-42';
76
77const s = await open({ name: 'crystalmemory', signIn: false, connect: false });
78const { page } = s;
79
80if (BREAK) {
81 const spec = BREAKS[BREAK];
82 if (!spec) {
83 console.error('no such break: ' + BREAK + '\nhave: ' + Object.keys(BREAKS).join(' '));
84 process.exit(2);
85 }
86 const src = fs.readFileSync(path.join(WWW, spec.file), 'utf8');
87 const n = src.split(spec.find).length - 1;
88 if (n !== 1) {
89 console.error(`break '${BREAK}': the anchor appears ${n} times in ${spec.file}, `
90 + 'so nothing was broken and the run below would prove nothing.');
91 process.exit(2);
92 }
93 await page.route('**/' + spec.file, r => r.fulfill({
94 status: 200, contentType: 'application/javascript', body: src.replace(spec.find, spec.with),
95 }));
96 console.log(` (running with the app broken: ${BREAK})`);
97}
98
99const memText = () => page.$eval('.crystal-memory-ta', el => el.value).catch(() => null);
100
101try {
102 await page.goto(process.env.DAIMOND_APP || 'http://localhost:8777', { waitUntil: 'domcontentloaded' });
103 await signInAs(s, 'crystalmemory');
104 await connectMock(s);
105 await page.waitForTimeout(1500);
106
107 await page.click('#new-diamond-btn', { force: true });
108 await page.waitForSelector('.dlg-input', { timeout: 10000 });
109 await page.fill('.dlg-input', 'Memory');
110 await page.click('.dlg-ok', { force: true });
111 await page.waitForTimeout(1800);
112
113 const id = await page.evaluate(async () => {
114 const m = await import('/pkg/oxedyne_daimond.js');
115 const app = new m.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 4096, '', true);
116 window.__free = app;
117 const d = JSON.parse(await app.list_diamonds()).find(x => x.name === 'Memory');
118 return d ? d.id : '';
119 });
120 check('a Diamond to watch', !!id, id);
121
122 // Only the memory is written; the page stays the shipped one.
123 await page.evaluate(async (a) => {
124 await window.__free.run_tool('file_write', JSON.stringify({
125 path: 'diamonds/' + a.id + '/crystal.json', content: a.crystal }));
126 }, { id, crystal: JSON.stringify(CRYSTAL) });
127 await page.$$eval('.diamond-box', els => els[0] && els[0].click());
128 await page.waitForTimeout(2500);
129
130 // ── 1. The disclosure is there, and it holds the memory as it stands.
131 const present = await page.$('.crystal-memory');
132 check('the crystal face carries a Memory disclosure', !!present);
133 const shown = await memText();
134 check('and its editor holds the memory as it sits on disk',
135 !!shown && shown.indexOf(CRYSTAL.summary) >= 0, JSON.stringify(shown && shown.slice(0, 60)));
136
137 // ── 2. An edit made there and saved persists.
138 // Open the disclosure, add a fact carrying the marker, then Save through its button.
139 await page.evaluate(() => { const d = document.querySelector('.crystal-memory'); if (d) d.open = true; });
140 await page.waitForTimeout(200);
141 const edited = JSON.stringify({
142 title: CRYSTAL.title,
143 summary: CRYSTAL.summary,
144 facts: [{ k: 'Added', v: ADDED }],
145 }, null, 2);
146 await page.$eval('.crystal-memory-ta', (el, v) => {
147 el.value = v; el.dispatchEvent(new Event('input', { bubbles: true }));
148 }, edited);
149 await page.click('.crystal-memory .crystal-act.primary', { force: true });
150 await page.waitForTimeout(2000);
151
152 const onDisk = await page.evaluate(a => window.__free.read_crystal_data(a.id), { id });
153 check('an edit made in the Memory editor and saved persists',
154 !!onDisk && onDisk.indexOf(ADDED) >= 0, JSON.stringify(onDisk && onDisk.slice(0, 80)));
155
156 // ── 3. The saved memory reaches a fresh daimon's system message.
157 const wire = await page.evaluate(async (a) => {
158 try {
159 const w = JSON.parse(await window.__free.wire_system(a.id, '[]', '[]', '[]'));
160 return String(w && w.local || '');
161 } catch (e) { return 'ERR:' + String(e && e.message || e); }
162 }, { id });
163 check('and the saved memory is in the system message a fresh daimon is composed with',
164 wire.indexOf(ADDED) >= 0, JSON.stringify(wire.slice(0, 100)));
165
166 const errs = errors(s).filter(e => !/502|Bad Gateway|account/i.test(e));
167 check('no unexpected console errors', errs.length === 0, errs.slice(0, 2).join(' | ') || 'clean');
168} catch (e) {
169 check('the run completed', false, String((e && e.message) || e));
170} finally {
171 await s.close?.().catch(() => {});
172}
173
174console.log(`\n${ok.length} passed, ${bad.length} failed`);
175if (BREAK) {
176 console.log(bad.length
177 ? `\nbreak '${BREAK}' produced failures, as it must.`
178 : `\nBREAK '${BREAK}' CHANGED NOTHING — the check it targets is not proving anything.`);
179}
180process.exit(bad.length ? 1 : 0);