@agentshouse/mdruntime 0.1.0-c99e5271060cd → 0.1.0-ca14c49c06d90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/AGENTS.md +142 -112
  2. package/README.md +11 -1
  3. package/dist/adapter.d.ts +59 -13
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/attempt.d.ts +16 -0
  6. package/dist/attempt.d.ts.map +1 -0
  7. package/dist/attempt.js +70 -0
  8. package/dist/binary.d.ts +23 -0
  9. package/dist/binary.d.ts.map +1 -0
  10. package/dist/binary.js +8 -0
  11. package/dist/bootstrap.d.ts +2 -2
  12. package/dist/bootstrap.d.ts.map +1 -1
  13. package/dist/bootstrap.js +33 -14
  14. package/dist/change-set.d.ts.map +1 -1
  15. package/dist/change-set.js +8 -7
  16. package/dist/change.d.ts +11 -0
  17. package/dist/change.d.ts.map +1 -1
  18. package/dist/change.js +26 -1
  19. package/dist/chat/acts.d.ts +33 -0
  20. package/dist/chat/acts.d.ts.map +1 -0
  21. package/dist/chat/acts.js +36 -0
  22. package/dist/chat/context.d.ts +3 -0
  23. package/dist/chat/context.d.ts.map +1 -0
  24. package/dist/chat/context.js +10 -0
  25. package/dist/chat/input.d.ts +9 -0
  26. package/dist/chat/input.d.ts.map +1 -0
  27. package/dist/chat/input.js +99 -0
  28. package/dist/chat/kit.d.ts +60 -0
  29. package/dist/chat/kit.d.ts.map +1 -0
  30. package/dist/chat/kit.js +123 -0
  31. package/dist/chat/lasting.d.ts +6 -0
  32. package/dist/chat/lasting.d.ts.map +1 -0
  33. package/dist/chat/lasting.js +19 -0
  34. package/dist/chat/list.d.ts +26 -0
  35. package/dist/chat/list.d.ts.map +1 -0
  36. package/dist/chat/list.js +62 -0
  37. package/dist/chat/markdown.d.ts +20 -0
  38. package/dist/chat/markdown.d.ts.map +1 -0
  39. package/dist/chat/markdown.js +188 -0
  40. package/dist/chat/message.d.ts +6 -0
  41. package/dist/chat/message.d.ts.map +1 -0
  42. package/dist/chat/message.js +192 -0
  43. package/dist/chat/messages.d.ts +21 -0
  44. package/dist/chat/messages.d.ts.map +1 -0
  45. package/dist/chat/messages.js +151 -0
  46. package/dist/chat/name.d.ts +6 -0
  47. package/dist/chat/name.d.ts.map +1 -0
  48. package/dist/chat/name.js +37 -0
  49. package/dist/chat/opened.d.ts +11 -0
  50. package/dist/chat/opened.d.ts.map +1 -0
  51. package/dist/chat/opened.js +17 -0
  52. package/dist/chat/overlay.d.ts +31 -0
  53. package/dist/chat/overlay.d.ts.map +1 -0
  54. package/dist/chat/overlay.js +97 -0
  55. package/dist/chat/parts.d.ts +57 -0
  56. package/dist/chat/parts.d.ts.map +1 -0
  57. package/dist/chat/parts.js +119 -0
  58. package/dist/chat/process.d.ts +20 -0
  59. package/dist/chat/process.d.ts.map +1 -0
  60. package/dist/chat/process.js +32 -0
  61. package/dist/chat/provider.d.ts +63 -0
  62. package/dist/chat/provider.d.ts.map +1 -0
  63. package/dist/chat/provider.js +620 -0
  64. package/dist/chat/question.d.ts +9 -0
  65. package/dist/chat/question.d.ts.map +1 -0
  66. package/dist/chat/question.js +80 -0
  67. package/dist/chat/questions.d.ts +31 -0
  68. package/dist/chat/questions.d.ts.map +1 -0
  69. package/dist/chat/questions.js +111 -0
  70. package/dist/chat/read.d.ts +5 -0
  71. package/dist/chat/read.d.ts.map +1 -0
  72. package/dist/chat/read.js +13 -0
  73. package/dist/chat/ready.d.ts +33 -0
  74. package/dist/chat/ready.d.ts.map +1 -0
  75. package/dist/chat/ready.js +91 -0
  76. package/dist/chat/style.d.ts +4 -0
  77. package/dist/chat/style.d.ts.map +1 -0
  78. package/dist/chat/style.js +302 -0
  79. package/dist/chat.d.ts +14 -0
  80. package/dist/chat.d.ts.map +1 -0
  81. package/dist/chat.js +8 -0
  82. package/dist/client.d.ts +11 -4
  83. package/dist/client.d.ts.map +1 -1
  84. package/dist/client.js +12 -2
  85. package/dist/connection.d.ts +13 -6
  86. package/dist/connection.d.ts.map +1 -1
  87. package/dist/connection.js +588 -205
  88. package/dist/conversation.d.ts +14 -4
  89. package/dist/conversation.d.ts.map +1 -1
  90. package/dist/conversation.js +188 -63
  91. package/dist/entry.d.ts +17 -0
  92. package/dist/entry.d.ts.map +1 -0
  93. package/dist/entry.js +156 -0
  94. package/dist/expectation.d.ts +21 -0
  95. package/dist/expectation.d.ts.map +1 -0
  96. package/dist/expectation.js +70 -0
  97. package/dist/fence.d.ts +6 -0
  98. package/dist/fence.d.ts.map +1 -0
  99. package/dist/fence.js +10 -0
  100. package/dist/host.d.ts +3 -2
  101. package/dist/host.d.ts.map +1 -1
  102. package/dist/host.js +13 -6
  103. package/dist/identity.d.ts +2 -0
  104. package/dist/identity.d.ts.map +1 -1
  105. package/dist/identity.js +6 -0
  106. package/dist/index.d.ts +9 -4
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +4 -0
  109. package/dist/memory.d.ts +4 -3
  110. package/dist/memory.d.ts.map +1 -1
  111. package/dist/memory.js +40 -11
  112. package/dist/outcome.d.ts +11 -1
  113. package/dist/outcome.d.ts.map +1 -1
  114. package/dist/outcome.js +7 -0
  115. package/dist/overlay.d.ts +13 -2
  116. package/dist/overlay.d.ts.map +1 -1
  117. package/dist/overlay.js +64 -22
  118. package/dist/query.d.ts +2 -0
  119. package/dist/query.d.ts.map +1 -1
  120. package/dist/reach.d.ts +2 -0
  121. package/dist/reach.d.ts.map +1 -1
  122. package/dist/reach.js +26 -4
  123. package/dist/react.d.ts +2 -1
  124. package/dist/react.d.ts.map +1 -1
  125. package/dist/read-side.d.ts.map +1 -1
  126. package/dist/read-side.js +9 -2
  127. package/dist/refresh.d.ts.map +1 -1
  128. package/dist/refresh.js +12 -17
  129. package/dist/replica.d.ts +5 -2
  130. package/dist/replica.d.ts.map +1 -1
  131. package/dist/replica.js +33 -28
  132. package/dist/room-draft.d.ts +19 -2
  133. package/dist/room-draft.d.ts.map +1 -1
  134. package/dist/room-draft.js +119 -1
  135. package/dist/rows.d.ts +3 -1
  136. package/dist/rows.d.ts.map +1 -1
  137. package/dist/rows.js +3 -1
  138. package/dist/save.d.ts +23 -0
  139. package/dist/save.d.ts.map +1 -1
  140. package/dist/save.js +309 -18
  141. package/dist/status.d.ts +0 -1
  142. package/dist/status.d.ts.map +1 -1
  143. package/dist/status.js +0 -8
  144. package/dist/store.d.ts +36 -18
  145. package/dist/store.d.ts.map +1 -1
  146. package/dist/store.js +291 -172
  147. package/dist/wire.d.ts +3 -0
  148. package/dist/wire.d.ts.map +1 -1
  149. package/dist/wire.js +3 -0
  150. package/package.json +26 -14
  151. package/dist/attachment.d.ts +0 -3
  152. package/dist/attachment.d.ts.map +0 -1
  153. package/dist/attachment.js +0 -8
@@ -8,16 +8,19 @@ import { createSearchIndex, searchPlanOf, } from './search.js';
8
8
  import { bootstrapRoom } from './bootstrap.js';
9
9
  import { changeSet, mergeChangeSets } from './change-set.js';
10
10
  import { createConversationStore, } from './conversation.js';
11
+ import { queuedFile } from './change.js';
11
12
  import { validRoot } from './collection.js';
12
13
  import { runtimeError } from './failure.js';
13
14
  import { createLog } from './log.js';
14
- import { calledResult, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
15
+ import { binaryRecord } from './binary.js';
16
+ import { absentBinary, binaryAnswer, calledResult, failure, historyAnswer, undeclared, UNCERTAIN, } from './outcome.js';
15
17
  import { refreshRoom } from './refresh.js';
16
- import { buildBatch } from './save.js';
18
+ import { buildBatch, joined, limitedIn, requeued } from './save.js';
17
19
  import { applyReturned, compositionOf, confirmedOf, landed, readReclassified, replicaOf, sameMembership, withdrawals, } from './replica.js';
18
- import { changedParts, changesOf, held, readParts, returnedBy, returnedInto, staleWrite, underneath, } from './room-draft.js';
19
- import { settled, same, saving, STARTING, transferring, } from './status.js';
20
+ import { binaryBase, changedParts, changesOf, held, heldFile, joinedDraft, readParts, returnedBy, returnedInto, staleWrite, underneath, withdrawnDrafts, } from './room-draft.js';
21
+ import { same, saving, STARTING, transferring, } from './status.js';
20
22
  import { detectStorageLoss, nextGeneration } from './durability.js';
23
+ import { FencedMap } from './fence.js';
21
24
  import { roomDraftRevision } from './identity.js';
22
25
  import { beneath, normalize } from './paths.js';
23
26
  import { openRuntimeStore, } from './store.js';
@@ -36,22 +39,36 @@ function declaredSelection(room) {
36
39
  root !== '' &&
37
40
  selection.roots.every((other, at) => at === index || !within(other, root)));
38
41
  }
42
+ const OPENING_LANE = 'opening';
43
+ const CONVERSATION_LANE = 'conversations';
44
+ const ROOM_READS = 256;
45
+ function roomLane(locator) {
46
+ return `room ${locator}`;
47
+ }
39
48
  export function composeRuntime(options) {
40
49
  const localSaves = options.adapter.localSaves === true;
41
50
  let authorization = options.authorization;
42
51
  let opening;
43
52
  let store;
44
- const replicas = new Map();
53
+ const fences = new Map();
54
+ const isFenced = (locator) => fences.has(locator);
55
+ let fencings = 0;
56
+ const lastFenced = new Map();
57
+ const fencedSince = (locator, since) => fences.has(locator) || (lastFenced.get(locator) ?? 0) > since;
58
+ let discoveries = 0;
59
+ const replicas = new FencedMap(isFenced);
60
+ const queuesWithoutReplica = new FencedMap(isFenced);
45
61
  const pendings = new Map();
46
- const roomDrafts = new Map();
62
+ const roomDrafts = new FencedMap(isFenced);
47
63
  const presentations = new Map();
48
64
  let limits = [];
49
65
  let capabilities = [];
50
- const rooms = new Map();
66
+ const rooms = new FencedMap(isFenced);
51
67
  const counted = new Map();
52
68
  const unreadable = new Set();
53
- const models = new Map();
54
- const liveModels = new Map();
69
+ const isModelFenced = (key) => isFenced(key.slice(key.indexOf(' ') + 1));
70
+ const models = new FencedMap(isModelFenced);
71
+ const liveModels = new FencedMap(isModelFenced);
55
72
  const invalidModels = new Set();
56
73
  const discovered = new Map();
57
74
  let roomsListed = false;
@@ -87,13 +104,20 @@ export function composeRuntime(options) {
87
104
  let withdrawStream;
88
105
  const announced = new Map();
89
106
  let underway = null;
107
+ let streamed = null;
90
108
  let handed = false;
91
109
  const submitting = new Map();
92
110
  const answering = new Map();
93
111
  let complete = false;
94
112
  let recorded = false;
95
113
  const stale = new Set();
96
- let chain = Promise.resolve();
114
+ let generation = 0;
115
+ let discovering = null;
116
+ let opened = null;
117
+ const owed = new Set();
118
+ const lanes = new Map();
119
+ let roomReads = { generation, free: ROOM_READS, waiting: [] };
120
+ const working = new Set();
97
121
  let mutations = Promise.resolve();
98
122
  const roomListeners = new Set();
99
123
  const refusalListeners = new Set();
@@ -104,7 +128,7 @@ export function composeRuntime(options) {
104
128
  const presentationListeners = new Set();
105
129
  const readSide = createReadSide();
106
130
  let withdrawConversations;
107
- let currencyQueued = false;
131
+ let currencyQueued = null;
108
132
  let socketOpen = false;
109
133
  let serviceMode = 'open';
110
134
  let haltedRefusal = false;
@@ -114,7 +138,7 @@ export function composeRuntime(options) {
114
138
  const memberListeners = new Set();
115
139
  const materialized = new Set();
116
140
  let everyRoom = false;
117
- const transfers = new Map();
141
+ const transfers = new FencedMap(isFenced);
118
142
  const conversations = createConversationStore({
119
143
  agents: declaredAgents,
120
144
  documents: (locator) => rowsQuery(planOf({ rooms: [locator], select: 'all' }, roomFacts())),
@@ -122,6 +146,7 @@ export function composeRuntime(options) {
122
146
  durably,
123
147
  stream: () => options.adapter.conversations,
124
148
  publish: publishChangeSet,
149
+ online: () => !status.providerUnreachable && refusal === null,
125
150
  closed: () => closed,
126
151
  });
127
152
  const searchIndex = createSearchIndex({
@@ -193,7 +218,9 @@ export function composeRuntime(options) {
193
218
  }
194
219
  }
195
220
  function pending() {
196
- return !localSaves && [...replicas.values()].some((replica) => replica.queue.length > 0);
221
+ return (!localSaves &&
222
+ (queuesWithoutReplica.size > 0 ||
223
+ [...replicas.values()].some((replica) => replica.queue.length > 0)));
197
224
  }
198
225
  function attention() {
199
226
  const attentionRequired = [];
@@ -227,12 +254,11 @@ export function composeRuntime(options) {
227
254
  if (replica === undefined)
228
255
  return 0;
229
256
  const roots = replica.confirmed.record.typedSelection?.roots ?? [];
230
- let count = 0;
231
- for (const path of Object.keys(replica.visible.documents)) {
232
- if (markdownPath(path) && !roots.some((root) => beneath(root, path)))
233
- count += 1;
234
- }
235
- return count;
257
+ const paths = [
258
+ ...Object.keys(replica.visible.documents).filter(markdownPath),
259
+ ...Object.keys(replica.visible.binaries),
260
+ ];
261
+ return paths.filter((path) => !roots.some((root) => beneath(root, path))).length;
236
262
  }
237
263
  function listed() {
238
264
  return structuredClone([...rooms.values()].map((room) => {
@@ -303,10 +329,7 @@ export function composeRuntime(options) {
303
329
  return Object.freeze({ batch, ...(state?.submission ?? { state: 'waiting' }) });
304
330
  }
305
331
  function entriesOf(locator) {
306
- const replica = replicas.get(locator);
307
- if (replica === undefined)
308
- return [];
309
- return replica.queue.map(entryOf);
332
+ return (replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? []).map(entryOf);
310
333
  }
311
334
  function emitQueue(locator) {
312
335
  const entries = entriesOf(locator);
@@ -403,10 +426,7 @@ export function composeRuntime(options) {
403
426
  const revoked = { status: 'unavailable', reason: 'revoked' };
404
427
  try {
405
428
  const local = await openLocal();
406
- await local.revokeModels([...replicas.values()].map((replica) => ({
407
- record: replica.confirmed.record,
408
- queue: replica.queue,
409
- })));
429
+ await local.revokeModels([...replicas.values()].map((replica) => replica.confirmed.record));
410
430
  }
411
431
  catch {
412
432
  journal.record('model revocation persistence failed');
@@ -473,19 +493,28 @@ export function composeRuntime(options) {
473
493
  },
474
494
  providerOwnedPaths: room.providerOwnedPaths,
475
495
  roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
476
- attachments: replicas.get(room.locator)?.confirmed.record.attachments ?? [],
477
496
  synchronizationAvailable,
478
497
  refusal: scopedRefusals.get(room.locator) ?? sourceRefusals.get(room.locator) ?? null,
479
498
  });
480
499
  }
481
- async function applyDiscovery(listed, local) {
500
+ async function applyDiscovery(found, started) {
501
+ for (const room of found) {
502
+ const fence = fences.get(room.locator);
503
+ if (fence !== undefined && fence.erased && fence.at < started)
504
+ fences.delete(room.locator);
505
+ }
506
+ const listed = found.filter((room) => !fences.has(room.locator));
482
507
  const listedLocators = new Set(listed.map((room) => room.locator));
483
508
  for (const locator of listedLocators)
484
509
  scopedRefusals.delete(locator);
485
510
  discovered.clear();
486
511
  for (const room of listed)
487
512
  discovered.set(room.locator, room);
488
- await dropUnlisted(listedLocators, local);
513
+ const unlisted = unlistedHeld(new Set(found.map((room) => room.locator)));
514
+ if (options.adapter.exhaustiveDiscovery === true)
515
+ await loseRooms(unlisted);
516
+ else
517
+ unlistedKept(unlisted);
489
518
  for (const room of listed) {
490
519
  const held = replicas.get(room.locator)?.confirmed.record;
491
520
  if (held !== undefined && held.replica.selection !== room.replica.selection) {
@@ -496,44 +525,79 @@ export function composeRuntime(options) {
496
525
  }
497
526
  roomsListed = true;
498
527
  emitRooms();
528
+ return listed;
499
529
  }
500
530
  function unlistedHeld(listed) {
501
- return [...new Set([...rooms.keys(), ...roomDrafts.keys(), ...transfers.keys()])].filter((locator) => !listed.has(locator));
502
- }
503
- async function dropUnlisted(listed, local) {
504
- if (unlistedHeld(listed).length === 0)
505
- return;
506
- await exclusive(async () => {
507
- const unlisted = unlistedHeld(listed);
508
- await local.dropRooms(unlisted);
509
- const removed = unlisted.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
510
- room: true,
511
- queue: true,
512
- drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
513
- }));
514
- for (const locator of unlisted) {
515
- for (const batch of replicas.get(locator)?.queue ?? [])
516
- pendings.delete(batch.id);
517
- replicas.delete(locator);
531
+ return [
532
+ ...new Set([
533
+ ...rooms.keys(),
534
+ ...roomDrafts.keys(),
535
+ ...transfers.keys(),
536
+ ...queuesWithoutReplica.keys(),
537
+ ]),
538
+ ].filter((locator) => !listed.has(locator));
539
+ }
540
+ function unlistedKept(unlisted) {
541
+ for (const locator of unlisted) {
542
+ const held = replicas.get(locator)?.confirmed.record;
543
+ if (held !== undefined)
544
+ list(held, false);
545
+ else
518
546
  rooms.delete(locator);
519
- roomDrafts.delete(locator);
520
- transfers.delete(locator);
521
- unreadable.delete(locator);
522
- stale.delete(locator);
523
- scopedRefusals.delete(locator);
524
- sourceRefusals.delete(locator);
525
- declarationRefusals.delete(locator);
526
- for (const attached of ['collection', 'typed-selection']) {
527
- const key = modelKey(locator, attached);
528
- models.delete(key);
529
- liveModels.delete(key);
530
- invalidModels.delete(key);
531
- }
532
- journal.record(`room unlisted ${locator}`);
547
+ }
548
+ }
549
+ function fence(locators) {
550
+ const fresh = [...new Set(locators)].filter((locator) => !fences.has(locator));
551
+ const removed = fresh.map((locator) => changeSet(locator, replicas.get(locator) ?? null, null, {
552
+ room: true,
553
+ queue: true,
554
+ drafts: [...(roomDrafts.get(locator)?.keys() ?? [])],
555
+ }));
556
+ for (const locator of fresh) {
557
+ fences.set(locator, { at: discoveries, erased: false });
558
+ lastFenced.set(locator, ++fencings);
559
+ for (const batch of replicas.get(locator)?.queue ?? queuesWithoutReplica.get(locator) ?? [])
560
+ pendings.delete(batch.id);
561
+ replicas.delete(locator);
562
+ queuesWithoutReplica.delete(locator);
563
+ rooms.delete(locator);
564
+ roomDrafts.delete(locator);
565
+ transfers.delete(locator);
566
+ discovered.delete(locator);
567
+ announced.delete(locator);
568
+ unreadable.delete(locator);
569
+ stale.delete(locator);
570
+ scopedRefusals.delete(locator);
571
+ sourceRefusals.delete(locator);
572
+ declarationRefusals.delete(locator);
573
+ for (const attached of ['collection', 'typed-selection']) {
574
+ const key = modelKey(locator, attached);
575
+ models.delete(key);
576
+ liveModels.delete(key);
577
+ invalidModels.delete(key);
533
578
  }
534
- publishChangeSet(mergeChangeSets(removed));
535
- moveTo({ ...status, ...attention() });
536
- });
579
+ journal.record(`room lost ${locator}`);
580
+ }
581
+ if (fresh.length === 0)
582
+ return fresh;
583
+ publishChangeSet(mergeChangeSets(removed));
584
+ for (const locator of fresh)
585
+ materialized.delete(locator);
586
+ moveTo({ ...status, ...attention() });
587
+ return fresh;
588
+ }
589
+ async function erased(local, locators) {
590
+ await local.erase(locators);
591
+ for (const locator of locators) {
592
+ const held = fences.get(locator);
593
+ if (held !== undefined)
594
+ held.erased = true;
595
+ }
596
+ }
597
+ async function loseRooms(locators) {
598
+ const fresh = fence(locators);
599
+ if (fresh.length > 0)
600
+ await durably((local) => erased(local, fresh));
537
601
  }
538
602
  function restored(key, persisted) {
539
603
  const prior = models.get(key);
@@ -588,6 +652,12 @@ export function composeRuntime(options) {
588
652
  }
589
653
  const previousReplicas = new Map(replicas);
590
654
  replicas.clear();
655
+ queuesWithoutReplica.clear();
656
+ const replicated = new Set(records.map((record) => record.locator));
657
+ for (const [locator, queue] of queuedBatches) {
658
+ if (!replicated.has(locator))
659
+ queuesWithoutReplica.set(locator, queue);
660
+ }
591
661
  for (const record of records) {
592
662
  const model = restored(modelKey(record.locator, 'collection'), record.model);
593
663
  const selectionModel = restored(modelKey(record.locator, 'typed-selection'), record.selectionModel);
@@ -601,7 +671,6 @@ export function composeRuntime(options) {
601
671
  rooms.set(record.locator, {
602
672
  ...existing,
603
673
  roomVersion: record.roomVersion,
604
- attachments: record.attachments,
605
674
  operations: record.operations,
606
675
  replica: record.replica,
607
676
  model: modelTypes(model),
@@ -633,8 +702,15 @@ export function composeRuntime(options) {
633
702
  return lastCompleted;
634
703
  }
635
704
  async function open() {
636
- const opened = await openRuntimeStore();
705
+ if (closed)
706
+ throw new Error('runtime closed');
707
+ const opened = await openRuntimeStore(isFenced);
637
708
  store = opened;
709
+ const interrupted = await opened.fences();
710
+ for (const locator of interrupted) {
711
+ fences.set(locator, { at: discoveries, erased: false });
712
+ lastFenced.set(locator, ++fencings);
713
+ }
638
714
  const kept = options.shell.generation;
639
715
  const storedGeneration = await opened.generation();
640
716
  const loss = detectStorageLoss(storedGeneration, kept);
@@ -648,6 +724,8 @@ export function composeRuntime(options) {
648
724
  emitStorageLoss();
649
725
  }
650
726
  const lastCompleted = await readState(opened);
727
+ if (interrupted.length > 0)
728
+ await erased(opened, interrupted);
651
729
  moveTo({ ...status, lastCompleted, ...attention() });
652
730
  return opened;
653
731
  }
@@ -674,6 +752,7 @@ export function composeRuntime(options) {
674
752
  return state !== undefined && state.submission.state !== 'submitted' && !state.lost;
675
753
  }
676
754
  async function settle(local, locator, before, confirmed, queue, settling = {}) {
755
+ const previousQueue = before?.queue ?? queuesWithoutReplica.get(locator) ?? [];
677
756
  const withdrawn = withdrawals(confirmed, queue, withdrawable, localSaves);
678
757
  const kept = queue.filter((batch) => !withdrawn.includes(batch));
679
758
  const unqueued = withdrawn.map((batch) => ({
@@ -699,20 +778,36 @@ export function composeRuntime(options) {
699
778
  });
700
779
  }
701
780
  }
702
- await local.write({
703
- next: confirmed.record,
704
- previous: before?.confirmed.record ?? null,
705
- ...(settling.stored === undefined ? {} : { stored: settling.stored }),
706
- queue: kept,
707
- previousQueue: before?.queue ?? [],
708
- roomDrafts: [...written.values()],
709
- });
710
- for (const batch of before?.queue ?? []) {
781
+ await local.keepAuthored({ locator, queue: kept, roomDrafts: [...written.values()] });
782
+ for (const batch of previousQueue) {
711
783
  if (!kept.includes(batch))
712
784
  pendings.delete(batch.id);
713
785
  }
714
786
  for (const record of written.values())
715
787
  rememberRoomDraft(record);
788
+ try {
789
+ await local.write({
790
+ next: confirmed.record,
791
+ previous: before?.confirmed.record ?? null,
792
+ ...(settling.stored === undefined ? {} : { stored: settling.stored }),
793
+ });
794
+ }
795
+ catch (error) {
796
+ const held = before === undefined ? null : replicaOf(before.confirmed, kept, before);
797
+ if (held !== null)
798
+ replicas.set(locator, held);
799
+ else if (kept.length > 0)
800
+ queuesWithoutReplica.set(locator, kept);
801
+ else
802
+ queuesWithoutReplica.delete(locator);
803
+ publishChangeSet(changeSet(locator, before ?? null, held, {
804
+ queue: kept.length !== previousQueue.length,
805
+ drafts: [...written.keys()],
806
+ }));
807
+ moveTo({ ...status, ...attention() });
808
+ throw error;
809
+ }
810
+ queuesWithoutReplica.delete(locator);
716
811
  for (const { batch, returned } of unqueued) {
717
812
  const outcome = localSaves ? 'dropped' : returned === null ? 'landed' : 'withdrawn';
718
813
  journal.record(`outcome ${locator} ${batch.id} ${outcome}`);
@@ -720,7 +815,7 @@ export function composeRuntime(options) {
720
815
  replicas.set(locator, replicaOf(confirmed, kept, before));
721
816
  return changeSet(locator, before ?? null, replicas.get(locator) ?? null, {
722
817
  room: settling.room,
723
- queue: kept.length !== (before?.queue.length ?? 0),
818
+ queue: kept.length !== previousQueue.length,
724
819
  drafts: [...written.keys()],
725
820
  });
726
821
  }
@@ -755,10 +850,10 @@ export function composeRuntime(options) {
755
850
  selection: taken(answers?.selection, held.selection, 'typed-selection'),
756
851
  };
757
852
  }
758
- function publish(local, room, confirmed, landing = {}) {
853
+ function publish(local, room, confirmed, landing, live) {
759
854
  const { base, stored } = landing;
760
855
  return exclusive(async () => {
761
- if (!continuing())
856
+ if (!live())
762
857
  return true;
763
858
  const prior = replicas.get(room.locator);
764
859
  if (base !== undefined &&
@@ -767,7 +862,7 @@ export function composeRuntime(options) {
767
862
  return false;
768
863
  }
769
864
  adoptModels(room.locator, confirmed.record);
770
- const queue = prior?.queue ?? [];
865
+ const queue = prior?.queue ?? queuesWithoutReplica.get(room.locator) ?? [];
771
866
  const roomVersion = confirmed.record.roomVersion;
772
867
  const accepted = queue.filter((batch) => {
773
868
  const accepted = pendings.get(batch.id)?.accepted;
@@ -786,6 +881,7 @@ export function composeRuntime(options) {
786
881
  list(room, true);
787
882
  publishChangeSet(set);
788
883
  moveTo({ ...status, ...attention() });
884
+ options.adapter.roomStream?.published(room.locator, roomVersion);
789
885
  return true;
790
886
  });
791
887
  }
@@ -809,7 +905,24 @@ export function composeRuntime(options) {
809
905
  if (changed.length > 0)
810
906
  return { outcome: 'refused', code: 'changed-underneath', retryable: false, parts: changed };
811
907
  const parts = saved.map(({ path, parts }) => changesOf(path, parts));
812
- const built = buildBatch({
908
+ const changes = parts.flatMap(({ changes }) => changes);
909
+ const requeue = requeued(replica.queue, changes, replica.visible, withdrawable, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
910
+ if (requeue.kind === 'refused')
911
+ return { outcome: 'refused', ...requeue.refusal };
912
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
913
+ const writer = replica.queue.find((batch) => batch.id === origin)?.writtenBy;
914
+ return writer?.app !== writtenBy?.app || writer?.release !== writtenBy?.release;
915
+ });
916
+ if (foreign) {
917
+ return {
918
+ outcome: 'refused',
919
+ code: 'unknown-revision',
920
+ retryable: false,
921
+ file: [...requeue.queued].at(-1),
922
+ };
923
+ }
924
+ const withdrawing = requeue.withdrawn.size > 0;
925
+ const input = {
813
926
  local: localSaves,
814
927
  baseRoomVersion: replica.confirmed.record.roomVersion,
815
928
  composition: compositionFor(replica.confirmed.record),
@@ -818,28 +931,86 @@ export function composeRuntime(options) {
818
931
  providerLimits: limits,
819
932
  handle: room.handle,
820
933
  visible: replica.visible,
821
- changes: parts.flatMap(({ changes }) => changes),
934
+ changes,
822
935
  bases: parts.flatMap(({ bases }) => bases),
823
936
  writtenBy,
937
+ };
938
+ const checked = withdrawing ? buildBatch({ ...input, queued: requeue.queued }) : null;
939
+ if (checked?.kind === 'refused')
940
+ return { outcome: 'refused', ...checked.refusal };
941
+ const owners = [...requeue.owners].flatMap((origin) => {
942
+ const batch = requeue.withdrawn.get(origin);
943
+ return batch === undefined || batch === null ? [] : [{ origin, batch }];
824
944
  });
825
- if (built.kind === 'refused')
945
+ if (owners.length > 1) {
946
+ return {
947
+ outcome: 'refused',
948
+ code: 'unknown-revision',
949
+ retryable: false,
950
+ file: [...requeue.queued].at(-1),
951
+ };
952
+ }
953
+ const built = !withdrawing
954
+ ? buildBatch(input)
955
+ : requeue.changes.length === 0
956
+ ? null
957
+ : buildBatch({
958
+ ...input,
959
+ visible: replicaOf(replica.confirmed, requeue.queue, replica).visible,
960
+ changes: requeue.changes,
961
+ });
962
+ if (built?.kind === 'refused')
826
963
  return { outcome: 'refused', ...built.refusal };
827
- const batches = [...replica.queue, built.batch];
964
+ const [owner] = owners;
965
+ const join = built === null || owner === undefined
966
+ ? null
967
+ : joined(requeue.queue, owner.batch, built.batch);
968
+ if (join?.kind === 'refused')
969
+ return { outcome: 'refused', ...join.refusal };
970
+ const withdrawn = new Map(requeue.withdrawn);
971
+ if (join !== null && owner !== undefined)
972
+ withdrawn.set(owner.origin, join.batch);
973
+ const drafted = new Map();
974
+ for (const [origin, replaced] of withdrawn) {
975
+ if (replaced === null)
976
+ continue;
977
+ const carried = withdrawnDrafts(pendings.get(origin)?.drafts ?? [], changes);
978
+ drafted.set(replaced.id, replaced === join?.batch
979
+ ? [
980
+ ...carried.map((held) => joinedDraft(held, saved)),
981
+ ...saved.filter((draft) => !carried.some((held) => held.path === draft.path)),
982
+ ]
983
+ : carried);
984
+ }
985
+ if (built !== null && join === null)
986
+ drafted.set(built.batch.id, saved);
987
+ const batches = join !== null ? join.queue : built === null ? requeue.queue : [...requeue.queue, built.batch];
988
+ for (const replaced of withdrawn.values()) {
989
+ if (replaced === null || !batches.includes(replaced))
990
+ continue;
991
+ const limited = limitedIn(batches, replaced, limits, (queue) => replicaOf(replica.confirmed, queue, replica).visible);
992
+ if (limited !== null)
993
+ return { outcome: 'refused', ...limited };
994
+ }
828
995
  const consumed = saved.map(({ path }) => path);
829
996
  try {
830
- await local.write({
831
- next: replica.confirmed.record,
832
- previous: replica.confirmed.record,
997
+ await local.keepAuthored({
998
+ locator,
833
999
  queue: batches,
834
- previousQueue: replica.queue,
835
- saved,
1000
+ saved: drafted,
836
1001
  consumedRoomDrafts: consumed,
837
1002
  });
838
1003
  }
839
1004
  catch {
840
1005
  return { outcome: 'refused', code: 'durable-write', retryable: true };
841
1006
  }
842
- pendings.set(built.batch.id, { drafts: saved, submission: { state: 'waiting' }, lost: false });
1007
+ if (fences.has(locator))
1008
+ throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
1009
+ for (const origin of withdrawn.keys())
1010
+ pendings.delete(origin);
1011
+ for (const [id, drafts] of drafted) {
1012
+ pendings.set(id, { drafts, submission: { state: 'waiting' }, lost: false });
1013
+ }
843
1014
  if (!localSaves)
844
1015
  recorded = false;
845
1016
  for (const path of consumed)
@@ -851,7 +1022,10 @@ export function composeRuntime(options) {
851
1022
  }));
852
1023
  moveTo({ ...(localSaves ? status : saving(status)), ...attention() });
853
1024
  submitLater(locator);
854
- return { outcome: 'saved', identifier: built.batch.id };
1025
+ const identifier = join?.batch.id ??
1026
+ built?.batch.id ??
1027
+ [...requeue.withdrawn].map(([origin, replaced]) => replaced?.id ?? origin).at(-1);
1028
+ return { outcome: 'saved', identifier };
855
1029
  }
856
1030
  async function mark(local, locator, identifier, submission, lost) {
857
1031
  const state = pendings.get(identifier);
@@ -861,6 +1035,8 @@ export function composeRuntime(options) {
861
1035
  return;
862
1036
  }
863
1037
  await local.submit(locator, identifier, submission, lost);
1038
+ if (fences.has(locator))
1039
+ return;
864
1040
  pendings.set(identifier, { ...state, submission, lost });
865
1041
  const replica = replicas.get(locator);
866
1042
  if (replica !== undefined)
@@ -871,6 +1047,8 @@ export function composeRuntime(options) {
871
1047
  if (!continuing())
872
1048
  throw new Error('connection refused');
873
1049
  await local.keepUpload(locator, identifier, upload);
1050
+ if (fences.has(locator))
1051
+ throw runtimeError('room-not-held', `No Local Replica for ${locator}`);
874
1052
  pendings.set(identifier, { ...pendings.get(identifier), upload });
875
1053
  if (!continuing())
876
1054
  throw new Error('connection refused');
@@ -886,6 +1064,7 @@ export function composeRuntime(options) {
886
1064
  }
887
1065
  if (closed || authorization === undefined || refusal !== null)
888
1066
  return UNCERTAIN;
1067
+ const since = fencings;
889
1068
  const transfer = ['transfer', 'history', locator, path];
890
1069
  if (query.revision !== undefined)
891
1070
  transfer.push(query.revision);
@@ -900,6 +1079,8 @@ export function composeRuntime(options) {
900
1079
  ...(query.pageToken === undefined ? {} : { pageToken: query.pageToken }),
901
1080
  });
902
1081
  journal.record(`outcome history ${result.kind}`);
1082
+ if (fencedSince(locator, since))
1083
+ return failure({ kind: 'room-not-readable', locator });
903
1084
  if (result.kind === 'scoped-refusal') {
904
1085
  const room = replicas.get(result.locator)?.confirmed.record;
905
1086
  if (room !== undefined)
@@ -914,6 +1095,100 @@ export function composeRuntime(options) {
914
1095
  return UNCERTAIN;
915
1096
  }
916
1097
  }
1098
+ function queuedBlob(locator, path, localId) {
1099
+ for (const batch of replicas.get(locator)?.queue ?? []) {
1100
+ const change = batch.changes[queuedFile(batch, path, localId)];
1101
+ if (change?.kind === 'binary')
1102
+ return change.blob;
1103
+ }
1104
+ return null;
1105
+ }
1106
+ function binaryRoom(request) {
1107
+ if (request.kind === 'path')
1108
+ return request.locator;
1109
+ const scope = options.adapter.referrerScope(request.referrer);
1110
+ return scope?.kind === 'room' ? scope.locator : null;
1111
+ }
1112
+ async function readBinary(request) {
1113
+ if (closed || refusal !== null)
1114
+ return UNCERTAIN;
1115
+ try {
1116
+ await ensureAuthorization();
1117
+ }
1118
+ catch {
1119
+ return UNCERTAIN;
1120
+ }
1121
+ if (closed || authorization === undefined || refusal !== null)
1122
+ return UNCERTAIN;
1123
+ const since = fencings;
1124
+ journal.record(request.kind === 'path'
1125
+ ? `transfer binary ${request.locator} ${request.path}`
1126
+ : `transfer binary ${request.fileRef}`);
1127
+ try {
1128
+ const result = await options.adapter.binary(authorization, request);
1129
+ journal.record(`outcome binary ${result.kind}`);
1130
+ const locator = binaryRoom(request);
1131
+ if (locator !== null && fencedSince(locator, since))
1132
+ return failure({ kind: 'room-not-readable', locator });
1133
+ if (result.kind === 'scoped-refusal') {
1134
+ const room = replicas.get(result.locator)?.confirmed.record;
1135
+ if (room !== undefined)
1136
+ await scoped(result, room, await openLocal());
1137
+ }
1138
+ if (result.kind === 'revoked-authorization')
1139
+ await refuse('revoked-authorization');
1140
+ return binaryAnswer(result);
1141
+ }
1142
+ catch {
1143
+ journal.record('outcome binary transport-uncertainty');
1144
+ return UNCERTAIN;
1145
+ }
1146
+ }
1147
+ async function readBinaryPath(locator, path, revision) {
1148
+ await ready;
1149
+ if (revision !== undefined)
1150
+ return readBinary({ kind: 'path', locator, path, revision });
1151
+ const visible = replicas.get(locator)?.visible;
1152
+ const held = visible?.binaries[path];
1153
+ if (visible === undefined || held === undefined)
1154
+ return absentBinary(path);
1155
+ if (held.state === 'pending') {
1156
+ const blob = queuedBlob(locator, path, held.localId);
1157
+ return blob === null ? absentBinary(path) : { kind: 'binary', blob };
1158
+ }
1159
+ const record = binaryRecord(path, held, visible.revisions[path]);
1160
+ if (record.state !== 'accepted')
1161
+ return absentBinary(path);
1162
+ const replica = replicas.get(locator);
1163
+ const queued = (replica?.queue ?? []).flatMap((batch) => batch.changes);
1164
+ const holds = (at) => {
1165
+ const kept = replica?.confirmed.record.binaries[at];
1166
+ return (kept?.state === 'accepted' &&
1167
+ kept.fileRef === record.fileRef &&
1168
+ replica?.confirmed.record.revisions[at] === record.revision);
1169
+ };
1170
+ let confirmed = path;
1171
+ for (let index = queued.length - 1; index >= 0 && !holds(confirmed); index -= 1) {
1172
+ const change = queued[index];
1173
+ if (change.kind === 'move' && change.to === confirmed)
1174
+ confirmed = change.path;
1175
+ }
1176
+ return readBinary({
1177
+ kind: 'path',
1178
+ locator,
1179
+ path: confirmed,
1180
+ revision: record.revision,
1181
+ fileRef: record.fileRef,
1182
+ });
1183
+ }
1184
+ async function readBinaryReference(reference) {
1185
+ await ready;
1186
+ return readBinary({
1187
+ kind: 'reference',
1188
+ fileRef: reference.fileRef,
1189
+ referrer: reference.referrer,
1190
+ });
1191
+ }
917
1192
  function declaredAgents() {
918
1193
  for (const capability of capabilities) {
919
1194
  if (capability.conversationAgents !== undefined)
@@ -978,11 +1253,11 @@ export function composeRuntime(options) {
978
1253
  options.adapter.reached(locator, written, roomVersion) &&
979
1254
  !options.adapter.reached(locator, roomVersion, written));
980
1255
  }
981
- async function pullRefresh(authorized, confirmed) {
982
- if (!continuing())
1256
+ async function pullRefresh(authorized, confirmed, live) {
1257
+ if (!live())
983
1258
  return unconfirmed;
984
1259
  try {
985
- const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => remodel(confirmed.record.locator, models, held), continuing);
1260
+ const result = await refreshRoom(options.adapter, authorized, confirmed, (models, held) => (live() ? remodel(confirmed.record.locator, models, held) : held), live);
986
1261
  const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
987
1262
  journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
988
1263
  return result;
@@ -992,33 +1267,39 @@ export function composeRuntime(options) {
992
1267
  throw error;
993
1268
  }
994
1269
  }
995
- function staging(local) {
1270
+ function staging(local, live) {
996
1271
  return {
997
1272
  held: (locator) => transfers.get(locator) ?? null,
998
1273
  staged: (record) => local.staged(record),
999
1274
  stage: (record, page) => exclusive(async () => {
1000
- if (!continuing())
1275
+ if (!live())
1001
1276
  return false;
1002
1277
  await local.stage(record, page);
1003
1278
  transfers.set(record.locator, record);
1004
1279
  return true;
1005
1280
  }),
1006
1281
  abandon: (locator) => exclusive(async () => {
1007
- if (!continuing())
1282
+ if (!live())
1008
1283
  return;
1009
1284
  await local.abandon(locator);
1010
1285
  transfers.delete(locator);
1011
1286
  }),
1012
1287
  };
1013
1288
  }
1014
- async function pullBootstrap(authorized, room, local) {
1015
- if (!continuing())
1289
+ function reusable(room) {
1290
+ const record = replicas.get(room.locator)?.confirmed.record;
1291
+ if (record === undefined || record.replica.selection !== room.replica.selection)
1292
+ return null;
1293
+ return { documents: record.documents, revisions: record.revisions };
1294
+ }
1295
+ async function pullBootstrap(authorized, room, local, live) {
1296
+ if (!live())
1016
1297
  return unconfirmed;
1017
1298
  try {
1018
1299
  const result = await bootstrapRoom(options.adapter, authorized, room, {
1019
1300
  collection: modelOf(room.locator),
1020
1301
  selection: modelOf(room.locator, 'typed-selection'),
1021
- }, (models, held) => remodel(room.locator, models, held), continuing, staging(local));
1302
+ }, (models, held) => (live() ? remodel(room.locator, models, held) : held), live, staging(local, live), reusable(room));
1022
1303
  const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
1023
1304
  journal.record(`transfer bootstrap ${room.locator} ${detail}`);
1024
1305
  return result;
@@ -1028,11 +1309,11 @@ export function composeRuntime(options) {
1028
1309
  throw error;
1029
1310
  }
1030
1311
  }
1031
- async function advance(room, local, authorized) {
1312
+ async function advance(room, local, authorized, live) {
1032
1313
  const held = replicas.get(room.locator);
1033
1314
  if (held !== undefined) {
1034
- const refreshed = await pullRefresh(authorized, held.confirmed);
1035
- if (!continuing())
1315
+ const refreshed = await pullRefresh(authorized, held.confirmed, live);
1316
+ if (!live())
1036
1317
  return unconfirmed;
1037
1318
  if (refreshed.kind === 'still-current') {
1038
1319
  stale.delete(room.locator);
@@ -1046,24 +1327,28 @@ export function composeRuntime(options) {
1046
1327
  return null;
1047
1328
  }
1048
1329
  if (refreshed.kind === 'advanced') {
1049
- if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }))) {
1330
+ if (!(await publish(local, room, refreshed.confirmed, { base: held.confirmed }, live))) {
1050
1331
  const now = replicas.get(room.locator)?.confirmed.record.roomVersion;
1051
1332
  const reached = now !== undefined &&
1052
1333
  options.adapter.reached(room.locator, now, refreshed.confirmed.record.roomVersion);
1053
- return reached ? null : advance(room, local, authorized);
1334
+ return reached ? null : advance(room, local, authorized, live);
1054
1335
  }
1055
1336
  await retainModel(local, room, 'collection', modelOf(room.locator));
1056
1337
  return null;
1057
1338
  }
1058
1339
  if (refreshed.kind !== 'beyond-horizon')
1059
1340
  return refreshed;
1341
+ if (opened !== null) {
1342
+ owed.add(room.locator);
1343
+ return unconfirmed;
1344
+ }
1060
1345
  }
1061
- const bootstrapped = await pullBootstrap(authorized, room, local);
1062
- if (!continuing())
1346
+ const bootstrapped = await pullBootstrap(authorized, room, local, live);
1347
+ if (!live())
1063
1348
  return unconfirmed;
1064
1349
  if (bootstrapped.kind !== 'staged')
1065
1350
  return bootstrapped;
1066
- await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
1351
+ await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored }, live);
1067
1352
  return null;
1068
1353
  }
1069
1354
  async function withhold(withheld, room, local, progress) {
@@ -1137,13 +1422,13 @@ export function composeRuntime(options) {
1137
1422
  logAccepted(room.locator, submitted, accepted);
1138
1423
  const applied = options.adapter.reached(room.locator, held, accepted.roomVersion)
1139
1424
  ? { kind: 'staged', confirmed: current.confirmed }
1140
- : applyReturned(current.confirmed, accepted.documents, accepted.revisions, accepted.changes, accepted.removals, accepted.attachments, options.adapter.follows(room.locator, held, accepted.roomVersion)
1425
+ : applyReturned(current.confirmed, submitted, accepted, options.adapter.follows(room.locator, held, accepted.roomVersion)
1141
1426
  ? accepted.roomVersion
1142
1427
  : held, accepted.roomVersion);
1143
1428
  if (applied.kind !== 'staged') {
1144
1429
  const state = pendings.get(submitted.id);
1145
1430
  await local.accept(room.locator, submitted.id, accepted.roomVersion);
1146
- if (state !== undefined)
1431
+ if (state !== undefined && !fences.has(room.locator))
1147
1432
  pendings.set(submitted.id, { ...state, accepted: accepted.roomVersion });
1148
1433
  emitQueue(room.locator);
1149
1434
  return applied;
@@ -1192,7 +1477,7 @@ export function composeRuntime(options) {
1192
1477
  let result;
1193
1478
  journal.record(`submission ${room.locator} ${head.id}`);
1194
1479
  await exclusive(() => mark(local, room.locator, head.id, { state: 'submitted' }, lost));
1195
- if (discoveryPaused)
1480
+ if (discoveryPaused || fences.has(room.locator))
1196
1481
  return;
1197
1482
  if (writesHeld()) {
1198
1483
  await exclusive(() => mark(local, room.locator, head.id, submission, earlier));
@@ -1255,7 +1540,7 @@ export function composeRuntime(options) {
1255
1540
  result.kind === 'expired' ||
1256
1541
  result.kind === 'retryable-refusal') {
1257
1542
  if (result.kind === 'expired') {
1258
- const withheld = await advance(room, local, authorized);
1543
+ const withheld = await advance(room, local, authorized, continuing);
1259
1544
  if (withheld !== null)
1260
1545
  return withhold(withheld, room, local, progress);
1261
1546
  }
@@ -1293,7 +1578,7 @@ export function composeRuntime(options) {
1293
1578
  });
1294
1579
  if (result.kind === 'expired')
1295
1580
  continue;
1296
- const withheld = await advance(room, local, authorized);
1581
+ const withheld = await advance(room, local, authorized, continuing);
1297
1582
  if (withheld !== null)
1298
1583
  return withhold(withheld, room, local, progress);
1299
1584
  continue;
@@ -1310,9 +1595,9 @@ export function composeRuntime(options) {
1310
1595
  const awaited = new Promise((resolve) => {
1311
1596
  release = resolve;
1312
1597
  });
1313
- answering.set(locator, awaited);
1598
+ answering.set(locator, { generation, answered: awaited });
1314
1599
  return () => {
1315
- if (answering.get(locator) === awaited)
1600
+ if (answering.get(locator)?.answered === awaited)
1316
1601
  answering.delete(locator);
1317
1602
  release();
1318
1603
  };
@@ -1356,11 +1641,7 @@ export function composeRuntime(options) {
1356
1641
  const read = reclassified.kind === 'deferred'
1357
1642
  ? { kind: 'staged', confirmed: confirmedOf(record, held.confirmed) }
1358
1643
  : reclassified;
1359
- await local.write({
1360
- next: record,
1361
- previous: held.confirmed.record,
1362
- queue: held.queue,
1363
- });
1644
+ await local.write({ next: record, previous: held.confirmed.record });
1364
1645
  if (read.kind === 'staged') {
1365
1646
  replicas.set(room.locator, replicaOf(read.confirmed, held.queue, held));
1366
1647
  if (declarationRefusals.delete(room.locator)) {
@@ -1428,13 +1709,14 @@ export function composeRuntime(options) {
1428
1709
  roomVersion: record.roomVersion,
1429
1710
  documents: record.documents,
1430
1711
  revisions: record.revisions,
1431
- attachments: record.attachments,
1432
1712
  },
1433
1713
  ];
1434
1714
  });
1435
1715
  }
1436
- async function synchronize(listed, local, progress, attempt) {
1716
+ async function synchronize(listed, local, progress, attempt, live) {
1437
1717
  const prepared = await Promise.allSettled(listed.map((room) => prepareRoom(room, local, progress)));
1718
+ if (!live())
1719
+ return;
1438
1720
  const ready = listed.filter((_, index) => {
1439
1721
  const outcome = prepared[index];
1440
1722
  return outcome?.status === 'fulfilled' && outcome.value;
@@ -1443,9 +1725,10 @@ export function composeRuntime(options) {
1443
1725
  options.adapter.roomStream?.hold(heldRooms(ready));
1444
1726
  handed = true;
1445
1727
  }
1728
+ discovering?.release();
1446
1729
  const loaded = await Promise.allSettled(ready
1447
1730
  .filter((room) => attempt === 'unstreamed' || !replicas.has(room.locator))
1448
- .map((room) => loadRoom(room, local, progress)));
1731
+ .map((room) => roomTask(room.locator, (held) => loadRoom(room, local, progress, held))));
1449
1732
  const failed = [...prepared, ...loaded].find((room) => room.status === 'rejected');
1450
1733
  if (failed !== undefined)
1451
1734
  throw failed.reason;
@@ -1474,14 +1757,14 @@ export function composeRuntime(options) {
1474
1757
  }
1475
1758
  return true;
1476
1759
  }
1477
- async function caughtUp(room, local, progress) {
1760
+ async function caughtUp(room, local, progress, live) {
1478
1761
  const held = replicas.get(room.locator)?.confirmed.record.roomVersion;
1479
- if (held === undefined || authorization === undefined)
1762
+ if (held === undefined || authorization === undefined || !live())
1480
1763
  return;
1481
1764
  const behind = [announced.get(room.locator), room.roomVersion].some((later) => later !== undefined && !options.adapter.reached(room.locator, held, later));
1482
1765
  if (!behind)
1483
1766
  return;
1484
- const withheld = await advance(room, local, authorization);
1767
+ const withheld = await advance(room, local, authorization, live);
1485
1768
  if (withheld !== null)
1486
1769
  await withhold(withheld, room, local, progress);
1487
1770
  }
@@ -1491,39 +1774,41 @@ export function composeRuntime(options) {
1491
1774
  return model.status === 'unavailable' && model.reason === 'revoked';
1492
1775
  });
1493
1776
  }
1494
- async function loadRoom(room, local, progress) {
1495
- if (discoveryPaused || authorization === undefined)
1777
+ async function loadRoom(room, local, progress, live) {
1778
+ if (!live() || authorization === undefined)
1496
1779
  return;
1497
1780
  if (replicas.has(room.locator) && revoked(room.locator)) {
1498
- await reload(room, local, progress);
1781
+ await reload(room, local, progress, live);
1499
1782
  return;
1500
1783
  }
1501
- const withheld = await advance(room, local, authorization);
1784
+ const withheld = await advance(room, local, authorization, live);
1502
1785
  if (withheld !== null) {
1503
1786
  await withhold(withheld, room, local, progress);
1504
1787
  return;
1505
1788
  }
1506
- await caughtUp(room, local, progress);
1789
+ await caughtUp(room, local, progress, live);
1507
1790
  }
1508
- async function reload(room, local, progress) {
1509
- if (discoveryPaused || authorization === undefined)
1791
+ async function reload(room, local, progress, live) {
1792
+ if (!live() || authorization === undefined)
1510
1793
  return;
1511
- const bootstrapped = await pullBootstrap(authorization, room, local);
1512
- if (!continuing())
1794
+ const bootstrapped = await pullBootstrap(authorization, room, local, live);
1795
+ if (!live())
1513
1796
  return;
1514
1797
  if (bootstrapped.kind !== 'staged') {
1515
1798
  await withhold(bootstrapped, room, local, progress);
1516
1799
  return;
1517
1800
  }
1518
- await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored });
1519
- await caughtUp(room, local, progress);
1801
+ await publish(local, room, bootstrapped.confirmed, { stored: bootstrapped.stored }, live);
1802
+ await caughtUp(room, local, progress, live);
1520
1803
  }
1521
- async function discoverRooms(local, progress, attempt) {
1804
+ async function discoverRooms(local, progress, attempt, live) {
1522
1805
  if (closed || authorization === undefined) {
1523
1806
  notifyReady();
1524
1807
  return;
1525
1808
  }
1526
1809
  moveTo(transferring(status));
1810
+ discoveries += 1;
1811
+ const started = discoveries;
1527
1812
  let result;
1528
1813
  try {
1529
1814
  result = await options.adapter.discover(authorization);
@@ -1531,7 +1816,8 @@ export function composeRuntime(options) {
1531
1816
  catch {
1532
1817
  result = { kind: 'transport-uncertainty' };
1533
1818
  }
1534
- if (closed) {
1819
+ if (!live()) {
1820
+ progress.complete = false;
1535
1821
  notifyReady();
1536
1822
  return;
1537
1823
  }
@@ -1601,8 +1887,7 @@ export function composeRuntime(options) {
1601
1887
  member = signedIn;
1602
1888
  emitMember();
1603
1889
  }
1604
- await applyDiscovery(result.rooms, local);
1605
- await synchronize(result.rooms, local, progress, attempt);
1890
+ await synchronize(await applyDiscovery(result.rooms, started), local, progress, attempt, live);
1606
1891
  }
1607
1892
  async function ensureAuthorization() {
1608
1893
  if (authorization !== undefined)
@@ -1616,32 +1901,64 @@ export function composeRuntime(options) {
1616
1901
  throw error;
1617
1902
  }
1618
1903
  }
1619
- function task(work) {
1904
+ async function admittedRead(work) {
1905
+ if (roomReads.generation !== generation) {
1906
+ roomReads = { generation, free: ROOM_READS, waiting: [] };
1907
+ }
1908
+ const reads = roomReads;
1909
+ while (reads.free === 0)
1910
+ await new Promise((admit) => reads.waiting.push(admit));
1911
+ reads.free -= 1;
1912
+ try {
1913
+ await work();
1914
+ }
1915
+ finally {
1916
+ reads.free += 1;
1917
+ reads.waiting.shift()?.();
1918
+ }
1919
+ }
1920
+ function roomTask(locator, work) {
1921
+ return task(roomLane(locator), (live) => admittedRead(() => work(live)));
1922
+ }
1923
+ function task(lane, work) {
1620
1924
  if (stopping)
1621
- return chain;
1622
- chain = chain.then(work, work).catch(() => undefined);
1623
- return chain;
1925
+ return Promise.resolve();
1926
+ const held = lanes.get(lane);
1927
+ const current = held?.generation === generation ? held : { generation, chain: Promise.resolve() };
1928
+ const turn = current.generation;
1929
+ const live = () => turn === generation && continuing();
1930
+ const run = current.chain.then(() => work(live)).catch(() => undefined);
1931
+ current.chain = run;
1932
+ lanes.set(lane, current);
1933
+ working.add(run);
1934
+ void run.finally(() => working.delete(run));
1935
+ return run;
1624
1936
  }
1625
1937
  function sync(attempt) {
1626
- return task(async () => {
1627
- if (closed || stopping || discoveryPaused) {
1938
+ return task(OPENING_LANE, async (live) => {
1939
+ if (!live()) {
1628
1940
  notifyReady();
1629
1941
  return;
1630
1942
  }
1631
1943
  const progress = { complete: true, reachable: true };
1632
- underway = attempt === 'opened' ? progress : null;
1944
+ const gate = discover();
1945
+ if (attempt === 'opened')
1946
+ opened = generation;
1947
+ underway = attempt === 'opened' ? { generation, progress } : null;
1948
+ if (attempt === 'opened')
1949
+ streamed = progress;
1633
1950
  complete = false;
1634
1951
  recorded = false;
1635
1952
  moveTo(transferring(status));
1636
1953
  handed = false;
1637
1954
  try {
1638
1955
  const local = await openLocal();
1639
- if (closed)
1956
+ if (!live())
1640
1957
  return;
1641
1958
  await ensureAuthorization();
1642
- if (closed)
1959
+ if (!live())
1643
1960
  return;
1644
- await discoverRooms(local, progress, attempt);
1961
+ await discoverRooms(local, progress, attempt, live);
1645
1962
  }
1646
1963
  catch {
1647
1964
  progress.complete = false;
@@ -1649,17 +1966,21 @@ export function composeRuntime(options) {
1649
1966
  progress.reachable = false;
1650
1967
  }
1651
1968
  finally {
1969
+ gate.release();
1652
1970
  notifyReady();
1653
1971
  }
1972
+ if (!live())
1973
+ return;
1654
1974
  if (attempt !== 'opened') {
1655
- await finishOpening(progress);
1975
+ await finishOpening(progress, live);
1656
1976
  return;
1657
1977
  }
1658
1978
  if (handed || closed || progress.reachable)
1659
1979
  return;
1660
1980
  underway = null;
1981
+ opened = null;
1661
1982
  journal.record('connection reopen');
1662
- await rest(progress);
1983
+ await rest(progress, live);
1663
1984
  options.adapter.roomStream?.reopen();
1664
1985
  });
1665
1986
  }
@@ -1670,29 +1991,48 @@ export function composeRuntime(options) {
1670
1991
  void refuse('revoked-authorization');
1671
1992
  return;
1672
1993
  }
1673
- if (opened || status.providerUnreachable)
1674
- return;
1675
- moveTo({ ...status, providerUnreachable: true });
1994
+ complete = false;
1995
+ if (streamed !== null)
1996
+ streamed.complete = false;
1997
+ moveTo({
1998
+ ...status,
1999
+ state: status.state === 'synced' ? 'syncing' : status.state,
2000
+ providerUnreachable: status.providerUnreachable || !opened,
2001
+ });
1676
2002
  }
1677
2003
  function applyOpening(expired) {
1678
- return task(async () => {
1679
- const progress = underway;
2004
+ const progress = underway?.generation === generation ? underway.progress : null;
2005
+ const cycle = progress ?? { complete: true, reachable: true };
2006
+ const replacing = reloadNow((room) => expired.includes(room.locator), cycle);
2007
+ return task(OPENING_LANE, async (live) => {
1680
2008
  underway = null;
1681
- if (closed || stopping)
2009
+ if (!live())
1682
2010
  return;
1683
2011
  if (progress === null) {
1684
- await reloadExpired(expired);
2012
+ await replacing;
2013
+ await rest(cycle, live);
1685
2014
  return;
1686
2015
  }
2016
+ await roomsCarried(expired);
2017
+ if (!live())
2018
+ return;
2019
+ if (opened === generation)
2020
+ opened = null;
2021
+ const replaced = new Set(owed);
2022
+ owed.clear();
1687
2023
  try {
1688
2024
  const local = await openLocal();
1689
2025
  const held = [...discovered.values()].filter((room) => replicas.has(room.locator));
1690
- const reloading = held.filter((room) => expired.includes(room.locator) || revoked(room.locator));
1691
- const reloaded = await Promise.allSettled(reloading.map((room) => reload(room, local, progress)));
2026
+ const reloading = held.filter((room) => !expired.includes(room.locator) &&
2027
+ (replaced.has(room.locator) || revoked(room.locator)));
2028
+ const reloads = Promise.allSettled(reloading.map((room) => roomTask(room.locator, (current) => reload(room, local, progress, current))));
2029
+ const reloaded = [...(await reloads), ...(await replacing)];
1692
2030
  if (progress.reachable) {
1693
- for (const room of held)
1694
- if (!reloading.includes(room))
2031
+ for (const room of held) {
2032
+ if (!expired.includes(room.locator) && !reloading.includes(room)) {
1695
2033
  await confirmModels(room, local);
2034
+ }
2035
+ }
1696
2036
  }
1697
2037
  const failed = reloaded.find((outcome) => outcome.status === 'rejected');
1698
2038
  if (failed !== undefined)
@@ -1702,21 +2042,21 @@ export function composeRuntime(options) {
1702
2042
  progress.complete = false;
1703
2043
  progress.reachable = false;
1704
2044
  }
1705
- await finishOpening(progress);
2045
+ if (live())
2046
+ await finishOpening(progress, live);
1706
2047
  });
1707
2048
  }
1708
- async function reloadExpired(expired) {
1709
- const progress = { complete: true, reachable: true };
2049
+ async function reloadNow(replaced, progress) {
1710
2050
  try {
1711
2051
  const local = await openLocal();
1712
- await Promise.allSettled([...discovered.values()]
1713
- .filter((room) => replicas.has(room.locator) && expired.includes(room.locator))
1714
- .map((room) => reload(room, local, progress)));
2052
+ return await Promise.allSettled([...discovered.values()]
2053
+ .filter((room) => replicas.has(room.locator) && replaced(room))
2054
+ .map((room) => roomTask(room.locator, (live) => reload(room, local, progress, live))));
1715
2055
  }
1716
2056
  catch {
1717
2057
  progress.reachable = false;
2058
+ return [];
1718
2059
  }
1719
- await rest(progress);
1720
2060
  }
1721
2061
  async function confirmModels(room, local) {
1722
2062
  for (const attached of ['collection', 'typed-selection']) {
@@ -1768,9 +2108,9 @@ export function composeRuntime(options) {
1768
2108
  submitLater(locator);
1769
2109
  }
1770
2110
  }
1771
- async function finishOpening(progress) {
2111
+ async function finishOpening(progress, live) {
1772
2112
  try {
1773
- if (closed || discoveryPaused) {
2113
+ if (!live()) {
1774
2114
  progress.complete = false;
1775
2115
  return;
1776
2116
  }
@@ -1780,6 +2120,8 @@ export function composeRuntime(options) {
1780
2120
  conversations.reconnected();
1781
2121
  const reading = { complete: true, reachable: true };
1782
2122
  await conversations.catchUp(reading);
2123
+ if (progress.reachable && reading.reachable)
2124
+ await conversations.resend(reading);
1783
2125
  if (!reading.reachable)
1784
2126
  progress.reachable = false;
1785
2127
  }
@@ -1787,12 +2129,14 @@ export function composeRuntime(options) {
1787
2129
  progress.complete = false;
1788
2130
  }
1789
2131
  finally {
1790
- complete = progress.complete;
1791
- await rest(progress);
2132
+ if (live()) {
2133
+ complete = progress.complete;
2134
+ await rest(progress, live);
2135
+ }
1792
2136
  }
1793
2137
  }
1794
- async function rest(progress) {
1795
- if (closed)
2138
+ async function rest(progress, live = () => !closed) {
2139
+ if (!live())
1796
2140
  return;
1797
2141
  let lastCompleted = status.lastCompleted;
1798
2142
  let synced = complete && stale.size === 0 && !pending();
@@ -1800,6 +2144,8 @@ export function composeRuntime(options) {
1800
2144
  const completedAt = new Date().toISOString();
1801
2145
  try {
1802
2146
  await (await openLocal()).complete(completedAt);
2147
+ if (!live())
2148
+ return;
1803
2149
  recorded = true;
1804
2150
  lastCompleted = completedAt;
1805
2151
  }
@@ -1808,17 +2154,29 @@ export function composeRuntime(options) {
1808
2154
  }
1809
2155
  }
1810
2156
  moveTo({
1811
- state: pending() ? 'pending' : synced ? 'synced' : 'syncing',
2157
+ state: pending() ? 'pending' : synced && complete ? 'synced' : 'syncing',
1812
2158
  lastCompleted,
1813
2159
  providerUnreachable: !progress.reachable,
1814
2160
  ...attention(),
1815
2161
  });
1816
2162
  }
2163
+ function discover() {
2164
+ let release = () => { };
2165
+ const done = new Promise((resolve) => {
2166
+ release = resolve;
2167
+ });
2168
+ discovering = { generation, done, release };
2169
+ return discovering;
2170
+ }
1817
2171
  function applyRoom(locator, roomVersion) {
1818
2172
  announced.set(locator, roomVersion);
1819
- return task(async () => {
1820
- await answering.get(locator);
1821
- if (closed || stopping || discoveryPaused || authorization === undefined)
2173
+ return task(roomLane(locator), async (live) => {
2174
+ if (discovering?.generation === generation)
2175
+ await discovering.done;
2176
+ const saving = answering.get(locator);
2177
+ if (saving?.generation === generation)
2178
+ await saving.answered;
2179
+ if (!live() || authorization === undefined)
1822
2180
  return;
1823
2181
  const room = discovered.get(locator);
1824
2182
  const held = replicas.get(locator);
@@ -1829,25 +2187,41 @@ export function composeRuntime(options) {
1829
2187
  options.adapter.reached(locator, held.confirmed.record.roomVersion, roomVersion))) {
1830
2188
  return;
1831
2189
  }
1832
- const during = underway;
2190
+ const during = underway?.generation === generation ? underway.progress : null;
1833
2191
  const progress = during ?? { complete: true, reachable: true };
1834
2192
  if (during === null)
1835
2193
  moveTo(transferring(status));
1836
2194
  try {
1837
2195
  const local = await openLocal();
1838
- const withheld = await advance(room, local, authorization);
1839
- if (withheld !== null)
1840
- await withhold(withheld, room, local, progress);
2196
+ await admittedRead(async () => {
2197
+ if (!live() || authorization === undefined)
2198
+ return;
2199
+ const withheld = await advance(room, local, authorization, live);
2200
+ if (withheld !== null)
2201
+ await withhold(withheld, room, local, progress);
2202
+ });
1841
2203
  }
1842
2204
  catch {
1843
2205
  stale.add(locator);
1844
2206
  }
1845
2207
  finally {
1846
2208
  if (during === null)
1847
- await rest(progress);
2209
+ await rest(progress, live);
1848
2210
  }
1849
2211
  });
1850
2212
  }
2213
+ function roomsCarried(replacing) {
2214
+ const replaced = new Set(replacing.map(roomLane));
2215
+ return Promise.all([...lanes]
2216
+ .filter(([lane, held]) => lane !== OPENING_LANE && !replaced.has(lane) && held.generation === generation)
2217
+ .map(([, held]) => held.chain));
2218
+ }
2219
+ const receipts = {
2220
+ heads: async () => (await openLocal()).receivedHeads(),
2221
+ parts: async (key, count) => (await openLocal()).receivedParts(key, count),
2222
+ keep: async (key, head, part) => (await openLocal()).keepReceived(key, head, part),
2223
+ drop: async (keys) => (await openLocal()).dropReceived(keys),
2224
+ };
1851
2225
  function observeStream() {
1852
2226
  const stream = options.adapter.roomStream;
1853
2227
  if (stream === undefined || withdrawStream !== undefined)
@@ -1857,6 +2231,7 @@ export function composeRuntime(options) {
1857
2231
  return;
1858
2232
  if (event.kind === 'opened') {
1859
2233
  socketOpen = true;
2234
+ generation += 1;
1860
2235
  void sync('opened');
1861
2236
  }
1862
2237
  else if (event.kind === 'closed')
@@ -1865,7 +2240,7 @@ export function composeRuntime(options) {
1865
2240
  void applyOpening(event.expired);
1866
2241
  else
1867
2242
  void applyRoom(event.locator, event.roomVersion);
1868
- });
2243
+ }, receipts);
1869
2244
  }
1870
2245
  function writesHeld() {
1871
2246
  return serviceMode === 'write-halted' || haltedRefusal;
@@ -1903,34 +2278,30 @@ export function composeRuntime(options) {
1903
2278
  });
1904
2279
  }
1905
2280
  function scheduleCurrency() {
1906
- if (currencyQueued || closed || stopping)
2281
+ if (currencyQueued === generation || closed || stopping)
1907
2282
  return;
1908
- currencyQueued = true;
1909
- void task(runCurrency);
2283
+ currencyQueued = generation;
2284
+ void task(CONVERSATION_LANE, runCurrency);
1910
2285
  }
1911
- async function runCurrency() {
1912
- currencyQueued = false;
1913
- if (closed || stopping || discoveryPaused)
2286
+ async function runCurrency(live) {
2287
+ if (currencyQueued === generation)
2288
+ currencyQueued = null;
2289
+ if (!live())
1914
2290
  return;
1915
2291
  await conversations.reconcile();
1916
2292
  if (conversations.current()) {
1917
2293
  show();
1918
2294
  return;
1919
2295
  }
1920
- const resting = status;
1921
- const progress = { complete: false, reachable: !resting.providerUnreachable };
1922
- moveTo(transferring(resting));
2296
+ const progress = { complete: false, reachable: !status.providerUnreachable };
2297
+ moveTo(transferring(status));
1923
2298
  try {
1924
2299
  await conversations.catchUp(progress);
1925
2300
  }
1926
2301
  catch {
1927
2302
  progress.reachable = false;
1928
2303
  }
1929
- finally {
1930
- if (!closed) {
1931
- moveTo(settled(resting, progress, resting.lastCompleted, pending(), attention()));
1932
- }
1933
- }
2304
+ await rest(progress, live);
1934
2305
  }
1935
2306
  function close() {
1936
2307
  if (closed)
@@ -1961,7 +2332,7 @@ export function composeRuntime(options) {
1961
2332
  async function stop() {
1962
2333
  stopping = true;
1963
2334
  stopTimer();
1964
- await chain.catch(() => undefined);
2335
+ await Promise.all(working);
1965
2336
  await Promise.all([...submitting.values()].map((run) => run.done));
1966
2337
  await mutations;
1967
2338
  close();
@@ -2019,6 +2390,8 @@ export function composeRuntime(options) {
2019
2390
  }
2020
2391
  if (topic === 'conversations')
2021
2392
  return conversations.watchList(deliver);
2393
+ if (topic === 'agents')
2394
+ return conversations.watchAgents(deliver);
2022
2395
  if (topic === 'presentation') {
2023
2396
  const listener = {
2024
2397
  app: input[0],
@@ -2132,11 +2505,14 @@ export function composeRuntime(options) {
2132
2505
  if ((current?.revision ?? null) !== revision) {
2133
2506
  return staleWrite(current === undefined ? null : roomDraftOf(current));
2134
2507
  }
2508
+ const file = parts.file === undefined
2509
+ ? undefined
2510
+ : await heldFile(parts.file.blob, binaryBase(replicas.get(room)?.visible, path));
2135
2511
  const record = {
2136
2512
  locator: room,
2137
2513
  path,
2138
2514
  revision: roomDraftRevision(),
2139
- parts: held(current?.parts, parts),
2515
+ parts: held(current?.parts, parts, file),
2140
2516
  returned: current?.returned ?? null,
2141
2517
  };
2142
2518
  await local.keepRoomDraft(record);
@@ -2185,6 +2561,10 @@ export function composeRuntime(options) {
2185
2561
  history(locator, path, query) {
2186
2562
  return readHistory(locator, path, query);
2187
2563
  },
2564
+ binaryFile: {
2565
+ read: readBinaryPath,
2566
+ readReference: readBinaryReference,
2567
+ },
2188
2568
  call(name, input) {
2189
2569
  return invokeAction(name, input);
2190
2570
  },
@@ -2204,23 +2584,26 @@ export function composeRuntime(options) {
2204
2584
  if (refusal === null || !refusal.reauthorizationOffered)
2205
2585
  return;
2206
2586
  journal.record('connection reauthorize');
2207
- await chain.catch(() => undefined);
2587
+ await Promise.all([...lanes.values()]
2588
+ .filter((held) => held.generation === generation)
2589
+ .map((held) => held.chain));
2208
2590
  await mutations;
2209
2591
  authorization = undefined;
2210
2592
  discoveryPaused = false;
2211
2593
  if (options.adapter.roomStream === undefined)
2212
2594
  await sync('unstreamed');
2213
2595
  else
2214
- await task(ensureAuthorization);
2596
+ await task(OPENING_LANE, ensureAuthorization);
2215
2597
  },
2216
2598
  };
2217
2599
  const reachable = {
2218
2600
  md,
2219
2601
  watch,
2602
+ referrerScope: (referrer) => options.adapter.referrerScope(referrer),
2220
2603
  plan: planned,
2221
2604
  query: (plan) => rowsQuery(plan),
2222
2605
  watchPlan,
2223
2606
  search: searched,
2224
2607
  };
2225
- return { md, stop, watch, within: (reach) => narrowed(reachable, reach) };
2608
+ return { md, stop, lose: loseRooms, watch, within: (reach) => narrowed(reachable, reach) };
2226
2609
  }