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. |
| 34 | import fs from 'node:fs'; |
| 35 | import path from 'node:path'; |
| 36 | import { fileURLToPath } from 'node:url'; |
| 37 | import { open, signInAs, connectMock, errors } from './harness.mjs'; |
| 38 | |
| 39 | const HERE = path.dirname(fileURLToPath(import.meta.url)); |
| 40 | const WWW = path.join(HERE, '..', 'www'); |
| 41 | |
| 42 | const BREAK = (() => { |
| 43 | const i = process.argv.indexOf('--break'); |
| 44 | return i > 0 ? String(process.argv[i + 1] || '') : ''; |
| 45 | })(); |
| 46 | |
| 47 | const 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 | |
| 62 | const ok = [], bad = []; |
| 63 | const 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. |
| 69 | const 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. |
| 75 | const ADDED = 'edited-memory-marker-42'; |
| 76 | |
| 77 | const s = await open({ name: 'crystalmemory', signIn: false, connect: false }); |
| 78 | const { page } = s; |
| 79 | |
| 80 | if (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 | |
| 99 | const memText = () => page.$eval('.crystal-memory-ta', el => el.value).catch(() => null); |
| 100 | |
| 101 | try { |
| 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 | |
| 174 | console.log(`\n${ok.length} passed, ${bad.length} failed`); |
| 175 | if (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 | } |
| 180 | process.exit(bad.length ? 1 : 0); |