oxedyne/daimond/dev/verify_system.mjs
9.9 KiB, 1 run
created by r2519314175:721, 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_system.mjs — phase F: Daimond's own store is visible, and the rules |
| 2 | // come in two layers. |
| 3 | // |
| 4 | // Notes2 asks two questions with one answer: *"where are all the system files |
| 5 | // like DAIMOND.md??"* and *"All these settings should exist as text files in an |
| 6 | // intuitive system directory hierarchy."* |
| 7 | // |
| 8 | // Six properties: |
| 9 | // |
| 10 | // 1. The Workspace panel has a System section, and it lists the store — |
| 11 | // `diamonds/`, `prompts/`, `DAIMOND.md` — which `renderTree` deliberately |
| 12 | // filters out of the tree above it (a × beside `diamonds/` would delete |
| 13 | // every Diamond you have). |
| 14 | // 2. It is a section, NOT the tree: nothing from the store leaks into the |
| 15 | // workspace listing, and nothing in the section carries a delete control. |
| 16 | // 3. Its rows open in the Doc panel and read from the STORE, which is a |
| 17 | // different root from the workspace whenever a folder is open. |
| 18 | // 4. A role prompt lives in the store, so it does not change under the user |
| 19 | // when they open a folder. This was the deferral's whole reason: doing the |
| 20 | // instructions half alone would leave the user's rules surviving a root |
| 21 | // switch while the agent's own prompt silently changed. |
| 22 | // 5. `DAIMOND.md` is TWO layers: the user's own from the store, always in |
| 23 | // force, and the project's from the folder, appended below it under a |
| 24 | // heading that says whose it is. |
| 25 | // 6. With no folder open there is one layer, not the same file counted twice. |
| 26 | // |
| 27 | // Properties 5 and 6 are measured through `DaimondPrompts`/`Instructions`' |
| 28 | // composed text rather than from a screenshot, because what matters is what |
| 29 | // reaches the MODEL. |
| 30 | // |
| 31 | // node dev/verify_system.mjs |
| 32 | // node dev/verify_system.mjs --break hidden # the store stays invisible |
| 33 | // node dev/verify_system.mjs --break onelayer # the project's copy wins outright |
| 34 | // |
| 35 | // Needs dev/serve.mjs (dev/world.sh N --up). No gateway and no model needed, |
| 36 | // but a connected provider makes the Diamond path work. |
| 37 | import fs from 'node:fs'; |
| 38 | import path from 'node:path'; |
| 39 | import os from 'node:os'; |
| 40 | import { open, connectMock, scratch, shot } from './harness.mjs'; |
| 41 | |
| 42 | const OUT = path.join(os.homedir(), '.cache/daimond/system-shots'); |
| 43 | fs.mkdirSync(OUT, { recursive: true }); |
| 44 | |
| 45 | const BI = process.argv.indexOf('--break'); |
| 46 | const BEQ = process.argv.find(a => a.startsWith('--break=')); |
| 47 | const BREAK = BEQ ? BEQ.split('=')[1] : (BI >= 0 ? (process.argv[BI + 1] || '') : ''); |
| 48 | |
| 49 | let failures = 0; |
| 50 | const check = (cond, msg, detail) => { |
| 51 | console.log((cond ? ' ok ' : ' FAIL ') + msg + (detail != null ? ' — ' + detail : '')); |
| 52 | if (!cond) failures++; |
| 53 | }; |
| 54 | |
| 55 | const MODEL = 'accounts/fireworks/models/glm-5p2'; |
| 56 | |
| 57 | const s = await open({ name: 'system', profile: scratch('pw', 'system-' + process.pid) }); |
| 58 | const { page: p } = s; |
| 59 | try { |
| 60 | await connectMock(s, { model: MODEL }); |
| 61 | if (BREAK) console.log(` .. running with --break ${BREAK}`); |
| 62 | if (BREAK === 'hidden') { |
| 63 | // The section removed: what the panel looked like before this phase, when |
| 64 | // the only answer to "where is DAIMOND.md" was "in a place you cannot see". |
| 65 | await p.addStyleTag({ content: '#sys-sec { display: none !important; }' }); |
| 66 | } |
| 67 | |
| 68 | // Something in the store to look at: a Diamond, and a role prompt seeded from |
| 69 | // its shipped default. |
| 70 | await p.evaluate(() => document.getElementById('new-diamond-btn').click()); |
| 71 | await p.waitForSelector('.dlg-card', { timeout: 8000 }); |
| 72 | await p.evaluate(() => { |
| 73 | const c = [...document.querySelectorAll('.dlg-card')] |
| 74 | .filter(x => x.getClientRects().length).pop(); |
| 75 | const i = c.querySelector('input.dlg-input'); |
| 76 | i.value = 'Dee'; |
| 77 | i.dispatchEvent(new Event('input', { bubbles: true })); |
| 78 | c.querySelector('.dlg-ok').click(); |
| 79 | }); |
| 80 | await p.waitForTimeout(1200); |
| 81 | await p.evaluate(() => DaimondPrompts.edit('compactor')); |
| 82 | await p.waitForTimeout(1800); |
| 83 | |
| 84 | // ══ 1. The section lists the store ════════════════════════════════ |
| 85 | // Opened the way a person does: the section is collapsed until asked for. |
| 86 | const collapsed = await p.evaluate(() => { |
| 87 | const h = document.getElementById('sys-head'); |
| 88 | return h ? h.getAttribute('aria-expanded') : null; |
| 89 | }); |
| 90 | await p.evaluate(() => { const h = document.getElementById('sys-head'); if (h) h.click(); }); |
| 91 | await p.waitForTimeout(700); |
| 92 | const sec = await p.evaluate(() => { |
| 93 | const el = document.getElementById('sys-sec'); |
| 94 | if (!el) return null; |
| 95 | return { |
| 96 | shown: el.getClientRects().length > 0, |
| 97 | rows: [...document.querySelectorAll('#sys-tree .sys-row')] |
| 98 | .map(r => (r.querySelector('.files-name') || r).textContent.trim()), |
| 99 | }; |
| 100 | }); |
| 101 | check(sec !== null, 'the Workspace panel has a System section'); |
| 102 | check(!!(sec && sec.shown), 'and it is on screen'); |
| 103 | check(collapsed === 'false', |
| 104 | 'collapsed by default — it answers a question rather than being where the work is', |
| 105 | String(collapsed)); |
| 106 | check(!!(sec && sec.rows.some(r => /diamonds/.test(r))), |
| 107 | 'diamonds/ is listed', sec && sec.rows.join(', ')); |
| 108 | check(!!(sec && sec.rows.some(r => /prompts/.test(r))), |
| 109 | 'prompts/ is listed', sec && sec.rows.join(', ')); |
| 110 | |
| 111 | // ══ 2. It is a section, not the tree ══════════════════════════════ |
| 112 | const tree = await p.evaluate(() => |
| 113 | [...document.querySelectorAll('.files-tree .files-row')] |
| 114 | .map(r => (r.querySelector('.files-name') || r).textContent.trim())); |
| 115 | check(!tree.some(r => /diamonds/.test(r)), |
| 116 | 'and the workspace tree above it still does NOT list the store', |
| 117 | tree.join(', ') || '(empty)'); |
| 118 | const dels = await p.evaluate(() => |
| 119 | [...document.querySelectorAll('#sys-tree .sys-row')] |
| 120 | .filter(r => r.querySelector('.files-del') |
| 121 | && getComputedStyle(r.querySelector('.files-del')).display !== 'none').length); |
| 122 | check(dels === 0, 'no row in the section carries a delete control', String(dels)); |
| 123 | |
| 124 | // ══ 3. A row opens, and reads from the STORE ══════════════════════ |
| 125 | const opened = await p.evaluate(async () => { |
| 126 | const row = [...document.querySelectorAll('#sys-tree .sys-row')] |
| 127 | .find(r => /prompts/.test(r.textContent || '')); |
| 128 | if (!row) return 'no prompts row'; |
| 129 | row.click(); |
| 130 | return 'ok'; |
| 131 | }); |
| 132 | check(opened === 'ok', 'the prompts/ row can be opened', opened); |
| 133 | await p.waitForTimeout(900); |
| 134 | const inside = await p.evaluate(() => |
| 135 | [...document.querySelectorAll('#sys-tree .sys-row')] |
| 136 | .map(r => (r.querySelector('.files-name') || r).textContent.trim())); |
| 137 | check(inside.some(r => /compactor\.md/.test(r)), |
| 138 | 'and the role prompt is inside it', inside.join(', ')); |
| 139 | |
| 140 | // ══ 4. A role prompt lives in the store, not the folder ═══════════ |
| 141 | // |
| 142 | // Proved where it can be: the file is READ through the store door. A real |
| 143 | // folder cannot be opened here (`showDirectoryPicker` needs a user gesture |
| 144 | // the harness cannot supply), so what is checked is that the read goes to |
| 145 | // the store at all — which is what makes it survive a root switch. |
| 146 | const promptSrc = await p.evaluate(async () => { |
| 147 | const W = await import('/pkg/oxedyne_daimond.js'); |
| 148 | let store = null, ws = null; |
| 149 | try { store = await W.store_read('prompts/compactor.md'); } catch (e) { store = null; } |
| 150 | try { ws = await W.read_file('prompts/compactor.md'); } catch (e) { ws = null; } |
| 151 | return { store: (store || '').slice(0, 60), ws: (ws || '').slice(0, 60), |
| 152 | held: (DaimondPrompts.md.compactor || '').slice(0, 60) }; |
| 153 | }); |
| 154 | check(!!promptSrc.store, 'the compactor prompt is written in the store', |
| 155 | promptSrc.store.slice(0, 40)); |
| 156 | check(promptSrc.held === promptSrc.store, |
| 157 | 'and what the app holds is what the STORE says', promptSrc.held.slice(0, 40)); |
| 158 | |
| 159 | // ══ 5–6. DAIMOND.md in two layers ═════════════════════════════════ |
| 160 | const layers = await p.evaluate(async (brk) => { |
| 161 | const W = await import('/pkg/oxedyne_daimond.js'); |
| 162 | await W.store_write('DAIMOND.md', 'Always answer in British English.'); |
| 163 | const I = window.DaimondInstructions; |
| 164 | if (!I) return null; |
| 165 | // One layer: no folder is open, so the project's copy is the same file and |
| 166 | // must not be counted twice. |
| 167 | await I.refresh(); |
| 168 | const one = { md: I.md, mine: I.mine, theirs: I.theirs }; |
| 169 | // Two layers, simulated at the seam the folder would arrive through. A real |
| 170 | // folder needs a user gesture the harness cannot make; what is under test is |
| 171 | // the COMPOSITION, and that is this function. |
| 172 | I.theirs = brk === 'onelayer' ? 'Use tabs, not spaces.' : 'Use tabs, not spaces.'; |
| 173 | if (brk === 'onelayer') I.mine = ''; // the project's wins outright |
| 174 | const two = { md: I.layered(), mine: I.mine, theirs: I.theirs }; |
| 175 | return { one, two }; |
| 176 | }, BREAK); |
| 177 | if (!layers) { |
| 178 | check(false, 'the instructions module is reachable'); |
| 179 | } else { |
| 180 | check(layers.one.mine === 'Always answer in British English.', |
| 181 | 'your own DAIMOND.md is read from the store', layers.one.mine); |
| 182 | check(layers.one.theirs === '', |
| 183 | 'with no folder open there is no second layer', `"${layers.one.theirs}"`); |
| 184 | check(layers.one.md === layers.one.mine, |
| 185 | 'so the composed text is exactly yours, not yours twice over', |
| 186 | layers.one.md); |
| 187 | check(/British English/.test(layers.two.md), |
| 188 | 'with a folder open, YOUR rules are still in force', layers.two.md.slice(0, 80)); |
| 189 | check(/Use tabs/.test(layers.two.md), |
| 190 | 'and the project’s are too', layers.two.md.slice(0, 120)); |
| 191 | check(layers.two.md.indexOf('British English') < layers.two.md.indexOf('Use tabs'), |
| 192 | 'yours first: a project cannot quietly overrule how you work', |
| 193 | layers.two.md.replace(/\n+/g, ' / ')); |
| 194 | check(/## For this project/.test(layers.two.md), |
| 195 | 'and the project’s are headed, so a model can tell whose is whose', |
| 196 | layers.two.md.replace(/\n+/g, ' / ')); |
| 197 | } |
| 198 | await shot(s, 'system'); |
| 199 | |
| 200 | } catch (e) { |
| 201 | check(false, 'the run finished', String(e && e.message || e)); |
| 202 | try { await shot(s, 'threw'); } catch {} |
| 203 | } finally { |
| 204 | await s.close(); |
| 205 | } |
| 206 | |
| 207 | console.log(failures === 0 |
| 208 | ? `\nverify_system: all checks pass.` |
| 209 | : `\nverify_system: ${failures} failed.`); |
| 210 | process.exit(failures === 0 ? 0 : 1); |