oxedyne/daimond/dev/verify_writeplace.mjs
13.4 KiB, 1 run
created by r2519314175:833, 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_writeplace.mjs — a write that landed in browser storage never reads as a write to disk. |
| 2 | // |
| 3 | // THE DEFECT, AND IT IS THE WORST SHAPE THE OPFS/DISK SPLIT TAKES. Daimond has two |
| 4 | // filesystems: the browser's own storage, which the file tools reach, and the folder on |
| 5 | // this computer, which only `run` reaches. `two_places_note` (src/tools.rs, lane H, |
| 6 | // 2026-08-24) taught seven tools to say which one they had just answered about, and |
| 7 | // `absent_here` wired them up. `file_write` was left out, because the note works by |
| 8 | // asking whether the path EXISTS and a write to a path that does not exist is the |
| 9 | // ordinary case. |
| 10 | // |
| 11 | // What was left is not a missing sentence. `opfs::descend` calls `set_create(true)` on |
| 12 | // every directory component it walks, so a write into a folder browser storage does not |
| 13 | // hold MAKES THE FOLDER instead of failing. A daimon that has just found `src/tools.rs` |
| 14 | // with a `grep` on the machine, and writes to it with `file_write`, is answered |
| 15 | // |
| 16 | // Wrote 812 bytes to src/tools.rs. |
| 17 | // |
| 18 | // with outcome `done`. A folder named `src` now exists in browser storage that nobody |
| 19 | // asked for, holding a file the daimon believes is on disk. And the deceit compounds: |
| 20 | // `file_read` of that path now SUCCEEDS, `file_list` of that folder lists the file, and |
| 21 | // `absent_here` answers nothing anywhere, because the entry is present. Every check the |
| 22 | // daimon can afterwards make agrees that the work landed. Nothing on the machine changed. |
| 23 | // |
| 24 | // That is why a sentence alone was judged insufficient here and is not what this pins. A |
| 25 | // note on the answer is one line contradicted by every later signal; the write itself has |
| 26 | // to not happen. |
| 27 | // |
| 28 | // SEVEN PROPERTIES: |
| 29 | // |
| 30 | // 0. NO HAND, NO NOISE. With no machine hand paired there is one filesystem and nothing |
| 31 | // to disambiguate, so an ordinary write answers exactly as it always did. Without |
| 32 | // this, a build that refused or annotated every write would pass everything below. |
| 33 | // 1. A WRITE THAT CANNOT REACH THE MACHINE DOES NOT PRETEND TO. Hand paired, no folder |
| 34 | // open, and the write would have to invent a folder to land: it is REFUSED, and — |
| 35 | // the load-bearing half — NOTHING IS CREATED. Not the file, not the folder. |
| 36 | // 2. THE REFUSAL IS A ROUTE. It names `run` for the machine, `dir_create` for browser |
| 37 | // storage, and opening the folder for making the two one place. |
| 38 | // 3. AND THE ROUTE IS ONE CALL. `dir_create` then the same write lands. A daimon that |
| 39 | // really meant browser storage is delayed by one call, not stopped. |
| 40 | // 4. A WRITE THAT DOES LAND SAYS WHERE IT LANDED. The folder is already there, so |
| 41 | // nothing is invented and nothing is refused — but the answer still names the |
| 42 | // filesystem, because a daimon reading `Wrote 812 bytes` has been told nothing. |
| 43 | // 5. AND ITS FIRST LINE IS UNCHANGED. `dev/verify_replylen.mjs` reads `Wrote (\d+) |
| 44 | // bytes to <file>` out of a tool answer. The note goes on a SECOND line, for the |
| 45 | // reason `dev/verify_refusedpath.mjs` check 1c records: a reader that stops |
| 46 | // recognising an answer does not read it as unknown, it reads it as something else. |
| 47 | // 6. A DIAMOND'S OWN STORAGE IS NOT SECOND-GUESSED. `diamonds/`, `chats/` and `mail/` |
| 48 | // ARE browser storage by design — the system prompt says so and the page's own mail |
| 49 | // writer creates folders under `mail/` on every refresh. Refusing those would break |
| 50 | // the product to fix a different fault. |
| 51 | // |
| 52 | // THE RED, AND HOW IT WAS TAKEN. The breaks here are in Rust, so they are not `--break` |
| 53 | // flags over `www/js/daimond.js` the way `dev/verify_refusedpath.mjs`'s are: the engine |
| 54 | // has to be rebuilt, which is two minutes. The world before the fix is the whole of it — |
| 55 | // `Tool::FileWrite` with no `write_place` call at all — and this file was written and run |
| 56 | // against that bundle first. What it printed is in the commit message. Reproduce it by |
| 57 | // deleting the two lines in the `Tool::FileWrite` arm that read |
| 58 | // |
| 59 | // let place = res!(write_place(ctx, &raw, &path).await); |
| 60 | // if let WritePlace::Inventing(dir) = &place { |
| 61 | // |
| 62 | // with their bodies, and the `place_line` that follows, then `bash dev/build-wasm.sh`. |
| 63 | // Checks 1, 2 and 4 go red together; 1's second half is the one to watch, because it is |
| 64 | // the only one that says the bytes went somewhere. |
| 65 | // |
| 66 | // eval "$(bash dev/world.sh 6 --up)" |
| 67 | // node dev/verify_writeplace.mjs |
| 68 | // |
| 69 | // Needs dev/serve.mjs. No mock and no gateway: every call here goes straight to the real |
| 70 | // registry through `run_tool_outcome`, which is the door `dev/CONTRACT_OUTCOME.md` §1 |
| 71 | // defines and the only one that answers what became of a call. |
| 72 | import fs from 'node:fs'; |
| 73 | import path from 'node:path'; |
| 74 | import { fileURLToPath } from 'node:url'; |
| 75 | import { open, scratch } from './harness.mjs'; |
| 76 | |
| 77 | const HERE = path.dirname(fileURLToPath(import.meta.url)); |
| 78 | |
| 79 | let bad = 0; |
| 80 | const check = (pass, name, detail) => { |
| 81 | if (!pass) bad++; |
| 82 | console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : '')); |
| 83 | }; |
| 84 | |
| 85 | const PROFILE = scratch('pw', 'writeplace'); |
| 86 | fs.rmSync(PROFILE, { recursive: true, force: true }); |
| 87 | |
| 88 | const s = await open({ name: 'writeplace', profile: PROFILE, connect: false }); |
| 89 | const { page: p } = s; |
| 90 | |
| 91 | /// One call through the real registry, with the outcome the engine gave it. |
| 92 | const call = (name, args) => p.evaluate(async (a) => { |
| 93 | const mod = await import('../pkg/oxedyne_daimond.js'); |
| 94 | const app = new mod.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true); |
| 95 | const r = await app.run_tool_outcome(a.name, JSON.stringify(a.args)); |
| 96 | return { outcome: (r && r.outcome) || 'none', text: String((r && r.text) || '') }; |
| 97 | }, { name, args }); |
| 98 | |
| 99 | /// A machine hand, or the page's own relay saying there is none. |
| 100 | /// |
| 101 | /// `hand.js` installs `window.DaimondHand` on EVERY page — it is the shim that answers "no |
| 102 | /// hand is installed" — so the unpaired world is the page LEFT ALONE, and `hasHand()` is |
| 103 | /// what tells the two apart. A REAL pairing needs the extension and a native host, neither |
| 104 | /// of which an automated browser can be given, so the paired world is a stub that answers |
| 105 | /// that one question the way a paired relay does. What is under test is what `file_write` |
| 106 | /// does when a second filesystem exists, not how it came to exist. |
| 107 | const hand = (on) => p.evaluate((v) => { |
| 108 | if (v) { |
| 109 | window.__realHand = window.__realHand || window.DaimondHand; |
| 110 | window.DaimondHand = { hasHand: () => true, status: async () => '{}', run: async () => '{}' }; |
| 111 | } else if (window.__realHand) { |
| 112 | window.DaimondHand = window.__realHand; |
| 113 | } |
| 114 | return !!(window.DaimondHand && window.DaimondHand.hasHand && window.DaimondHand.hasHand()); |
| 115 | }, on); |
| 116 | |
| 117 | /// What browser storage actually holds, asked of the browser and not of the tool that |
| 118 | /// was just refused. A tool that lied about writing would also lie about reading. |
| 119 | const held = (dir, file) => p.evaluate(async (a) => { |
| 120 | const root = await DaimondCloud.opfsRoot(); |
| 121 | const walk = async (parts) => { |
| 122 | let d = root; |
| 123 | for (const seg of parts) { d = await d.getDirectoryHandle(seg); } |
| 124 | return d; |
| 125 | }; |
| 126 | const out = { dir: false, file: false }; |
| 127 | try { await walk(a.dir.split('/').filter(Boolean)); out.dir = true; } catch (e) { /* absent */ } |
| 128 | try { |
| 129 | const parts = a.file.split('/').filter(Boolean); |
| 130 | const d = await walk(parts.slice(0, -1)); |
| 131 | await d.getFileHandle(parts[parts.length - 1]); |
| 132 | out.file = true; |
| 133 | } catch (e) { /* absent */ } |
| 134 | return out; |
| 135 | }, { dir, file }); |
| 136 | |
| 137 | const firstLine = (t) => String(t).split('\n')[0]; |
| 138 | |
| 139 | try { |
| 140 | await p.waitForFunction(() => !!(window.DaimondCloud && window.DaimondCloud.opfsRoot), |
| 141 | null, { timeout: 20000 }); |
| 142 | |
| 143 | // ── 0. The control: one filesystem, and the answer it always gave ─────────── |
| 144 | // |
| 145 | // Not a formality. Every check below reads a sentence the engine adds; a build that |
| 146 | // added it unconditionally would satisfy all of them and would have made the ordinary |
| 147 | // single-filesystem answer worse for every user who has no hand installed. |
| 148 | // The relay is the page's own, untouched, which is the state of every browser that has |
| 149 | // never paired one. `hand::present()` asked `relay().is_ok()` until 2026-08-24 and got |
| 150 | // TRUE here, because the object it looked for is the shim that says "no hand installed" — |
| 151 | // so the two-filesystems note was being told to users who had one filesystem. This check |
| 152 | // is red against that build, and it is the reason `present()` now asks `hasHand()`. |
| 153 | const none = await hand(false); |
| 154 | check(none === false, 'THE PAGE\'S OWN RELAY IS NOT A PAIRED HAND', `hasHand()=${none}`); |
| 155 | const plain = await call('file_write', { path: 'wp0/a/b.txt', content: 'plain' }); |
| 156 | check(plain.outcome === 'done' && /^Wrote \d+ bytes to wp0\/a\/b\.txt\.$/.test(plain.text.trim()), |
| 157 | 'NO HAND, NO NOISE — with one filesystem the answer is the one it always was', |
| 158 | JSON.stringify(plain.text.slice(0, 120))); |
| 159 | |
| 160 | // ── 1. The silence, ended ─────────────────────────────────────────────────── |
| 161 | // |
| 162 | // `wpm` is a folder browser storage does not hold. Before this change the write |
| 163 | // invented it and answered `Wrote 6 bytes to wpm/deep/thing.rs.`, outcome `done`. |
| 164 | const on = await hand(true); |
| 165 | check(on === true, 'THE FIXTURE STANDS: a hand is present and no folder is open', String(on)); |
| 166 | const meant = await call('file_write', { path: 'wpm/deep/thing.rs', content: 'fn main(){}' }); |
| 167 | const after = await held('wpm', 'wpm/deep/thing.rs'); |
| 168 | check(meant.outcome === 'refused', |
| 169 | 'A WRITE THAT CANNOT REACH THE MACHINE DOES NOT REPORT SUCCESS', |
| 170 | `${meant.outcome}: ${JSON.stringify(meant.text.slice(0, 150))}`); |
| 171 | check(after.dir === false && after.file === false, |
| 172 | 'AND NOTHING WAS CREATED — no invented folder, no bytes, so every later read ' |
| 173 | + 'tells the truth too', |
| 174 | `folder wpm ${after.dir ? 'EXISTS' : 'absent'}, file ${after.file ? 'EXISTS' : 'absent'}`); |
| 175 | |
| 176 | // ── 2. A refusal that is a route ──────────────────────────────────────────── |
| 177 | // |
| 178 | // Three ways out, because there are three different things the daimon may have meant, |
| 179 | // and a refusal that names none of them is a wall. `run` is the machine; `dir_create` |
| 180 | // is browser storage, said on purpose; opening the folder is how the user makes the |
| 181 | // two one place and stops this happening at all. |
| 182 | const says = String(meant.text); |
| 183 | check(/\brun\b/.test(says), 'THE REFUSAL NAMES run, for the machine', ''); |
| 184 | check(/\bdir_create\b/.test(says), 'and dir_create, for browser storage said on purpose', ''); |
| 185 | check(/open the folder/.test(says), 'and opening the folder, which makes the two one place', ''); |
| 186 | check(/the file tools reach while no folder is open/.test(says), |
| 187 | 'and it carries the clause both file tools share, which dev/reflux.mjs keys opfsSplit on', |
| 188 | ''); |
| 189 | |
| 190 | // ── 3. And the route is one call ──────────────────────────────────────────── |
| 191 | const made = await call('dir_create', { path: 'wpm/deep' }); |
| 192 | const again = await call('file_write', { path: 'wpm/deep/thing.rs', content: 'fn main(){}' }); |
| 193 | const now = await held('wpm', 'wpm/deep/thing.rs'); |
| 194 | check(made.outcome === 'done' && again.outcome === 'done' && now.file === true, |
| 195 | 'THE ROUTE IS ONE CALL — dir_create, then the same write lands', |
| 196 | `${made.outcome} / ${again.outcome} / file ${now.file ? 'present' : 'ABSENT'}`); |
| 197 | |
| 198 | // ── 4. A write that lands says where ──────────────────────────────────────── |
| 199 | // |
| 200 | // Nothing is invented now, so nothing is refused — and this is the case a note alone |
| 201 | // was always going to have to cover, because a daimon overwriting a file it has |
| 202 | // already read gets no refusal and still has to know which disk it is on. |
| 203 | const landed = await call('file_write', { path: 'wpm/deep/second.rs', content: 'fn two(){}' }); |
| 204 | check(landed.outcome === 'done' |
| 205 | && /this browser's own storage/.test(landed.text) |
| 206 | && /the file tools reach while no folder is open/.test(landed.text), |
| 207 | 'A WRITE THAT LANDS NAMES THE FILESYSTEM IT LANDED IN', |
| 208 | JSON.stringify(landed.text.slice(0, 200))); |
| 209 | |
| 210 | // ── 5. And its first line is unchanged ────────────────────────────────────── |
| 211 | check(/^Wrote \d+ bytes to wpm\/deep\/second\.rs\.$/.test(firstLine(landed.text).trim()), |
| 212 | 'THE FIRST LINE IS UNCHANGED, so dev/verify_replylen.mjs still reads the byte count', |
| 213 | JSON.stringify(firstLine(landed.text))); |
| 214 | |
| 215 | // ── 6. A Diamond's own storage is not second-guessed ──────────────────────── |
| 216 | // |
| 217 | // `mail/` is browser storage by design and the page's own mailbox writer creates |
| 218 | // folders under it unasked (`www/js/mail.js`). A rule that refused a store path would |
| 219 | // have broken mail to fix a fault mail does not have. |
| 220 | const store = await call('file_write', |
| 221 | { path: 'mail/someone@example.com/INBOX/index.md', content: '| UID |\n' }); |
| 222 | check(store.outcome === 'done' && !/Refused/.test(store.text), |
| 223 | 'A STORE PATH IS BROWSER STORAGE BY DESIGN — mail/ writes through, folders and all', |
| 224 | `${store.outcome}: ${JSON.stringify(store.text.slice(0, 90))}`); |
| 225 | const dia = await call('file_write', |
| 226 | { path: 'diamonds/wp-test/notes/n.md', content: 'note\n' }); |
| 227 | check(dia.outcome === 'done' && !/Refused/.test(dia.text), |
| 228 | 'and so is a Diamond\'s own directory', `${dia.outcome}`); |
| 229 | } finally { |
| 230 | await s.close(); |
| 231 | } |
| 232 | |
| 233 | console.log(bad ? `\n${bad} FAILED` : '\nall checks passed'); |
| 234 | process.exit(bad ? 1 : 0); |