oxedyne/daimond/dev/verify_diamondroot_recent.mjs
8.6 KiB, 1 run
created by r2519314175:365, 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_diamondroot_recent.mjs — the migration EVERY current user will take. |
| 2 | // |
| 3 | // verify_diamondroot.mjs seeds the oldest shape a workspace can have, `foci/` |
| 4 | // with a `.red/` store. This one seeds the shape almost every real workspace is |
| 5 | // in right now: `facets/` with a `.daimond/` store and a `brief.md`, written by |
| 6 | // the build that shipped yesterday. It is the likelier migration by far, and it |
| 7 | // exercises a different path -- the root move has only ONE hop to make and the |
| 8 | // store move has none, so a bug that hides behind the older seed's two-stage |
| 9 | // walk would show up here instead. |
| 10 | // |
| 11 | // What must survive: the Diamond itself, its crystal (which is converted from |
| 12 | // markdown to data in the same pass, so it arrives under a third name again), |
| 13 | // its version number (held in a field that has been renamed under it), its |
| 14 | // history, and any links it had asserted, whose stored ends name a kind that no |
| 15 | // longer exists. |
| 16 | // |
| 17 | // Run with dev/serve.mjs up. No gateway needed. |
| 18 | import { open, signInAs } from './harness.mjs'; |
| 19 | |
| 20 | const ok = [], bad = []; |
| 21 | const check = (name, pass, detail) => { |
| 22 | (pass ? ok : bad).push(name + (detail ? ' — ' + detail : '')); |
| 23 | console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : '')); |
| 24 | }; |
| 25 | |
| 26 | const A = 'recent1', B = 'recent2'; |
| 27 | |
| 28 | const s = await open({ name: 'diamondroot-recent', connect: false }); |
| 29 | const p = s.page; |
| 30 | await p.waitForTimeout(1200); |
| 31 | |
| 32 | // ── Seed yesterday's shape ────────────────────────────────────────────── |
| 33 | const seeded = await p.evaluate(async ({ a, b }) => { |
| 34 | const mod = await import('../pkg/oxedyne_daimond.js'); |
| 35 | const w = (path, content) => mod.write_file(path, content); |
| 36 | |
| 37 | for (const [id, name] of [[a, 'Ship the launch'], [b, 'Brand voice']]) { |
| 38 | await w(`facets/${id}/brief.md`, `# ${name}\n\nWritten yesterday, under the old names.\n`); |
| 39 | await w(`facets/${id}/versions/0000.md`, ''); |
| 40 | await w(`facets/${id}/versions/0001.md`, `# ${name}\n\nWritten yesterday.\n`); |
| 41 | await w(`facets/${id}/.daimond/meta.json`, |
| 42 | `{"name":"${name}","brief_version":1,"updated":1750000000000,"tags":["live"]}`); |
| 43 | await w(`facets/${id}/.daimond/deltas/0001.md`, `DELTA-${id}: what that fold consumed.`); |
| 44 | await w(`facets/${id}/.daimond/log`, |
| 45 | `{"id":"r1","ts":1750000000000,"kind":"create","agent":"user","task":"create",` |
| 46 | + `"parent_brief_version":-1,"brief_version":0,"delta_ref":"","note":"${name}"}\n` |
| 47 | + `{"id":"r2","ts":1750000000001,"kind":"fold","agent":"reducer","task":"fold delta",` |
| 48 | + `"parent_brief_version":0,"brief_version":1,` |
| 49 | + `"delta_ref":"facets/${id}/.daimond/deltas/0001.md","note":"folded yesterday"}\n`); |
| 50 | } |
| 51 | |
| 52 | // A link asserted between the two, with its ends named the way the substrate |
| 53 | // spelled a Diamond yesterday. |
| 54 | await w(`facets/${a}/.daimond/links.jsonl`, |
| 55 | `{"id":"l1","ts":1750000000002,"from":"facet:${a}","to":"facet:${b}",` |
| 56 | + `"rel":"informs","note":"The launch copy has to sound like this.","by":"user"}\n` |
| 57 | + `{"id":"l2","ts":1750000000003,"from":"facet:${a}","to":"file:notes/pricing.md",` |
| 58 | + `"rel":"produced","note":"","by":"agent:worker"}\n`); |
| 59 | |
| 60 | localStorage.setItem('daimond-facet-counter', '4'); |
| 61 | localStorage.setItem('daimond-facet-models', '{"recent1":{"provider":"p","model":"m"}}'); |
| 62 | return true; |
| 63 | }, { a: A, b: B }); |
| 64 | check("yesterday's workspace was seeded under facets/", seeded === true); |
| 65 | |
| 66 | // ── Boot it the way a user does ───────────────────────────────────────── |
| 67 | await p.reload({ waitUntil: 'domcontentloaded' }); |
| 68 | await signInAs(s, 'diamondroot-recent'); |
| 69 | await p.waitForTimeout(2500); |
| 70 | |
| 71 | const listed = await p.$eval('#diamond-list', e => e.textContent); |
| 72 | check('both Diamonds survived the root move', |
| 73 | /Ship the launch/.test(listed) && /Brand voice/.test(listed), listed.trim().slice(0, 70)); |
| 74 | |
| 75 | // ── What is on disk ───────────────────────────────────────────────────── |
| 76 | const disk = await p.evaluate(async ({ a }) => { |
| 77 | const mod = await import('../pkg/oxedyne_daimond.js'); |
| 78 | const read = async (path) => { |
| 79 | try { return await mod.read_file(path); } catch (e) { return null; } |
| 80 | }; |
| 81 | return { |
| 82 | crystal: await read(`diamonds/${a}/crystal.json`), |
| 83 | oldFile: await read(`diamonds/${a}/brief.md`), |
| 84 | oldMd: await read(`diamonds/${a}/crystal.md`), |
| 85 | meta: await read(`diamonds/${a}/.daimond/meta.json`), |
| 86 | log: await read(`diamonds/${a}/.daimond/log`), |
| 87 | delta: await read(`diamonds/${a}/.daimond/deltas/0001.md`), |
| 88 | version: await read(`diamonds/${a}/versions/0001.md`), |
| 89 | oldRoot: await read(`facets/${a}/brief.md`), |
| 90 | }; |
| 91 | }, { a: A }); |
| 92 | |
| 93 | check('brief.md became crystal.json', /Ship the launch/.test(disk.crystal || '')); |
| 94 | // Two different kinds of hop. `brief.md` -> `crystal.md` is a RENAME and deletes; |
| 95 | // `crystal.md` -> `crystal.json` is a CONVERSION and keeps its source, because |
| 96 | // its self-check proves the bytes round-trip and cannot prove the structure is |
| 97 | // right. |
| 98 | check('and brief.md itself is gone', disk.oldFile === null, 'brief.md=' + disk.oldFile); |
| 99 | check('while crystal.md is kept beside the data it became', |
| 100 | disk.oldMd !== null, 'crystal.md=' + disk.oldMd); |
| 101 | check('the markdown snapshots came across', /Ship the launch/.test(disk.version || '')); |
| 102 | check('the retained delta came across', /DELTA-recent1/.test(disk.delta || '')); |
| 103 | check('nothing was left behind under facets/', disk.oldRoot === null); |
| 104 | check('the delta_ref was rewritten to the new root', |
| 105 | !!disk.log && disk.log.includes(`diamonds/${A}/.daimond/deltas/0001.md`) |
| 106 | && !disk.log.includes('facets/'), |
| 107 | (disk.log || '').split('\n')[1]?.slice(0, 80)); |
| 108 | |
| 109 | // ── The version, held in a field renamed under it ─────────────────────── |
| 110 | const rows = await p.evaluate(async () => { |
| 111 | const mod = await import('../pkg/oxedyne_daimond.js'); |
| 112 | const app = new mod.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true); |
| 113 | return JSON.parse(await app.list_diamonds() || '[]'); |
| 114 | }); |
| 115 | const rowA = rows.find(r => r.id === A); |
| 116 | check('the version survived the field being renamed under it', |
| 117 | rowA && rowA.crystal_version === 1, String(rowA && rowA.crystal_version)); |
| 118 | check('and so did the tags', !!rowA && JSON.stringify(rowA.tags || []).includes('live'), |
| 119 | JSON.stringify(rowA && rowA.tags)); |
| 120 | |
| 121 | // ── The links, whose stored ends name a kind that is gone ─────────────── |
| 122 | const links = await p.evaluate(async ({ a }) => { |
| 123 | const mod = await import('../pkg/oxedyne_daimond.js'); |
| 124 | const app = new mod.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true); |
| 125 | return { |
| 126 | fromNew: JSON.parse(await app.links_touching(`diamond:${a}`) || '[]'), |
| 127 | fromOld: JSON.parse(await app.links_touching(`facet:${a}`) || '[]'), |
| 128 | }; |
| 129 | }, { a: A }); |
| 130 | check('a link asserted before the rename is found under the NEW kind', |
| 131 | links.fromNew.length === 2, `${links.fromNew.length} found`); |
| 132 | check('its far end was rewritten too, not just the near one', |
| 133 | links.fromNew.some(l => l.other === `diamond:${B}`), |
| 134 | links.fromNew.map(l => l.other).join(', ')); |
| 135 | check('a non-Diamond end was left exactly as it was', |
| 136 | links.fromNew.some(l => l.other === 'file:notes/pricing.md'), |
| 137 | links.fromNew.map(l => l.other).join(', ')); |
| 138 | check('and nothing answers to the old kind any more', |
| 139 | links.fromOld.length === 0, `${links.fromOld.length} found`); |
| 140 | |
| 141 | // ── The keys, one generation back rather than two ─────────────────────── |
| 142 | const keys = await p.evaluate(() => ({ |
| 143 | counter: localStorage.getItem('daimond-diamond-counter'), |
| 144 | models: localStorage.getItem('daimond-diamond-models'), |
| 145 | old: localStorage.getItem('daimond-facet-counter'), |
| 146 | })); |
| 147 | check('the counter carried over from the facet-era key', keys.counter === '4', String(keys.counter)); |
| 148 | check('so did the per-Diamond model choices', /recent1/.test(keys.models || '')); |
| 149 | check('and the old key was dropped', keys.old === null); |
| 150 | |
| 151 | // ── Idempotence ───────────────────────────────────────────────────────── |
| 152 | await p.reload({ waitUntil: 'domcontentloaded' }); |
| 153 | await signInAs(s, 'diamondroot-recent'); |
| 154 | await p.waitForTimeout(2500); |
| 155 | const again = await p.$eval('#diamond-list', e => e.textContent); |
| 156 | check('a second boot leaves it alone', |
| 157 | /Ship the launch/.test(again) && /Brand voice/.test(again), again.trim().slice(0, 60)); |
| 158 | |
| 159 | await s.close(); |
| 160 | console.log(`\n${ok.length} passed, ${bad.length} failed`); |
| 161 | if (bad.length) console.log('FAILED:\n ' + bad.join('\n ')); |
| 162 | process.exit(bad.length ? 1 : 0); |