1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703 | // The suite identity record as home writes it, and the `apps` section of the
// multi-device self-sync.
//
// home IS the apex origin: it writes the record straight into localStorage
// through writeBridgeRecord, which does NOT sanitize. Every other writer in the
// suite goes through the bridge host and is rebuilt field by field on the way
// in; the hub's builders are the one place a malformed record can be minted, so
// they are pinned here — KEY ORDER INCLUDED. Insertion order is the storage
// shape (the record is JSON.stringify'd), and #js{}/js-obj switches to hash
// order from nine pairs up with no warning at any optimization level, which is
// exactly what a ten-field record walks into. A key-order-blind deepEqual would
// prove nothing, so every shape assertion below is on Object.keys or on the
// serialized string.
import test from "node:test";
import assert from "node:assert/strict";
import { boot } from "./harness.mjs";
import {
BRIDGE_STORE_KEY,
MAX_APPS_BYTES,
MAX_APP_STATE_BYTES,
MAX_RECORD_BYTES,
RESERVED_APP_KEYS,
RESERVED_CORE_BYTES,
appKeyOfOrigin,
readBridgeRecord,
sanitizeRecord,
writeBridgeRecord,
} from "ardegazu-id-kit";
const { env, lib } = await boot();
/** Run `f` with console.warn captured; answers the warnings it emitted. */
function warnings(f) {
const got = [];
const real = console.warn;
console.warn = (...a) => got.push(a.join(" "));
try {
f();
} finally {
console.warn = real;
}
return got;
}
// the record's fields, in wire order — spelled out here rather than imported,
// so a change to the source list has to be made twice, on purpose
const FIELDS = ["v", "seed", "name", "hue", "glyph", "lang", "soc", "apps", "createdAt", "updatedAt"];
const SEED = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; // 43 chars, SEED_RE
const OTHER_SEED = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const entry = (state, ts) => ({ state, ts });
/** Put a record on the apex origin, exactly as the hub would. */
function seed(rec) {
env.localStorage.clear();
writeBridgeRecord(rec);
}
function storedApps() {
return readBridgeRecord().rec.apps;
}
// ---- the ten-field record ---------------------------------------------------
test("newRecord builds all TEN fields, in wire order", () => {
const rec = lib.newRecord(SEED);
assert.deepEqual(Object.keys(rec), FIELDS);
// insertion order is the storage shape: pin the serialization too
assert.match(
JSON.stringify(rec),
/^\{"v":1,"seed":"a{43}","name":"","hue":null,"glyph":null,"lang":null,"soc":null,"apps":null,"createdAt":\d+,"updatedAt":\d+\}$/,
);
});
test("newRecord emits `apps`, empty as null", () => {
const rec = lib.newRecord(SEED);
assert.ok("apps" in rec, "a blank record must carry the apps field");
assert.equal(rec.apps, null);
// and in ITS position: between soc and createdAt, never appended at the end
assert.equal(Object.keys(rec).indexOf("apps"), 7);
});
test("the exported field list is the order the builder emits", () => {
assert.deepEqual([...lib.recordFields()], FIELDS);
assert.deepEqual(Object.keys(lib.newRecord(SEED)), [...lib.recordFields()]);
});
test("a blank record survives the apex round trip with its ten keys in order", () => {
seed(lib.newRecord(SEED));
const raw = env.localStorage.getItem(BRIDGE_STORE_KEY);
assert.deepEqual(Object.keys(JSON.parse(raw)), FIELDS);
// id-kit re-sanitizes on read and must not reorder or drop anything
assert.deepEqual(Object.keys(readBridgeRecord().rec), FIELDS);
});
test("writeRecord keeps the order and never loses a section", () => {
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ rooms: 2 }, 1000) };
seed(rec);
const out = lib.writeRecord(lib.curRecord(), [["soc", { f: [] }]]);
assert.deepEqual(Object.keys(out), FIELDS);
assert.deepEqual(storedApps(), { chat: { state: { rooms: 2 }, ts: 1000 } });
});
// ---- the appsSync hooks -----------------------------------------------------
test("appsSync.get hands over the record's own map, null when there is none", () => {
const hooks = lib.appsSyncHooks(SEED, () => {});
env.localStorage.clear();
assert.equal(hooks.get(), null, "no record at all");
seed(lib.newRecord(SEED));
assert.equal(hooks.get(), null, "a record with no sections reports none, not {}");
const rec = lib.newRecord(SEED);
rec.apps = { board: entry({ n: 1 }, 5000) };
seed(rec);
assert.deepEqual(hooks.get(), { board: { state: { n: 1 }, ts: 5000 } });
});
test("appsSync.apply folds the remote map in and stores it, order intact", () => {
let woke = 0;
const hooks = lib.appsSyncHooks(SEED, () => woke++);
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ rooms: 1 }, 1000) };
seed(rec);
hooks.apply({ board: entry({ n: 7 }, 2000) });
assert.deepEqual(storedApps(), {
board: { state: { n: 7 }, ts: 2000 },
chat: { state: { rooms: 1 }, ts: 1000 },
});
// a key only one side has is adopted, and mine survives: the merged superset
// has to go back out, or the sender never learns about `chat`
assert.equal(woke, 1);
assert.deepEqual(Object.keys(readBridgeRecord().rec), FIELDS);
});
test("apply must not clobber a NEWER local entry", () => {
let woke = 0;
const hooks = lib.appsSyncHooks(SEED, () => woke++);
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ v: "mine, newer" }, 9000) };
seed(rec);
hooks.apply({ chat: entry({ v: "theirs, older" }, 1000) });
assert.deepEqual(storedApps(), { chat: { state: { v: "mine, newer" }, ts: 9000 } });
assert.equal(woke, 0, "nothing changed: no write, no sync");
});
test("apply takes a strictly newer remote entry", () => {
const hooks = lib.appsSyncHooks(SEED, () => {});
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ v: "mine, older" }, 1000) };
seed(rec);
hooks.apply({ chat: entry({ v: "theirs, newer" }, 9000) });
assert.deepEqual(storedApps(), { chat: { state: { v: "theirs, newer" }, ts: 9000 } });
});
test("apply is an echo guard: an identical map writes nothing at all", () => {
let woke = 0;
const hooks = lib.appsSyncHooks(SEED, () => woke++);
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ rooms: 1 }, 1000) };
seed(rec);
const before = env.localStorage.getItem(BRIDGE_STORE_KEY);
hooks.apply({ chat: entry({ rooms: 1 }, 1000) });
assert.equal(env.localStorage.getItem(BRIDGE_STORE_KEY), before, "updatedAt must not move");
assert.equal(woke, 0);
});
test("apply onto a sectionless record adopts the remote map", () => {
const hooks = lib.appsSyncHooks(SEED, () => {});
seed(lib.newRecord(SEED));
hooks.apply({ chat: entry({ rooms: 3 }, 1000) });
assert.deepEqual(storedApps(), { chat: { state: { rooms: 3 }, ts: 1000 } });
assert.deepEqual(Object.keys(readBridgeRecord().rec), FIELDS);
});
test("apply refuses to write once the identity has swapped under it", () => {
let woke = 0;
const hooks = lib.appsSyncHooks(SEED, () => woke++);
// the fold belongs to the record that asked for it, not to whoever is here
// now: an import between the deposit and its arrival must not carry the old
// person's sections onto the new one
seed(lib.newRecord(OTHER_SEED));
hooks.apply({ chat: entry({ rooms: 3 }, 1000) });
assert.equal(storedApps(), null);
assert.equal(woke, 0);
env.localStorage.clear();
hooks.apply({ chat: entry({ rooms: 3 }, 1000) }); // no record at all
assert.equal(env.localStorage.getItem(BRIDGE_STORE_KEY), null);
assert.equal(woke, 0);
});
test("a poisoned remote key cannot reach the stored record", () => {
const hooks = lib.appsSyncHooks(SEED, () => {});
seed(lib.newRecord(SEED));
const nasty = JSON.parse('{"__proto__":{"state":1,"ts":9},"NotAKey":{"state":1,"ts":9},"chat":{"state":1,"ts":9}}');
hooks.apply(nasty);
assert.deepEqual(Object.keys(storedApps()), ["chat"]);
assert.equal(Object.prototype.hasOwnProperty.call({}, "state"), false, "Object.prototype untouched");
});
test("the storage snapshot moves only when the sections do", () => {
seed(lib.newRecord(SEED));
const empty = lib.appsSnapshot();
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ rooms: 1 }, 1000) };
seed(rec);
assert.notEqual(lib.appsSnapshot(), empty);
// a profile-only write leaves it alone
lib.writeRecord(lib.curRecord(), [["name", "ana"]]);
assert.notEqual(lib.appsSnapshot(), empty);
assert.equal(lib.appsSnapshot(), JSON.stringify({ chat: { state: { rooms: 1 }, ts: 1000 } }));
});
// ---- the reserved keys ------------------------------------------------------
test("`home` and `dev` are reserved: no subdomain can derive either", () => {
assert.deepEqual([...RESERVED_APP_KEYS], ["home", "dev"]);
assert.equal(appKeyOfOrigin("https://ardegazu.ro"), "home", "the apex — home's own section");
assert.equal(appKeyOfOrigin("http://localhost:5173"), "dev");
assert.equal(appKeyOfOrigin("http://127.0.0.1:4173"), "dev");
assert.equal(appKeyOfOrigin("https://chat.ardegazu.ro"), "chat");
// a host spelling a reserved label gets NO section, rather than someone else's
assert.equal(appKeyOfOrigin("https://home.ardegazu.ro"), null);
assert.equal(appKeyOfOrigin("https://dev.ardegazu.ro"), null);
assert.equal(appKeyOfOrigin("https://ardegazu.ro.evil.example"), null);
assert.equal(appKeyOfOrigin("http://ardegazu.ro"), null, "http on the apex is not the apex");
});
test("the section budget is the one the hub is pinned against", () => {
assert.equal(MAX_RECORD_BYTES, 131072);
assert.equal(MAX_APP_STATE_BYTES, 16384);
assert.equal(RESERVED_CORE_BYTES, 32768);
assert.equal(MAX_APPS_BYTES, 98304);
// the reserve is what keeps a soc/profile write from ever being crowded out
assert.equal(MAX_APPS_BYTES, MAX_RECORD_BYTES - RESERVED_CORE_BYTES);
// and it is a budget the hub SPENDS, not one it merely knows about: the fold
// is the only writer in the suite that bypasses the host's check, so pin that
// it enforces the same numbers (the behaviour is exercised below)
const rec = lib.newRecord(SEED);
rec.apps = fullSections(["chat", "board", "game1", "game2", "game3"]);
seed(rec);
warnings(() => lib.appsSyncHooks(SEED, () => {}).apply(fullSections(["game4"])));
assert.ok(JSON.stringify(storedApps()).length <= MAX_APPS_BYTES);
});
// ---- the section budget the fold has to spend --------------------------------
//
// Two devices can each be legally full and still not fit merged: id-kit caps a
// single section at MAX_APP_STATE_BYTES and the whole map at MAX_APPS_BYTES, so
// the union of two legal maps runs to twice the cap. Every OTHER writer in the
// suite is stopped by the bridge host, which refuses a `t:"app"` write with
// conflict "full" when the POST-write map would pass the cap. The hub's fold
// bypasses the host, so one oversized merge used to be permanent: from then on
// every app's section write on that device was refused — a shrinking one
// included, because the host checks the post-write total — and the outbound
// get() was re-capped by sanitizeAppsSync, so the sections past the cap were
// silently dropped on the wire every round. Only "forget identity" cleared it.
/** `n` sections at the per-entry cap: five of them is ~5/6 of the whole budget. */
function fullSections(keys) {
const big = "x".repeat(MAX_APP_STATE_BYTES - 2); // + the quotes = exactly the cap
return Object.fromEntries(keys.map((k, i) => [k, entry(big, 1000 + i)]));
}
test("a fold that would blow the section budget is trimmed, not stored", () => {
const mine = ["chat", "board", "game1", "game2", "game3"];
const rec = lib.newRecord(SEED);
rec.apps = fullSections(mine);
seed(rec);
assert.ok(
JSON.stringify(storedApps()).length <= MAX_APPS_BYTES,
"this device alone is legally full",
);
const theirs = fullSections(["game4", "game5", "sueta", "balaur", "home"]);
assert.ok(JSON.stringify(theirs).length <= MAX_APPS_BYTES, "so is the other device");
const warned = warnings(() => lib.appsSyncHooks(SEED, () => {}).apply(theirs));
assert.ok(
JSON.stringify(storedApps()).length <= MAX_APPS_BYTES,
"the merged map must stay inside the sections' budget",
);
assert.ok(
env.localStorage.getItem(BRIDGE_STORE_KEY).length <= MAX_RECORD_BYTES,
"and the record inside its own",
);
// the bricking is what actually matters: any app must still be able to write
// its section afterwards, which is the host's post-write total
const next = { ...storedApps(), chat: entry({ tiny: 1 }, Date.now()) };
const afterHost = sanitizeRecord({ ...readBridgeRecord().rec, apps: next }).apps;
assert.ok(
JSON.stringify(afterHost).length <= MAX_APPS_BYTES,
'the next t:"app" write must not be refused with conflict "full"',
);
// and it is not silent
assert.equal(warned.length, 1);
assert.match(warned[0], /exceed the record budget/);
});
test("the trim gives back the REMOTE sections, never this device's own", () => {
const mine = ["chat", "board", "game1", "game2", "game3"];
const rec = lib.newRecord(SEED);
rec.apps = fullSections(mine);
seed(rec);
const before = JSON.stringify(storedApps());
warnings(() => lib.appsSyncHooks(SEED, () => {}).apply(fullSections(["game4", "game5"])));
// nothing of ours moved: same keys, same entries, same bytes
assert.deepEqual(Object.keys(storedApps()).sort(), [...mine].sort());
assert.equal(JSON.stringify(storedApps()), before);
});
test("the trim gives back only what does not fit, biggest first", () => {
const rec = lib.newRecord(SEED);
rec.apps = fullSections(["chat", "board", "game1", "game2", "game3"]);
seed(rec);
// one section that fits in what is left, one that cannot possibly
const remote = { ...fullSections(["game4"]), sueta: entry({ n: 1 }, 3000) };
const warned = warnings(() => lib.appsSyncHooks(SEED, () => {}).apply(remote));
assert.deepEqual(storedApps().sueta, { state: { n: 1 }, ts: 3000 }, "the small one is adopted");
assert.equal(storedApps().game4, undefined, "the one that does not fit is refused");
assert.match(warned[0], /game4/);
assert.ok(JSON.stringify(storedApps()).length <= MAX_APPS_BYTES);
});
test("a remote entry that wins on ts but does not fit reverts to MY older one", () => {
const rec = lib.newRecord(SEED);
rec.apps = fullSections(["chat", "board", "game1", "game2", "game4"]);
rec.apps.game3 = entry({ v: "mine, older but small" }, 1000);
seed(rec);
// theirs is newer AND at the per-entry cap: adopting it would blow the budget
const remote = { game3: entry("y".repeat(MAX_APP_STATE_BYTES - 2), 9000) };
warnings(() => lib.appsSyncHooks(SEED, () => {}).apply(remote));
assert.deepEqual(
storedApps().game3,
{ state: { v: "mine, older but small" }, ts: 1000 },
"keeping my own older section beats holding none",
);
assert.ok(JSON.stringify(storedApps()).length <= MAX_APPS_BYTES);
});
// ---- get is bound to the identity the agent booted on -----------------------
test("appsSync.get refuses once the identity has swapped under it", () => {
const hooks = lib.appsSyncHooks(SEED, () => {}); // the agent booted on SEED
// another home tab imports a different identity; the record swaps underneath.
// this agent's channel, sealing key and mailbox room are all still SEED's, so
// answering the new identity's sections here deposits them into the OLD
// identity's self-inbox, where its other devices fold them in.
const rec = lib.newRecord(OTHER_SEED);
rec.apps = { chat: entry({ secret: "the other identity's app state" }, 1000) };
seed(rec);
assert.equal(hooks.get(), null, "not this agent's record to read");
// and the sections are still there, untouched — get must not write either
assert.deepEqual(storedApps(), { chat: { state: { secret: "the other identity's app state" }, ts: 1000 } });
// the guard is the seed, not "any record": the right one still reads
const ours = lib.newRecord(SEED);
ours.apps = { chat: entry({ n: 1 }, 1000) };
seed(ours);
assert.deepEqual(hooks.get(), { chat: { state: { n: 1 }, ts: 1000 } });
});
// ---- the write path is a compare-and-set ------------------------------------
//
// Every other writer in the suite reaches the record through the bridge host,
// where a write is a CAS on the seed. home bypasses the host — it IS the apex —
// and used to read, mutate and blind-write. An app's /id/ iframe is a different
// browsing context on the same origin with its own synchronous handler, so it
// can land a `t:"app"` write between the hub's read and its store.
test("a write does not clobber a section that landed in the window", () => {
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ v: 1 }, 1000) };
seed(rec);
const stale = lib.curRecord(); // the hub reads…
// …and an app's bridge iframe writes its own section right here
const mid = readBridgeRecord().rec;
mid.apps = { ...mid.apps, board: entry({ v: 2 }, 2000) };
writeBridgeRecord(mid);
// …then the hub stores its soc mirror from the stale read
const out = lib.writeRecord(stale, [["soc", { f: ["x"] }]]);
assert.deepEqual(storedApps(), {
chat: { state: { v: 1 }, ts: 1000 },
board: { state: { v: 2 }, ts: 2000 },
}, "the intervening section survives");
assert.deepEqual(readBridgeRecord().rec.soc, { f: ["x"] }, "and so does the write that raced it");
assert.deepEqual(Object.keys(out), FIELDS);
});
test("an apps fold re-folds against the write that raced it", () => {
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ v: 1 }, 1000) };
seed(rec);
let woke = 0;
const hooks = lib.appsSyncHooks(SEED, () => woke++);
// land an app's own section write in the real window: after the fold has read
// the record, before the hub stores its result. The fold's first read of the
// store is the seam, so hook it.
const realGet = env.localStorage.getItem;
let reads = 0;
env.localStorage.getItem = (k) => {
const raw = realGet(k);
if (k === BRIDGE_STORE_KEY && ++reads === 1) {
const mid = JSON.parse(raw);
mid.apps = { ...mid.apps, game1: entry({ v: 3 }, 3000) };
env.localStorage.setItem(BRIDGE_STORE_KEY, JSON.stringify(mid));
}
return raw;
};
try {
hooks.apply({ board: entry({ v: 2 }, 2000) });
} finally {
env.localStorage.getItem = realGet;
}
// a blind write would have stored the fold of the STALE map and lost game1
assert.deepEqual(Object.keys(storedApps()).sort(), ["board", "chat", "game1"]);
assert.equal(woke, 1);
});
test("a write never resurrects a record that was forgotten under it", () => {
seed(lib.newRecord(SEED));
const stale = lib.curRecord();
env.localStorage.clear(); // "forget identity", in another tab
assert.equal(lib.writeRecord(stale, [["name", "ana"]]), null, "nothing to write onto");
assert.equal(env.localStorage.getItem(BRIDGE_STORE_KEY), null);
});
test("a write never rolls an identity swap back", () => {
seed(lib.newRecord(SEED));
const stale = lib.curRecord();
seed(lib.newRecord(OTHER_SEED)); // an import, in another tab
assert.equal(lib.writeRecord(stale, [["name", "ana"]]), null);
assert.equal(readBridgeRecord().rec.seed, OTHER_SEED, "the new identity stands");
assert.equal(readBridgeRecord().rec.name, "");
});
test("the three answers are distinguishable, because they mean different things", () => {
seed(lib.newRecord(SEED));
// a write
const out = lib.writeRecord(lib.curRecord(), [["name", "ana"]]);
assert.equal(typeof out, "object");
assert.equal(lib.wroteRecord(out), true);
// nothing to write: the result is byte-identical to what is stored. NOT a
// failure — no updatedAt bump, no storage event, no sync wake
const before = env.localStorage.getItem(BRIDGE_STORE_KEY);
const echo = lib.writeRecord(lib.curRecord(), [["name", "ana"]]);
assert.equal(echo, false);
assert.equal(lib.wroteRecord(echo), false);
assert.equal(env.localStorage.getItem(BRIDGE_STORE_KEY), before, "an echo must not restamp updatedAt");
// refused: the caller's change did not land. `some?` would call this a write,
// which is exactly why the two falsy answers are different values
env.localStorage.clear();
const refused = lib.writeRecord(lib.newRecord(SEED), [["name", "maria"]]);
assert.equal(refused, null);
assert.equal(lib.wroteRecord(refused), false);
});
test("a write reads the store exactly once, and applies to THAT read", () => {
// the read is not a retry loop: this function is synchronous, so no other
// frame on this origin can run between two reads and a second read can only
// ever agree with the first. One read, applied to, is the whole guarantee —
// and the loop that used to be here masked a mutation that applied the pairs
// to the caller's stale record, because it had already converged the two.
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ v: 1 }, 1000) };
seed(rec);
const stale = lib.curRecord();
const realGet = env.localStorage.getItem;
let reads = 0;
env.localStorage.getItem = (k) => {
if (k === BRIDGE_STORE_KEY) reads++;
return realGet(k);
};
try {
lib.writeRecord(stale, [["soc", { f: ["x"] }]]);
} finally {
env.localStorage.getItem = realGet;
}
assert.equal(reads, 1);
assert.deepEqual(storedApps(), { chat: { state: { v: 1 }, ts: 1000 } });
});
test("an undefined pair value is stored as the field's null, never dropped", () => {
// JSON.stringify DROPS an undefined value, so a nine-field record would land
// on the apex — from the very file whose job is being the key-order tripwire.
// A fold result that happens not to carry `hue` is exactly this shape.
const rec = lib.newRecord(SEED);
rec.hue = 200;
seed(rec);
lib.writeRecord(lib.curRecord(), [["hue", undefined]]);
const raw = env.localStorage.getItem(BRIDGE_STORE_KEY);
assert.deepEqual(Object.keys(JSON.parse(raw)), FIELDS, "ten fields, in order");
assert.match(raw, /"hue":null/);
});
// ---- the budget is the WRITE's rule, not the fold's --------------------------
//
// sanitizeRecord/readBridgeRecord do not cap on read, and writeBridgeRecord does
// not sanitize at all: the caps live in the bridge host, which home bypasses. A
// fold that respected the budget was therefore only half the fix — a big enough
// `soc` blob reached the same bricking through the same write.
test("a soc blob past the record budget is refused, and the sections stay alive", () => {
seed(lib.newRecord(SEED));
const fat = { f: Array.from({ length: 900 }, (_, i) => ({ pub: "p".repeat(180), pet: "n".repeat(32), i })) };
assert.ok(JSON.stringify(fat).length > MAX_RECORD_BYTES, "the blob alone is past the record cap");
const warned = warnings(() => {
assert.equal(lib.writeRecord(lib.curRecord(), [["soc", fat]]), null, "refused, not written");
});
assert.equal(readBridgeRecord().rec.soc, null, "nothing was stored");
assert.ok(env.localStorage.getItem(BRIDGE_STORE_KEY).length <= MAX_RECORD_BYTES);
assert.equal(warned.length, 1, "a refusal is never silent");
assert.match(warned[0], /refusing a write the bridge host would refuse/);
// the point of the refusal: the apps self-sync is still alive. With the blob
// stored, the host refuses every later t:"app" write with conflict "full" and
// the sections freeze for good
let woke = 0;
lib.appsSyncHooks(SEED, () => woke++).apply({ chat: entry({ n: 1 }, 1000) });
assert.equal(woke, 1);
assert.deepEqual(storedApps(), { chat: { state: { n: 1 }, ts: 1000 } });
});
test("a profile write is not size-gated — the host does not gate it either", () => {
// clampProfile bounds the four fields, and the host's t:"profile" branch has
// no fits? check at all. Refusing a rename because some other section filled
// the record would cost the user an edit for nothing.
const rec = lib.newRecord(SEED);
rec.soc = { blob: "s".repeat(MAX_RECORD_BYTES) }; // already past the cap
seed(rec);
assert.ok(env.localStorage.getItem(BRIDGE_STORE_KEY).length > MAX_RECORD_BYTES);
assert.ok(lib.wroteRecord(lib.writeRecord(lib.curRecord(), [["name", "ana"]])));
assert.equal(readBridgeRecord().rec.name, "ana");
});
test("a write that shrinks an over-budget record is always accepted", () => {
// the escape the host never needs (it can't be handed an over-budget record)
// and the hub does: the only way a device bricked by an older build is ever
// repaired is a write that makes the record smaller
const rec = lib.newRecord(SEED);
rec.soc = { blob: "s".repeat(MAX_RECORD_BYTES) };
seed(rec);
const before = env.localStorage.getItem(BRIDGE_STORE_KEY).length;
const out = lib.writeRecord(lib.curRecord(), [["soc", { blob: "s".repeat(MAX_RECORD_BYTES - 1000) }]]);
assert.ok(lib.wroteRecord(out), "still over the cap, but smaller than what it replaces");
assert.ok(env.localStorage.getItem(BRIDGE_STORE_KEY).length < before);
});
test("a bricked device is repaired by a fold that only shrinks it", () => {
// a device that took an oversized fold from the pre-fix build. The give-back
// used to revert EVERY key the fold adopted, including one whose remote entry
// won on ts and was SMALLER — undoing a real improvement and leaving the
// device bricked through the one path that could have freed it for free.
const mine = ["chat", "board", "game1", "game2", "game3", "game4", "game5"];
const rec = lib.newRecord(SEED);
rec.apps = fullSections(mine);
seed(rec);
const before = JSON.stringify(storedApps()).length;
assert.ok(before > MAX_APPS_BYTES, "bricked: the host refuses every section write");
let woke = 0;
warnings(() => lib.appsSyncHooks(SEED, () => woke++).apply({ game5: entry({ v: "newer and smaller" }, 99999) }));
assert.ok(JSON.stringify(storedApps()).length < before, "strictly smaller than it was");
assert.deepEqual(Object.keys(storedApps()).sort(), [...mine].sort(), "and nothing of ours was dropped");
assert.deepEqual(storedApps().game5, { state: { v: "newer and smaller" }, ts: 99999 });
assert.equal(woke, 1, "the improvement goes back out to the other devices");
});
test("the record cap refuses sections the apps cap alone would have taken", () => {
// the two halves of the budget are not the same check. Here the merged map is
// far inside MAX_APPS_BYTES and it is the RECORD that overflows, because `soc`
// is spending the reserve — so a fits? that only looked at the apps map would
// wave the whole fold through, and the write would then be refused wholesale
// and NOTHING adopted.
const rec = lib.newRecord(SEED);
rec.soc = { blob: "s".repeat(50000) };
rec.apps = { chat: entry("x".repeat(16000), 1000) };
seed(rec);
const remote = {
sueta: entry("y".repeat(4000), 2000),
game1: entry("z".repeat(16000), 2001),
game2: entry("z".repeat(16000), 2002),
game3: entry("z".repeat(16000), 2003),
game4: entry("z".repeat(16000), 2004),
};
const warned = warnings(() => lib.appsSyncHooks(SEED, () => {}).apply(remote));
assert.ok(env.localStorage.getItem(BRIDGE_STORE_KEY).length <= MAX_RECORD_BYTES, "the record fits");
assert.ok(
JSON.stringify(storedApps()).length < MAX_APPS_BYTES - 20000,
"and the apps map was never anywhere near ITS cap — the record half is what bound",
);
// one section given back, four adopted: the trim is what makes that possible
assert.deepEqual(Object.keys(storedApps()).sort(), ["chat", "game2", "game3", "game4", "sueta"]);
assert.equal(warned.length, 1);
assert.match(warned[0], /game1/);
});
test("on-write never fires for a write that did not happen", () => {
const rec = lib.newRecord(SEED);
rec.apps = { chat: entry({ v: 1 }, 1000) };
seed(rec);
let woke = 0;
const hooks = lib.appsSyncHooks(SEED, () => woke++);
// "forget identity", in another tab, between the fold's read and its store
const realGet = env.localStorage.getItem;
let reads = 0;
env.localStorage.getItem = (k) => {
if (k === BRIDGE_STORE_KEY && ++reads === 2) env.localStorage.clear();
return realGet(k);
};
try {
hooks.apply({ board: entry({ v: 2 }, 2000) });
} finally {
env.localStorage.getItem = realGet;
}
assert.equal(env.localStorage.getItem(BRIDGE_STORE_KEY), null, "the record stayed forgotten");
assert.equal(woke, 0, "nothing was stored, so there is nothing to deposit for the other devices");
});
test("the refusal warning is bounded, and names the end that is at fault", () => {
// it used to fire once per sync round, forever, on rounds that wrote nothing
// — and it blamed the incoming sections for a record that was already full
const rec = lib.newRecord(SEED);
rec.apps = fullSections(["chat", "board", "game1", "game2", "game3", "game4", "game5"]);
seed(rec);
const hooks = lib.appsSyncHooks(SEED, () => {});
const remote = { sueta: entry({ n: 1 }, 3000) };
const before = env.localStorage.getItem(BRIDGE_STORE_KEY);
const first = warnings(() => hooks.apply(remote));
const second = warnings(() => hooks.apply(remote));
const third = warnings(() => hooks.apply(remote));
assert.equal(env.localStorage.getItem(BRIDGE_STORE_KEY), before, "no round wrote anything");
assert.equal(first.length, 1);
assert.equal(second.length, 0, "a round that stores nothing says it once");
assert.equal(third.length, 0);
assert.match(first[0], /own sections are ALREADY past it/, "the record is at fault, not the peer");
assert.match(first[0], /nothing was stored/);
});
test("a write is assembled through FIELDS, so a stray pair cannot append a key", () => {
seed(lib.newRecord(SEED));
// Object.assign preserves position only for keys the target already has, so a
// pair naming anything else used to be APPENDED — in the very file whose job
// is being the key-order tripwire
const warned = warnings(() =>
lib.writeRecord(lib.curRecord(), [["apps", { chat: entry({ n: 1 }, 9000) }], ["ephemeral", true]]));
assert.deepEqual(Object.keys(readBridgeRecord().rec), FIELDS);
assert.deepEqual(Object.keys(JSON.parse(env.localStorage.getItem(BRIDGE_STORE_KEY))), FIELDS);
assert.equal(warned.length, 1);
assert.match(warned[0], /ephemeral/);
});
|