Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_crystalcap.mjs

24.2 KiB, 1 run

created by r2519314175:325, 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_crystalcap.mjs — a crystal is a summary and its page is a page, and
2// each has a ceiling that says so.
3//
4// The crystal carries the reduced state of a Diamond; the scope attached to it
5// carries the weight. Nothing enforced that, so a daimon that started recording
6// rather than reducing simply kept going — and the bill arrived somewhere else,
7// because every fold copies the whole crystal into `versions/` and all of it
8// rides in the sync parcel.
9//
10// WHAT CHANGED ON 2026-08-09, and why this file was wrong until now: the crystal
11// became TWO files. `crystal.json` is the memory and `crystal.html` is the page
12// that renders it, and they have SEPARATE ceilings — `CRYSTAL_CAP_DEFAULT` and
13// `CRYSTAL_PAGE_CAP_DEFAULT` in `src/tools.rs`, read from there at the top of this
14// file rather than restated — because a page is bigger than a summary and NEITHER
15// IS EXEMPT. This file pinned one ceiling on one file, so a page could be any size
16// at all and nothing here would have noticed: the page rides in every `versions/` snapshot where it changed and
17// shares SYNC_DIAMONDS_MAX (4 MB) with the memory, so exempting presentation
18// voids the cap's stated purpose. Worse, the page is written by a MODEL, which
19// is the one author in this app with no sense of how big a file is.
20//
21// The rule is one function per file — `tools::crystal_write_refused` and
22// `tools::crystal_page_write_refused` — and each is checked at all THREE doors,
23// which is the only reason either holds:
24//
25// * `Tool::FileWrite`, which is how a DAIMON rewrites the file. The store
26// sees that write only afterwards, when `record_steer` snapshots what is on
27// disk, so a check there would be refusing a write that already happened.
28// * `Tool::FileEdit`, which is how a daimon edits it IN PLACE. This door was
29// missing until 2026-08-09, and the file said "BOTH doors" while a daimon
30// that edited rather than rewrote walked past the ceiling entirely. The
31// store's door did then fire, but too late to help: it reads the old length
32// from disk, and by then the edit had landed, so `old == new`, the refusal
33// arrived after the fact, `record_steer` errored, the turn failed, and an
34// OVERSIZED CRYSTAL WAS LEFT ON DISK WITH NO VERSION SNAPSHOT AND NO LOG
35// RECORD. Every assertion below used to go through the other two doors,
36// which is exactly why nothing went red.
37// * `diamond::snapshot`, reached by `write_crystal_data` / `write_crystal_page`,
38// which is how a HAND EDIT and a FOLD write. They never touch the file tool,
39// so the first two doors do not see them.
40//
41// And three things the ceilings must NOT do, each of which would be worse than
42// having no ceiling at all:
43//
44// * refuse a write that makes an oversized file SMALLER, which would leave
45// every Diamond that predates the rule unable to be edited down to it;
46// * apply to anything but the two live files — a `versions/NNNN.json` or
47// `versions/NNNN.html` snapshot of an oversized crystal has to keep being
48// written, or the Diamond at the ceiling cannot be recorded at all;
49// * be ONE ceiling wearing two names. They are different numbers for different
50// jobs, and a file sized between them is over one and under the other.
51//
52// How each family goes red (this lane could not run a browser, so the lead's
53// batched pass is the first time they are exercised):
54//
55// * make `is_crystal_page_path` return false → every page check goes red and
56// every data check stays green, which is the shape the old file could not see;
57// * point `crystal_page_cap()` at `crystal_cap()` → the three DEFAULTS checks
58// and the two independence checks go red;
59// * drop the `is_crystal_*_path` guard from `Tool::FileEdit` → the two
60// "refused bytes never reached the file" checks go red and nothing else does;
61// * drop the `&& new_len >= old_len` half of either rule → the four shrink
62// checks go red.
63//
64// node dev/verify_crystalcap.mjs
65//
66// Needs dev/serve.mjs (DAIMOND_PORT, default 8777). No gateway, no mock LLM: nothing
67// here runs a turn.
68import fs from 'node:fs';
69import path from 'node:path';
70import { fileURLToPath } from 'node:url';
71import { open, scratch } from './harness.mjs';
72
73// The two shipped ceilings, READ FROM THE ENGINE rather than restated here.
74//
75// They were `16 * 1024` and `64 * 1024` written out as literals in this file, and on
76// 2026-08-13 the page ceiling was raised to 128 KiB — so the "80 KiB is over it" check
77// went red against a build that was working exactly as intended, and the number this
78// file believed in was one nothing enforced. A restated constant can only ever be
79// right until somebody changes the real one. `dev/verify_lifelog.mjs` reads the same
80// constant the same way.
81const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
82const capOf = (name) => {
83 const src = fs.readFileSync(path.join(ROOT, 'src', 'tools.rs'), 'utf8');
84 const m = new RegExp(name + ':\\s*usize\\s*=\\s*(\\d+)\\s*\\*\\s*(\\d+)').exec(src);
85 if (!m) throw new Error('verify_crystalcap: ' + name + ' not found in src/tools.rs');
86 return Number(m[1]) * Number(m[2]);
87};
88const DATA_CAP = capOf('CRYSTAL_CAP_DEFAULT');
89const PAGE_CAP = capOf('CRYSTAL_PAGE_CAP_DEFAULT');
90
91// The whole point of the defaults block below is that these are two DIFFERENT numbers
92// with room between them. If they ever meet, there is no size that is over one and under
93// the other, and the block would pass by having nothing left to ask.
94if (!(DATA_CAP < PAGE_CAP)) {
95 console.error('verify_crystalcap: the page ceiling (' + PAGE_CAP + ') is not above the '
96 + 'memory ceiling (' + DATA_CAP + '), so no file can be over one and under the other. '
97 + 'These are meant to be different numbers for different jobs; see src/tools.rs.');
98 process.exit(2);
99}
100// AND THE FIGURE THE SETTINGS PANE SHOWS, read the same way and for the same reason.
101//
102// `DEFAULT_CRYSTAL_KB` and `DEFAULT_CRYSTAL_PAGE_KB` in `daimond.js` are a hand-kept copy of the
103// two constants above, used to label the "Default" row of each pulldown. Nothing set them from the
104// engine and nothing checked them, so when the page ceiling was raised to 128 KiB on 2026-08-13 the
105// label went on saying 64 KB and kept saying it for a fortnight. That is worse than having no label:
106// the one place in the product that names the ceiling named half of it, and the author of a capp
107// that met the real ceiling had to establish it by experiment. This runs before the browser does,
108// because a number that disagrees with the engine is wrong whatever the app then does with it.
109const labelOf = (name) => {
110 const src = fs.readFileSync(path.join(ROOT, 'www', 'js', 'daimond.js'), 'utf8');
111 const m = new RegExp('var\\s+' + name + '\\s*=\\s*(\\d+)\\s*;').exec(src);
112 if (!m) throw new Error('verify_crystalcap: ' + name + ' not found in www/js/daimond.js');
113 return Number(m[1]) * 1024;
114};
115for (const [label, cap, engine] of [
116 ['DEFAULT_CRYSTAL_KB', labelOf('DEFAULT_CRYSTAL_KB'), DATA_CAP],
117 ['DEFAULT_CRYSTAL_PAGE_KB', labelOf('DEFAULT_CRYSTAL_PAGE_KB'), PAGE_CAP],
118]) {
119 if (cap !== engine) {
120 console.error('verify_crystalcap: ' + label + ' in www/js/daimond.js says ' + cap
121 + ' bytes, and the engine enforces ' + engine + '. The settings pane would tell the '
122 + 'user a ceiling that is not the one refusing their writes; see src/tools.rs.');
123 process.exit(2);
124 }
125}
126
127// AND THE FIGURE THE GUIDE SHOWS, which is the copy a non-technical reader meets.
128//
129// `www/guide/capps.html` gives the page ceiling a card of its own, headed with the number, and
130// `dev/guide-i18n/_source.json` carries that heading as a translatable run. The block above was
131// written after the settings label had spent a fortnight naming half the real ceiling; the guide
132// was a THIRD copy of the same number, it was left at 128 KB when the engine went to 512 KiB, and
133// nothing here read it. A reader who trusts the guide over the refusal has no way to find out.
134// Both are checked, because a corrected page with a stale bank entry ships the old figure to
135// seven other languages.
136const guideCap = (label, file, rel, text) => {
137 const m = /(\d+)\s*(K|M)i?B/i.exec(text);
138 if (!m) {
139 console.error('verify_crystalcap: ' + label + ' names no size in ' + rel + '. The guide\'s '
140 + 'page-ceiling card is how a reader learns the number; see www/guide/capps.html.');
141 process.exit(2);
142 }
143 return Number(m[1]) * (m[2].toUpperCase() === 'M' ? 1024 * 1024 : 1024);
144};
145{
146 // The card is found by the control it names rather than by its heading id, so renumbering the
147 // page's anchors does not silently take the check with it.
148 const rel = path.join('www', 'guide', 'capps.html');
149 const html = fs.readFileSync(path.join(ROOT, rel), 'utf8');
150 const card = /<div class="card">\s*<h3[^>]*>([^<]*)<\/h3>\s*<p>(?:(?!<\/div>)[\s\S])*?Page size limit[\s\S]*?<\/div>/i.exec(html);
151 if (!card) {
152 console.error('verify_crystalcap: no card in ' + rel + ' names "Page size limit", so the '
153 + 'guide either stopped naming the page ceiling or renamed the setting it points at.');
154 process.exit(2);
155 }
156 const heading = card[1];
157 const shown = guideCap('the guide\'s page-ceiling card', rel, rel, heading);
158 if (shown !== PAGE_CAP) {
159 console.error('verify_crystalcap: ' + rel + ' heads its page-ceiling card "' + heading.trim()
160 + '" (' + shown + ' bytes), and the engine enforces ' + PAGE_CAP + '. This is the one '
161 + 'place a non-technical reader is told the ceiling; see src/tools.rs.');
162 process.exit(2);
163 }
164 // And the translatable run behind it, which is what the seven locale pages are built from.
165 const bankRel = path.join('dev', 'guide-i18n', '_source.json');
166 const bank = JSON.parse(fs.readFileSync(path.join(ROOT, bankRel), 'utf8'));
167 const runs = (bank['capps.html'] || []).filter((s) => /^\s*\d+\s*(K|M)i?B\s*$/i.test(s));
168 if (runs.length !== 1 || guideCap('the bank\'s copy', bankRel, bankRel, runs[0]) !== PAGE_CAP) {
169 console.error('verify_crystalcap: ' + bankRel + ' holds ' + JSON.stringify(runs) + ' where '
170 + 'the guide\'s page-ceiling heading should be, and the engine enforces ' + PAGE_CAP
171 + '. Run `node dev/guide_i18n.mjs extract` after editing the English page, or the '
172 + 'translations keep shipping the old figure.');
173 process.exit(2);
174 }
175}
176
177const KIB = (n) => (n / 1024) + ' KiB';
178const MID_LEN = DATA_CAP + Math.floor((PAGE_CAP - DATA_CAP) / 2); // over memory, under page
179const BIG_LEN = PAGE_CAP + 16 * 1024; // over page as well
180
181const PROFILE = scratch('pw', 'crystalcap');
182fs.rmSync(PROFILE, { recursive: true, force: true });
183
184let bad = 0;
185const check = (pass, name, detail) => {
186 if (!pass) bad++;
187 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
188};
189
190/// One recorded outcome, or a stand-in that FAILS and says why it is missing.
191///
192/// A result the run never reached must not read as a pass, and it must not
193/// throw here either: a build with no page ceiling should produce a column of
194/// named reds pointing at the missing function, not one stack trace.
195const R = (f, k) => (f && f[k]) || { ok: false, msg: (f && f.missing) || 'no result recorded' };
196
197const s = await open({ name: 'crystalcap', profile: PROFILE, connect: false });
198const { page } = s;
199
200try {
201 await page.waitForTimeout(1500);
202
203 const out = await page.evaluate(async ({ MID_LEN, BIG_LEN }) => {
204 const mod = await import('../pkg/oxedyne_daimond.js');
205 const app = new mod.DaimondApp('http://127.0.0.1/v1/chat/completions', '', 'none', 256, '', true);
206 const r = { absent: [] };
207
208 // Named up front, so a red is attributable to the engine rather than to the
209 // checks below discovering the same absence twenty times over.
210 for (const fn of ['set_crystal_cap', 'set_crystal_page_cap',
211 'write_crystal_data', 'write_crystal_page']) {
212 if (typeof app[fn] !== 'function') r.absent.push(fn);
213 }
214
215 const strip = (m) => String(m).replace(/\[[0-9;]*m/g, '');
216
217 // `run_tool` RESOLVES with the error text rather than rejecting, so a
218 // refusal and a write look identical to a `try`/`catch`. Success is the
219 // "Wrote N bytes" shape and nothing else -- reading it the other way round
220 // made this file report two passes it had not earned.
221 const write = async (path, content) => {
222 let msg;
223 try { msg = String(await app.run_tool('file_write', JSON.stringify({ path, content }))); }
224 catch (e) { msg = String(e && e.message ? e.message : e); }
225 return { ok: /^Wrote \d+ bytes/.test(msg), msg: strip(msg) };
226 };
227 const edit = async (path, oldS, newS) => {
228 let msg;
229 try {
230 msg = String(await app.run_tool('file_edit',
231 JSON.stringify({ path, old_string: oldS, new_string: newS })));
232 } catch (e) { msg = String(e && e.message ? e.message : e); }
233 return { ok: /^Edited /.test(msg), msg: strip(msg) };
234 };
235 // The bytes as they are on disk, through the store's own reader.
236 //
237 // NOT `run_tool('file_read')`: that is a MODEL-FACING rendering which
238 // numbers every line and wraps an untrusted path in an envelope, and two
239 // verifiers in this tree have already compared it to plain bytes and been
240 // red for weeks over nothing. `store_read` pins the OPFS root, which is
241 // where a Diamond lives whatever folder the user has open.
242 const onDisk = async (path) => {
243 try { return await mod.store_read(path); } catch (e) { return 'ERR ' + e; }
244 };
245
246 // ── The two ceilings are different numbers ───────────────────
247 // Measured BEFORE any setter runs, so what is under test is the shipped
248 // defaults. `MID_LEN` sits between the two, so it is over the memory's
249 // ceiling and under the page's: a single ceiling serving both files fails
250 // here whichever of the two numbers it happens to hold. `BIG_LEN` is over
251 // the page's as well, which is what says the page has a ceiling at all.
252 // Both are derived from the engine's own constants, so raising either
253 // ceiling moves the fixture with it instead of turning this red.
254 {
255 const id = await app.create_diamond('Ceilings');
256 const dir = 'diamonds/' + id;
257 // Legal JSON and legal HTML at exactly the sizes named, so a refusal
258 // can only ever be the weight and never the shape.
259 const dataMid = '{"t":"' + 'm'.repeat(MID_LEN - 8) + '"}';
260 const pageMid = '<!--' + 'm'.repeat(MID_LEN - 7) + '-->';
261 const pageBig = '<!--' + 'p'.repeat(BIG_LEN - 7) + '-->';
262 r.defDataMid = await write(dir + '/crystal.json', dataMid);
263 r.defPageMid = await write(dir + '/crystal.html', pageMid);
264 r.defPageBig = await write(dir + '/crystal.html', pageBig);
265 }
266
267 // ── Each file, at each of its three doors ────────────────────
268 const CAP = 1000; // small, so the test is about the rule and not about writing 16 KB.
269 const specs = [
270 {
271 key: 'data',
272 file: 'crystal.json',
273 snap: 'versions/0007.json',
274 other: 'notes.json',
275 // Legal JSON at every size, so a refusal can only be the ceiling.
276 // A store door that parses what it is handed would otherwise refuse
277 // a wall of `x` for a reason that has nothing to do with weight, and
278 // the check would pass having proved nothing.
279 body: (s) => '{"t":"' + s + '"}',
280 capFn: 'set_crystal_cap',
281 hand: (id, text) => app.write_crystal_data(id, text),
282 },
283 {
284 key: 'page',
285 file: 'crystal.html',
286 snap: 'versions/0007.html',
287 other: 'notes.html',
288 body: (s) => '<!--' + s + '-->',
289 capFn: 'set_crystal_page_cap',
290 hand: (id, text) => app.write_crystal_page(id, text),
291 },
292 ];
293
294 for (const spec of specs) {
295 const f = {};
296 r[spec.key] = f;
297 if (typeof app[spec.capFn] !== 'function') {
298 f.missing = 'this build of the engine has no ' + spec.capFn;
299 continue;
300 }
301 const setCap = (n) => app[spec.capFn](n);
302 const over = spec.body('').length; // what the wrapper itself costs
303 const fill = (n, ch) => spec.body((ch || 'x').repeat(Math.max(0, n - over)));
304
305 setCap(CAP);
306 const id = await app.create_diamond('Capped ' + spec.key);
307 const dir = 'diamonds/' + id;
308 const path = dir + '/' + spec.file;
309 const big = fill(CAP + 500, 'x');
310 const small = fill(200, 'y');
311
312 // ── The daimon's door ────────────────────────────────
313 f.underCap = await write(path, small);
314 f.overCap = await write(path, big);
315 // And the file must still hold the small one: a refusal that wrote anyway
316 // is not a refusal.
317 f.afterRefusal = { ok: (await onDisk(path)) === small, msg: 'on disk after the refusal' };
318
319 // ── Not one of the two live files, not capped ────────
320 f.versionBig = await write(dir + '/' + spec.snap, big);
321 f.ordinaryBig = await write(dir + '/' + spec.other, big);
322 // Exactly three path segments is what keeps `is_crystal_*_path` a small
323 // change rather than a redesign, and a four-segment `crystal/data.json`
324 // layout was declined for precisely this reason. A ceiling written as a
325 // filename match would silently start capping a user's own file.
326 f.nestedBig = await write(dir + '/nested/' + spec.file, big);
327
328 // ── The store's door ─────────────────────────────────
329 // `write_crystal_*` DOES reject, so here a throw is the refusal.
330 const hand = async (text) => {
331 try { await spec.hand(id, text); return { ok: true, msg: 'accepted' }; }
332 catch (e) { return { ok: false, msg: strip(String(e && e.message ? e.message : e)) }; }
333 };
334 f.storeOver = await hand(big);
335 f.storeUnder = await hand(small);
336
337 // ── The daimon's OTHER door: file_edit ───────────────
338 // `file_edit` writes the file just as `file_write` does, so it needs the
339 // same ceiling. Anchored on a unique token, because the tool refuses an
340 // `old_string` that appears more than once and a run of identical letters
341 // matches itself many times over.
342 const seed = spec.body('HEAD' + 'y'.repeat(200));
343 f.editSeed = await write(path, seed);
344 f.editUnder = await edit(path, 'HEAD', 'HEADER');
345 f.editOver = await edit(path, 'HEADER', 'z'.repeat(CAP + 500));
346 // The specific harm this door caused: not that the write was allowed, but
347 // that the turn then died at the store's door leaving the oversized bytes
348 // on disk, unsnapshotted and unlogged. So the file itself is the assertion.
349 f.afterEditRefusal = {
350 ok: (await onDisk(path)) === seed.replace('HEAD', 'HEADER'),
351 msg: 'on disk after the refused edit',
352 };
353
354 // ── An already-oversized file can still be edited DOWN ─
355 // Seeded past the ceiling with the ceiling RAISED -- not with zero, which
356 // means the default rather than "no ceiling", and which quietly refused
357 // the seed the first time this was written.
358 setCap(256 * 1024);
359 f.seeded = await write(path, fill(20 * 1024, 'z'));
360 setCap(CAP);
361 // The asymmetry has to hold at the edit door too, or a Diamond that
362 // predates the rule could be rewritten down to size but never edited down.
363 // A hair over half the run, so it matches once rather than twice.
364 f.editShrink = await edit(path, 'z'.repeat(10 * 1024 + 1), '');
365 // 20 KB less 10241 leaves 10239 -- still over, so the writes below are
366 // still shrinking and the chain that follows is unchanged.
367 f.shrinkToward = await write(path, fill(5 * 1024, 'z')); // still over, but smaller
368 f.shrinkUnder = await write(path, small); // and all the way down
369 f.growAgain = await write(path, fill(6 * 1024, 'z')); // over again: refused
370 }
371
372 // ── Two ceilings, two settings, no shared static ─────────────
373 // The likeliest way to build this wrong is a copy-pasted setter that moves
374 // the other file's number. Nothing in the run above would show it: each
375 // half only ever writes its own file.
376 if (r.absent.length === 0) {
377 const id = await app.create_diamond('Two settings');
378 const dir = 'diamonds/' + id;
379 const five = 5 * 1024;
380 app.set_crystal_cap(64 * 1024);
381 app.set_crystal_page_cap(1000);
382 r.pageSetterLeftData = await write(dir + '/crystal.json',
383 '{"t":"' + 'd'.repeat(five) + '"}');
384 app.set_crystal_cap(1000);
385 app.set_crystal_page_cap(64 * 1024);
386 r.dataSetterLeftPage = await write(dir + '/crystal.html',
387 '<!--' + 'h'.repeat(five) + '-->');
388 }
389
390 return r;
391 }, { MID_LEN, BIG_LEN });
392
393 if (out.absent.length) {
394 check(false, 'the engine offers both ceilings and both store doors',
395 'missing: ' + out.absent.join(', '));
396 } else {
397 check(true, 'the engine offers both ceilings and both store doors');
398 }
399
400 // ── The shipped defaults are two different numbers ───────────
401 check(!out.defDataMid.ok,
402 KIB(MID_LEN) + ' of MEMORY is over the default ceiling (' + KIB(DATA_CAP) + ') and is refused',
403 out.defDataMid.msg);
404 check(out.defPageMid.ok,
405 'the same ' + KIB(MID_LEN) + ' as a PAGE is under its own, larger ceiling ('
406 + KIB(PAGE_CAP) + ') and is written',
407 out.defPageMid.msg);
408 check(!out.defPageBig.ok,
409 'but ' + KIB(BIG_LEN) + ' of page is over that one and is refused too',
410 out.defPageBig.msg);
411
412 // The wording of a refusal, per file. The user has TWO pulldowns in settings,
413 // so a message that does not say which file it is about leaves them guessing
414 // which one to move -- and telling somebody to put their HTML "in the
415 // Diamond's scope" is advice for the memory, aimed at the wrong file.
416 const says = {
417 data: (m) => /scope/i.test(m || ''),
418 page: (m) => /page/i.test(m || ''),
419 };
420 const said = { data: 'names the scope as the place for the detail', page: 'names the page' };
421
422 for (const key of ['data', 'page']) {
423 const f = out[key];
424 const w = key === 'data' ? 'the memory' : 'the page';
425 check(R(f, 'underCap').ok, w + ' under its ceiling is written', R(f, 'underCap').msg);
426 check(!R(f, 'overCap').ok, w + ' over it is refused at the daimon\'s door',
427 R(f, 'overCap').msg);
428 check(says[key](R(f, 'overCap').msg), 'and the refusal ' + said[key],
429 R(f, 'overCap').msg);
430 check(R(f, 'afterRefusal').ok, 'and the refused bytes did not reach the file');
431
432 check(R(f, 'versionBig').ok, 'a version snapshot of ' + w + ' is not measured against it',
433 R(f, 'versionBig').msg);
434 check(R(f, 'ordinaryBig').ok, 'nor is an ordinary file of the same kind beside it',
435 R(f, 'ordinaryBig').msg);
436 check(R(f, 'nestedBig').ok, 'nor is one a folder deeper, which is not the crystal at all',
437 R(f, 'nestedBig').msg);
438
439 check(!R(f, 'storeOver').ok, 'a hand edit of ' + w + ' over the ceiling is refused at the store\'s door',
440 R(f, 'storeOver').msg);
441 check(says[key](R(f, 'storeOver').msg), 'and that refusal ' + said[key],
442 R(f, 'storeOver').msg);
443 check(R(f, 'storeUnder').ok, 'a hand edit under it is written', R(f, 'storeUnder').msg);
444
445 check(R(f, 'editSeed').ok, w + ' small enough to edit is in place', R(f, 'editSeed').msg);
446 check(R(f, 'editUnder').ok, 'an edit that keeps it under the ceiling is written',
447 R(f, 'editUnder').msg);
448 check(!R(f, 'editOver').ok, 'an edit that would push it over is refused at the edit door',
449 R(f, 'editOver').msg);
450 check(says[key](R(f, 'editOver').msg), 'and that refusal ' + said[key],
451 R(f, 'editOver').msg);
452 // The one that matters most: the old failure was not a permitted write, it was
453 // a write that landed and then killed the turn, leaving bytes nothing recorded.
454 check(R(f, 'afterEditRefusal').ok,
455 'and the refused bytes never reached the file, so nothing oversized is left unsnapshotted');
456 check(R(f, 'editShrink').ok, 'an edit may still make an oversized ' + w + ' SMALLER',
457 R(f, 'editShrink').msg);
458
459 check(R(f, 'seeded').ok, w + ' can be seeded past the ceiling with the ceiling lifted',
460 R(f, 'seeded').msg);
461 check(R(f, 'shrinkToward').ok, 'and edited SMALLER while still over', R(f, 'shrinkToward').msg);
462 check(R(f, 'shrinkUnder').ok, 'and all the way under', R(f, 'shrinkUnder').msg);
463 check(!R(f, 'growAgain').ok, 'but not grown again once it is under', R(f, 'growAgain').msg);
464 }
465
466 // ── The two settings are two settings ────────────────────────
467 check(R(out, 'pageSetterLeftData').ok,
468 'lowering the PAGE ceiling leaves the memory\'s where it was',
469 R(out, 'pageSetterLeftData').msg);
470 check(R(out, 'dataSetterLeftPage').ok,
471 'and lowering the memory\'s leaves the PAGE\'s where it was',
472 R(out, 'dataSetterLeftPage').msg);
473
474} finally {
475 await s.close();
476}
477
478console.log(bad === 0 ? '\nall checks passed' : `\n${bad} check(s) FAILED`);
479process.exit(bad === 0 ? 0 : 1);