Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_sharedbaseline.mjs

7.4 KiB, 1 run

created by r2519314175:677, 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_sharedbaseline.mjs — the sync fork point records only what was SHARED.
2//
3// The baseline is the state both devices agreed on, and `applyFiles` reads it as
4// exactly that: a path in the baseline that a COMPLETE remote census does not
5// carry has been deleted there, so it is deleted here.
6//
7// It was written on the way out of a PULL, from everything this device happened
8// to be holding. A file created locally and never yet sent anywhere was
9// therefore entered as "agreed" by a pull that carried no news of it — and the
10// other device's next complete census, which had never heard of the file,
11// deleted it. Nothing was wrong on either side; the record of agreement was a
12// record of a conversation that never happened.
13//
14// A file cannot be agreed until it has been sent, and the moment it has is a
15// successful push. That is what `commitFileBaseline` is for and where sync.js
16// already calls it.
17//
18// The second check is the cloud index's fork point, which had the same shape of
19// fault for a different reason: `applyFiles` runs before `applyChunked` in
20// `applySync` and rewrites the cloud baseline on its way out, so the chunk merge
21// compared local against ITSELF. `localChanged` was false for every path, the
22// remote won unconditionally, and cloud.js's both-sides-diverged branch could
23// never run.
24//
25// node dev/verify_sharedbaseline.mjs
26//
27// Needs dev/serve.mjs (DAIMOND_PORT, default 8777). No gateway: applySync is driven
28// directly, which is what lets a "remote" parcel be fabricated with a known census.
29import fs from 'node:fs';
30import { open, scratch } from './harness.mjs';
31
32const PROFILE = scratch('pw', 'sharedbaseline');
33fs.rmSync(PROFILE, { recursive: true, force: true });
34
35let bad = 0;
36const check = (pass, name, detail) => {
37 if (!pass) bad++;
38 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
39};
40
41const s = await open({ name: 'sharedbaseline', profile: PROFILE, connect: false });
42const { page } = s;
43
44try {
45 await page.waitForFunction(() => !!(window.DaimondCore && DaimondCore.collectSync),
46 null, { timeout: 15000 });
47 await page.waitForTimeout(900);
48
49 const write = (path, content) => page.evaluate(async (a) => {
50 const mod = await import('../pkg/oxedyne_daimond.js');
51 const app = new mod.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true);
52 await app.run_tool('file_write', JSON.stringify({ path: a.path, content: a.content }));
53 }, { path, content });
54
55 const exists = (path) => page.evaluate(async (p) => {
56 try {
57 const dir = await DaimondCloud.opfsRoot();
58 await dir.getFileHandle(p);
59 return true;
60 } catch (e) { return false; }
61 }, path);
62
63 const baseline = () => page.evaluate(() => {
64 try { return JSON.parse(localStorage.getItem('daimond-sync-filebase') || '{}'); }
65 catch (e) { return {}; }
66 });
67
68 // ── The setup: one file both devices know, one only this device has ──
69 await write('shared.md', 'both devices have this\n');
70 await write('mine-only.md', 'made here, never sent anywhere\n');
71 await page.waitForTimeout(400);
72
73 // A parcel from the other device that carries the shared file and, truthfully,
74 // has never heard of the local one. `filesComplete` is what entitles the
75 // receiver to delete by absence, so it is the whole point of the fixture.
76 const report = await page.evaluate(async () => {
77 const mine = await DaimondCore.collectSync();
78 const remote = {
79 v: 2, chats: [], tombs: {}, msgTombs: {},
80 files: { 'shared.md': mine.files['shared.md'] },
81 filesComplete: true,
82 diamonds: [], diamondTombs: {}, chunked: {},
83 };
84 const r = await DaimondCore.applySync(remote);
85 return r && r.failed ? r.failed : [];
86 });
87 check(report.length === 0, 'the fabricated parcel merges cleanly', report.join(','));
88
89 // ── 1. A file that never left must survive ──────────────────────
90 check(await exists('mine-only.md'),
91 'a file this device has never sent survives a complete census that lacks it');
92 check(await exists('shared.md'), 'and the file both devices hold is still here');
93
94 const base1 = await baseline();
95 check(!Object.prototype.hasOwnProperty.call(base1, 'mine-only.md'),
96 'and it is NOT recorded as agreed, having never been sent',
97 JSON.stringify(Object.keys(base1)));
98 check(Object.prototype.hasOwnProperty.call(base1, 'shared.md'),
99 'while the shared file IS', JSON.stringify(Object.keys(base1)));
100
101 // ── 2. A second round must not change its mind ──────────────────
102 // The bug deleted on the round AFTER the one that recorded the agreement, so
103 // one pull is not enough to prove anything.
104 await page.evaluate(async () => {
105 const mine = await DaimondCore.collectSync();
106 await DaimondCore.applySync({
107 v: 2, chats: [], tombs: {}, msgTombs: {},
108 files: { 'shared.md': mine.files['shared.md'] },
109 filesComplete: true, diamonds: [], diamondTombs: {}, chunked: {},
110 });
111 });
112 await page.waitForTimeout(400);
113 check(await exists('mine-only.md'),
114 'and it survives a SECOND complete census, which is when it used to go');
115
116 // ── 3. A real deletion is still honoured ────────────────────────
117 // The fix must not turn the delete-by-absence rule off: a file that WAS
118 // shared, and that a complete census no longer carries, is still gone.
119 await page.evaluate(async () => {
120 await DaimondCore.applySync({
121 v: 2, chats: [], tombs: {}, msgTombs: {},
122 files: {}, filesComplete: true, diamonds: [], diamondTombs: {}, chunked: {},
123 });
124 });
125 await page.waitForTimeout(400);
126 check(!(await exists('shared.md')),
127 'a file that WAS shared and is now absent from a complete census is deleted');
128 check(await exists('mine-only.md'),
129 'and the never-sent file is still not collateral');
130 const base2 = await baseline();
131 check(!Object.prototype.hasOwnProperty.call(base2, 'shared.md'),
132 'the deleted path leaves the fork point with it', JSON.stringify(Object.keys(base2)));
133
134 // ── 4. The cloud fork point is not moved by a pull ──────────────
135 // `applyFiles` used to commit it on its way out, and `applyChunked` runs
136 // after it in `applySync` -- so the chunk merge compared this device's index
137 // against a copy of itself taken moments earlier. Every path read as
138 // "unchanged locally", the remote won unconditionally, and the branch that
139 // preserves a doubly-diverged file as `.synced` was unreachable. A pull must
140 // leave the fork point where the last successful PUSH put it.
141 //
142 // The key is `daimond-cloud-base`. This check first named it
143 // `daimond-sync-cloudbase`, which nothing in the app writes -- so it wrote a
144 // sentinel nobody could disturb and passed without testing anything.
145 await page.evaluate(() => {
146 localStorage.setItem('daimond-cloud-base', JSON.stringify({ 'sentinel.bin': 'from-the-last-push' }));
147 });
148 await page.evaluate(async () => {
149 await DaimondCore.applySync({
150 v: 2, chats: [], tombs: {}, msgTombs: {},
151 files: {}, filesComplete: false, diamonds: [], diamondTombs: {}, chunked: {},
152 });
153 });
154 await page.waitForTimeout(400);
155 const cloudBase = await page.evaluate(() => {
156 try { return JSON.parse(localStorage.getItem('daimond-cloud-base') || '{}'); }
157 catch (e) { return {}; }
158 });
159 check(cloudBase['sentinel.bin'] === 'from-the-last-push',
160 'a pull leaves the cloud fork point where the last push put it',
161 JSON.stringify(cloudBase));
162
163} finally {
164 await s.close();
165}
166
167console.log(bad === 0 ? '\nall checks passed' : `\n${bad} check(s) FAILED`);
168process.exit(bad === 0 ? 0 : 1);