Oregami
Repositories/oxedyne/daimond

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.
18import { open, signInAs } from './harness.mjs';
19
20const ok = [], bad = [];
21const check = (name, pass, detail) => {
22 (pass ? ok : bad).push(name + (detail ? ' — ' + detail : ''));
23 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
24};
25
26const A = 'recent1', B = 'recent2';
27
28const s = await open({ name: 'diamondroot-recent', connect: false });
29const p = s.page;
30await p.waitForTimeout(1200);
31
32// ── Seed yesterday's shape ──────────────────────────────────────────────
33const 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 });
64check("yesterday's workspace was seeded under facets/", seeded === true);
65
66// ── Boot it the way a user does ─────────────────────────────────────────
67await p.reload({ waitUntil: 'domcontentloaded' });
68await signInAs(s, 'diamondroot-recent');
69await p.waitForTimeout(2500);
70
71const listed = await p.$eval('#diamond-list', e => e.textContent);
72check('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 ─────────────────────────────────────────────────────
76const 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
93check('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.
98check('and brief.md itself is gone', disk.oldFile === null, 'brief.md=' + disk.oldFile);
99check('while crystal.md is kept beside the data it became',
100 disk.oldMd !== null, 'crystal.md=' + disk.oldMd);
101check('the markdown snapshots came across', /Ship the launch/.test(disk.version || ''));
102check('the retained delta came across', /DELTA-recent1/.test(disk.delta || ''));
103check('nothing was left behind under facets/', disk.oldRoot === null);
104check('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 ───────────────────────
110const 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});
115const rowA = rows.find(r => r.id === A);
116check('the version survived the field being renamed under it',
117 rowA && rowA.crystal_version === 1, String(rowA && rowA.crystal_version));
118check('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 ───────────────
122const 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 });
130check('a link asserted before the rename is found under the NEW kind',
131 links.fromNew.length === 2, `${links.fromNew.length} found`);
132check('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(', '));
135check('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(', '));
138check('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 ───────────────────────
142const 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}));
147check('the counter carried over from the facet-era key', keys.counter === '4', String(keys.counter));
148check('so did the per-Diamond model choices', /recent1/.test(keys.models || ''));
149check('and the old key was dropped', keys.old === null);
150
151// ── Idempotence ─────────────────────────────────────────────────────────
152await p.reload({ waitUntil: 'domcontentloaded' });
153await signInAs(s, 'diamondroot-recent');
154await p.waitForTimeout(2500);
155const again = await p.$eval('#diamond-list', e => e.textContent);
156check('a second boot leaves it alone',
157 /Ship the launch/.test(again) && /Brand voice/.test(again), again.trim().slice(0, 60));
158
159await s.close();
160console.log(`\n${ok.length} passed, ${bad.length} failed`);
161if (bad.length) console.log('FAILED:\n ' + bad.join('\n '));
162process.exit(bad.length ? 1 : 0);