@agentshouse/mdruntime 0.1.0-alpha.3 → 0.1.0-alpha.4
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.
- package/dist/adapter.d.ts +21 -4
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/connection.d.ts +20 -4
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +446 -33
- package/dist/connection.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +5 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +15 -0
- package/dist/log.js.map +1 -0
- package/dist/memory.js +2 -1
- package/dist/memory.js.map +1 -1
- package/dist/outcome.d.ts +30 -0
- package/dist/outcome.d.ts.map +1 -0
- package/dist/outcome.js +13 -0
- package/dist/outcome.js.map +1 -0
- package/dist/replica.d.ts +1 -0
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +25 -0
- package/dist/replica.js.map +1 -1
- package/dist/status.d.ts +2 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +6 -4
- package/dist/status.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +6 -1
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -2,9 +2,11 @@ import { query as evaluate } from '@agentshouse/query';
|
|
|
2
2
|
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES } from './adapter.js';
|
|
3
3
|
import { bootstrapRoom } from './bootstrap.js';
|
|
4
4
|
import { MD_MODEL, PRESENTED } from './collection.js';
|
|
5
|
+
import { createLog } from './log.js';
|
|
6
|
+
import { queuedOutcome, refused } from './outcome.js';
|
|
5
7
|
import { refreshRoom } from './refresh.js';
|
|
6
8
|
import { buildBatch } from './save.js';
|
|
7
|
-
import { readConfirmed, replicaOf } from './replica.js';
|
|
9
|
+
import { applyReturned, readConfirmed, replicaOf } from './replica.js';
|
|
8
10
|
import { settled, same, saving, STARTING, transferring } from './status.js';
|
|
9
11
|
import { openReplicaStore } from './store.js';
|
|
10
12
|
import { unconfirmed } from './transfer.js';
|
|
@@ -17,7 +19,9 @@ export function createMdRuntime(options) {
|
|
|
17
19
|
let store;
|
|
18
20
|
const replicas = new Map();
|
|
19
21
|
const drafts = new Map();
|
|
22
|
+
const outcomes = new Map();
|
|
20
23
|
let limits = [];
|
|
24
|
+
let capabilities = [];
|
|
21
25
|
const rooms = new Map();
|
|
22
26
|
const unreadable = new Set();
|
|
23
27
|
let refusal = null;
|
|
@@ -34,6 +38,11 @@ export function createMdRuntime(options) {
|
|
|
34
38
|
const statusListeners = new Set();
|
|
35
39
|
const querySubscriptions = new Set();
|
|
36
40
|
const queueSubscriptions = new Set();
|
|
41
|
+
const journal = createLog();
|
|
42
|
+
let notifyReady = ()=>{};
|
|
43
|
+
const ready = new Promise((resolve)=>{
|
|
44
|
+
notifyReady = resolve;
|
|
45
|
+
});
|
|
37
46
|
function at(locator, path) {
|
|
38
47
|
return `${locator} ${path}`;
|
|
39
48
|
}
|
|
@@ -42,6 +51,29 @@ export function createMdRuntime(options) {
|
|
|
42
51
|
...replicas.values()
|
|
43
52
|
].some((replica)=>replica.queue.length > 0);
|
|
44
53
|
}
|
|
54
|
+
function attention() {
|
|
55
|
+
return [
|
|
56
|
+
...replicas.values()
|
|
57
|
+
].some((replica)=>{
|
|
58
|
+
const head = replica.queue[0];
|
|
59
|
+
return head !== undefined && refused(outcomes.get(head.id));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function storedOutcomes(batches) {
|
|
63
|
+
const stored = {};
|
|
64
|
+
for (const batch of batches){
|
|
65
|
+
const outcome = queuedOutcome(outcomes.get(batch.id));
|
|
66
|
+
if (outcome.kind !== 'pending') stored[batch.id] = outcome;
|
|
67
|
+
}
|
|
68
|
+
return stored;
|
|
69
|
+
}
|
|
70
|
+
async function persistQueue(local, locator, batches) {
|
|
71
|
+
await local.writeQueue({
|
|
72
|
+
locator,
|
|
73
|
+
batches,
|
|
74
|
+
outcomes: storedOutcomes(batches)
|
|
75
|
+
});
|
|
76
|
+
}
|
|
45
77
|
function listed() {
|
|
46
78
|
return [
|
|
47
79
|
...rooms.values()
|
|
@@ -84,13 +116,18 @@ export function createMdRuntime(options) {
|
|
|
84
116
|
subscription.notify(visible(subscription.query));
|
|
85
117
|
}
|
|
86
118
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
119
|
+
function entriesOf(locator) {
|
|
120
|
+
const replica = replicas.get(locator);
|
|
121
|
+
if (replica === undefined) return [];
|
|
122
|
+
return replica.queue.map((batch)=>Object.freeze({
|
|
123
|
+
batch,
|
|
124
|
+
outcome: queuedOutcome(outcomes.get(batch.id))
|
|
125
|
+
}));
|
|
89
126
|
}
|
|
90
127
|
function emitQueue(locator) {
|
|
91
|
-
const
|
|
128
|
+
const entries = entriesOf(locator);
|
|
92
129
|
for (const subscription of queueSubscriptions){
|
|
93
|
-
if (subscription.locator === locator) subscription.notify(
|
|
130
|
+
if (subscription.locator === locator) subscription.notify(entries);
|
|
94
131
|
}
|
|
95
132
|
}
|
|
96
133
|
function stopTimer() {
|
|
@@ -111,6 +148,7 @@ export function createMdRuntime(options) {
|
|
|
111
148
|
reason,
|
|
112
149
|
reauthorizationOffered: reason === 'revoked-authorization'
|
|
113
150
|
};
|
|
151
|
+
journal.record(`connection refused ${reason}`);
|
|
114
152
|
emitRefusal();
|
|
115
153
|
}
|
|
116
154
|
function list(room, synchronizationAvailable) {
|
|
@@ -154,6 +192,11 @@ export function createMdRuntime(options) {
|
|
|
154
192
|
queue.locator,
|
|
155
193
|
queue.batches
|
|
156
194
|
]));
|
|
195
|
+
for (const queue of queues){
|
|
196
|
+
for (const [id, outcome] of Object.entries(queue.outcomes)){
|
|
197
|
+
outcomes.set(id, outcome);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
157
200
|
for (const record of records){
|
|
158
201
|
const confirmed = readConfirmed(record);
|
|
159
202
|
if (confirmed === null) continue;
|
|
@@ -162,7 +205,8 @@ export function createMdRuntime(options) {
|
|
|
162
205
|
}
|
|
163
206
|
moveTo({
|
|
164
207
|
...status,
|
|
165
|
-
lastCompleted
|
|
208
|
+
lastCompleted,
|
|
209
|
+
attentionRequired: attention()
|
|
166
210
|
});
|
|
167
211
|
if (replicas.size > 0) {
|
|
168
212
|
emitRooms();
|
|
@@ -192,7 +236,7 @@ export function createMdRuntime(options) {
|
|
|
192
236
|
emitVisible();
|
|
193
237
|
});
|
|
194
238
|
}
|
|
195
|
-
async function write(locator, changes) {
|
|
239
|
+
async function write(locator, changes, replacing) {
|
|
196
240
|
const replica = replicas.get(locator);
|
|
197
241
|
const room = rooms.get(locator);
|
|
198
242
|
if (replica === undefined || room === undefined) {
|
|
@@ -210,32 +254,183 @@ export function createMdRuntime(options) {
|
|
|
210
254
|
outcome: 'refused',
|
|
211
255
|
...built.refusal
|
|
212
256
|
};
|
|
257
|
+
const head = replica.queue[0];
|
|
258
|
+
if (replacing !== undefined) {
|
|
259
|
+
if (head === undefined || head.id !== replacing || !refused(outcomes.get(head.id))) {
|
|
260
|
+
return {
|
|
261
|
+
outcome: 'refused',
|
|
262
|
+
rule: 'no-refused-head'
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
213
266
|
const local = await openLocal();
|
|
214
|
-
const batches = [
|
|
267
|
+
const batches = replacing === undefined ? [
|
|
215
268
|
...replica.queue,
|
|
216
269
|
built.batch
|
|
270
|
+
] : [
|
|
271
|
+
built.batch,
|
|
272
|
+
...replica.queue.slice(1)
|
|
217
273
|
];
|
|
274
|
+
if (replacing !== undefined) outcomes.delete(replacing);
|
|
218
275
|
const consumed = [
|
|
219
276
|
...new Set(changes.map((change)=>change.path))
|
|
220
277
|
];
|
|
221
278
|
await local.append({
|
|
222
279
|
locator,
|
|
223
|
-
batches
|
|
280
|
+
batches,
|
|
281
|
+
outcomes: storedOutcomes(batches)
|
|
224
282
|
}, consumed);
|
|
225
283
|
for (const path of consumed)drafts.delete(at(locator, path));
|
|
226
284
|
replicas.set(locator, replicaOf(replica.confirmed, batches));
|
|
227
285
|
emitQueue(locator);
|
|
228
286
|
emitVisible();
|
|
229
|
-
moveTo(
|
|
287
|
+
moveTo({
|
|
288
|
+
...saving(status),
|
|
289
|
+
attentionRequired: attention()
|
|
290
|
+
});
|
|
291
|
+
void enqueue();
|
|
230
292
|
return {
|
|
231
293
|
outcome: 'saved',
|
|
232
294
|
id: built.batch.id
|
|
233
295
|
};
|
|
234
296
|
}
|
|
297
|
+
async function discardHead(locator, identifier) {
|
|
298
|
+
const replica = replicas.get(locator);
|
|
299
|
+
const head = replica?.queue[0];
|
|
300
|
+
if (replica === undefined || head === undefined || head.id !== identifier || !refused(outcomes.get(head.id))) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const local = await openLocal();
|
|
304
|
+
const batches = replica.queue.slice(1);
|
|
305
|
+
outcomes.delete(identifier);
|
|
306
|
+
await persistQueue(local, locator, batches);
|
|
307
|
+
replicas.set(locator, replicaOf(replica.confirmed, batches));
|
|
308
|
+
emitQueue(locator);
|
|
309
|
+
emitVisible();
|
|
310
|
+
moveTo({
|
|
311
|
+
...status,
|
|
312
|
+
state: pending() ? 'pending' : status.state,
|
|
313
|
+
attentionRequired: attention()
|
|
314
|
+
});
|
|
315
|
+
void enqueue();
|
|
316
|
+
}
|
|
317
|
+
async function readHistory(locator, path, query = {}) {
|
|
318
|
+
if (closed || refusal !== null) return {
|
|
319
|
+
kind: 'transport-uncertainty'
|
|
320
|
+
};
|
|
321
|
+
try {
|
|
322
|
+
await ensureAuthorization();
|
|
323
|
+
} catch {
|
|
324
|
+
return {
|
|
325
|
+
kind: 'transport-uncertainty'
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (closed || authorization === undefined || refusal !== null) {
|
|
329
|
+
return {
|
|
330
|
+
kind: 'transport-uncertainty'
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const transfer = [
|
|
334
|
+
'transfer',
|
|
335
|
+
'history',
|
|
336
|
+
locator,
|
|
337
|
+
path
|
|
338
|
+
];
|
|
339
|
+
if (query.revision !== undefined) transfer.push(query.revision);
|
|
340
|
+
if (query.pageToken !== undefined) transfer.push(query.pageToken);
|
|
341
|
+
journal.record(transfer.join(' '));
|
|
342
|
+
try {
|
|
343
|
+
const result = await options.adapter.history(authorization, {
|
|
344
|
+
locator,
|
|
345
|
+
path,
|
|
346
|
+
...query.revision === undefined ? {} : {
|
|
347
|
+
revision: query.revision
|
|
348
|
+
},
|
|
349
|
+
...query.pageToken === undefined ? {} : {
|
|
350
|
+
pageToken: query.pageToken
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
journal.record(`outcome history ${result.kind}`);
|
|
354
|
+
if (result.kind === 'revoked-authorization') refuse('revoked-authorization');
|
|
355
|
+
return result;
|
|
356
|
+
} catch {
|
|
357
|
+
journal.record('outcome history transport-uncertainty');
|
|
358
|
+
return {
|
|
359
|
+
kind: 'transport-uncertainty'
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function declaredAction(name) {
|
|
364
|
+
for (const capability of capabilities){
|
|
365
|
+
for (const action of capability.actions ?? []){
|
|
366
|
+
if (action.name === name) return action;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
async function invokeAction(name, input) {
|
|
372
|
+
await ready;
|
|
373
|
+
if (closed || refusal !== null) return {
|
|
374
|
+
kind: 'transport-uncertainty'
|
|
375
|
+
};
|
|
376
|
+
if (declaredAction(name) === undefined) return {
|
|
377
|
+
kind: 'undeclared'
|
|
378
|
+
};
|
|
379
|
+
try {
|
|
380
|
+
await ensureAuthorization();
|
|
381
|
+
} catch {
|
|
382
|
+
return {
|
|
383
|
+
kind: 'transport-uncertainty'
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (closed || authorization === undefined || refusal !== null) {
|
|
387
|
+
return {
|
|
388
|
+
kind: 'transport-uncertainty'
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
journal.record(`transfer action ${name}`);
|
|
392
|
+
try {
|
|
393
|
+
const result = await options.adapter.action(authorization, {
|
|
394
|
+
name,
|
|
395
|
+
input
|
|
396
|
+
});
|
|
397
|
+
if (result.kind === 'revoked-authorization') refuse('revoked-authorization');
|
|
398
|
+
journal.record(`outcome action ${result.kind}`);
|
|
399
|
+
if (result.kind === 'rejected') journal.record(`refusal rejected ${result.text}`);
|
|
400
|
+
return result;
|
|
401
|
+
} catch {
|
|
402
|
+
journal.record('outcome action transport-uncertainty');
|
|
403
|
+
return {
|
|
404
|
+
kind: 'transport-uncertainty'
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function pullRefresh(authorized, confirmed) {
|
|
409
|
+
try {
|
|
410
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed);
|
|
411
|
+
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
412
|
+
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
413
|
+
return result;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
journal.record(`transfer refresh ${confirmed.record.locator} transport-uncertainty`);
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function pullBootstrap(authorized, room) {
|
|
420
|
+
try {
|
|
421
|
+
const result = await bootstrapRoom(options.adapter, authorized, room);
|
|
422
|
+
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
423
|
+
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
424
|
+
return result;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
journal.record(`transfer bootstrap ${room.locator} transport-uncertainty`);
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
235
430
|
async function advance(room, local, authorized) {
|
|
236
431
|
const held = replicas.get(room.locator);
|
|
237
432
|
if (held !== undefined) {
|
|
238
|
-
const refreshed = await
|
|
433
|
+
const refreshed = await pullRefresh(authorized, held.confirmed);
|
|
239
434
|
if (refreshed.kind === 'still-current') {
|
|
240
435
|
if (unreadable.delete(room.locator)) {
|
|
241
436
|
list(room, true);
|
|
@@ -250,62 +445,242 @@ export function createMdRuntime(options) {
|
|
|
250
445
|
}
|
|
251
446
|
if (refreshed.kind !== 'beyond-horizon') return refreshed;
|
|
252
447
|
}
|
|
253
|
-
const bootstrapped = await
|
|
448
|
+
const bootstrapped = await pullBootstrap(authorized, room);
|
|
254
449
|
if (bootstrapped.kind !== 'staged') return bootstrapped;
|
|
255
450
|
if (closed) return unconfirmed;
|
|
256
451
|
await publish(local, room, bootstrapped.confirmed);
|
|
257
452
|
return null;
|
|
258
453
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
454
|
+
function recordRefusal(result) {
|
|
455
|
+
if (result.kind === 'expired') return {
|
|
456
|
+
kind: 'expired'
|
|
457
|
+
};
|
|
458
|
+
if (result.kind === 'conflicted') {
|
|
459
|
+
return {
|
|
460
|
+
kind: 'conflicted',
|
|
461
|
+
roomVersion: result.roomVersion,
|
|
462
|
+
affected: result.affected,
|
|
463
|
+
text: result.text
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
kind: 'rejected',
|
|
468
|
+
affected: result.affected,
|
|
469
|
+
text: result.text
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
async function commitAccept(room, local, submitted, accepted, base, overlay) {
|
|
473
|
+
const current = replicas.get(room.locator);
|
|
474
|
+
if (current === undefined || !current.queue.some((batch)=>batch.id === submitted.id)) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
const remaining = current.queue.filter((batch)=>batch.id !== submitted.id);
|
|
478
|
+
const applied = overlay ? applyReturned(base, accepted.documents, accepted.revisions, accepted.removals, accepted.roomVersion) : base;
|
|
479
|
+
if (applied === null) return false;
|
|
480
|
+
await local.publish(applied.record);
|
|
481
|
+
await persistQueue(local, room.locator, remaining);
|
|
482
|
+
outcomes.set(submitted.id, {
|
|
483
|
+
kind: 'accepted',
|
|
484
|
+
roomVersion: accepted.roomVersion,
|
|
485
|
+
author: accepted.author,
|
|
486
|
+
time: accepted.time
|
|
487
|
+
});
|
|
488
|
+
unreadable.delete(room.locator);
|
|
489
|
+
replicas.set(room.locator, replicaOf(applied, remaining));
|
|
490
|
+
list(room, true);
|
|
491
|
+
emitRooms();
|
|
492
|
+
emitQueue(room.locator);
|
|
493
|
+
emitVisible();
|
|
494
|
+
journal.record(`outcome ${room.locator} ${submitted.id} accepted`);
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
async function submitRoom(room, local, authorized, progress) {
|
|
498
|
+
while(!closed && authorization !== undefined){
|
|
499
|
+
const replica = replicas.get(room.locator);
|
|
500
|
+
const head = replica?.queue[0];
|
|
501
|
+
if (replica === undefined || head === undefined || refused(outcomes.get(head.id))) {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
let result;
|
|
505
|
+
journal.record(`submission ${room.locator} ${head.id}`);
|
|
506
|
+
try {
|
|
507
|
+
result = await options.adapter.mutate(authorized, {
|
|
508
|
+
locator: room.locator,
|
|
509
|
+
changeBatch: head
|
|
510
|
+
});
|
|
511
|
+
} catch {
|
|
262
512
|
progress.complete = false;
|
|
263
|
-
|
|
513
|
+
progress.reachable = false;
|
|
514
|
+
return null;
|
|
264
515
|
}
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
if (withheld.kind === 'transport-uncertainty') {
|
|
516
|
+
if (closed) return null;
|
|
517
|
+
if (result.kind === 'transport-uncertainty') {
|
|
518
|
+
progress.complete = false;
|
|
269
519
|
progress.reachable = false;
|
|
270
|
-
|
|
520
|
+
return null;
|
|
271
521
|
}
|
|
272
|
-
if (
|
|
522
|
+
if (result.kind === 'room-not-readable') {
|
|
523
|
+
progress.complete = false;
|
|
273
524
|
unreadable.add(room.locator);
|
|
274
525
|
list(room, false);
|
|
275
526
|
emitRooms();
|
|
276
|
-
|
|
527
|
+
return null;
|
|
277
528
|
}
|
|
278
|
-
if (
|
|
529
|
+
if (result.kind === 'revoked-authorization') {
|
|
279
530
|
refuse('revoked-authorization');
|
|
531
|
+
return 'stop';
|
|
532
|
+
}
|
|
533
|
+
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
534
|
+
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}`);
|
|
535
|
+
if (result.kind !== 'expired') journal.record(`refusal ${result.kind} ${result.text}`);
|
|
536
|
+
await exclusive(async ()=>{
|
|
537
|
+
const current = replicas.get(room.locator);
|
|
538
|
+
if (current === undefined || !current.queue.some((batch)=>batch.id === head.id)) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
outcomes.set(head.id, recordRefusal(result));
|
|
542
|
+
await persistQueue(local, room.locator, current.queue);
|
|
543
|
+
emitQueue(room.locator);
|
|
544
|
+
moveTo({
|
|
545
|
+
...status,
|
|
546
|
+
attentionRequired: true
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
const from = replicas.get(room.locator)?.confirmed;
|
|
552
|
+
if (from === undefined) return null;
|
|
553
|
+
const pulled = await pullRefresh(authorized, from);
|
|
554
|
+
if (pulled.kind === 'revoked-authorization') {
|
|
555
|
+
refuse('revoked-authorization');
|
|
556
|
+
return 'stop';
|
|
557
|
+
}
|
|
558
|
+
if (pulled.kind === 'still-current') {
|
|
559
|
+
const committed = await exclusive(()=>commitAccept(room, local, head, result, from, true));
|
|
560
|
+
if (!committed) {
|
|
561
|
+
progress.complete = false;
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (pulled.kind === 'advanced') {
|
|
567
|
+
const overlay = pulled.confirmed.record.roomVersion === result.roomVersion;
|
|
568
|
+
const committed = await exclusive(()=>commitAccept(room, local, head, result, pulled.confirmed, overlay));
|
|
569
|
+
if (!committed) {
|
|
570
|
+
progress.complete = false;
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (pulled.kind === 'beyond-horizon') {
|
|
576
|
+
const bootstrapped = await pullBootstrap(authorized, room);
|
|
577
|
+
if (bootstrapped.kind === 'revoked-authorization') {
|
|
578
|
+
refuse('revoked-authorization');
|
|
579
|
+
return 'stop';
|
|
580
|
+
}
|
|
581
|
+
if (bootstrapped.kind === 'staged') {
|
|
582
|
+
const overlay = bootstrapped.confirmed.record.roomVersion === result.roomVersion;
|
|
583
|
+
const committed = await exclusive(()=>commitAccept(room, local, head, result, bootstrapped.confirmed, overlay));
|
|
584
|
+
if (!committed) {
|
|
585
|
+
progress.complete = false;
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
progress.complete = false;
|
|
591
|
+
if (bootstrapped.kind === 'transport-uncertainty') {
|
|
592
|
+
progress.reachable = false;
|
|
593
|
+
} else if (bootstrapped.kind === 'room-not-readable') {
|
|
594
|
+
unreadable.add(room.locator);
|
|
595
|
+
list(room, false);
|
|
596
|
+
emitRooms();
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
progress.complete = false;
|
|
601
|
+
if (pulled.kind === 'transport-uncertainty') {
|
|
602
|
+
progress.reachable = false;
|
|
603
|
+
} else if (pulled.kind === 'room-not-readable') {
|
|
604
|
+
unreadable.add(room.locator);
|
|
605
|
+
list(room, false);
|
|
606
|
+
emitRooms();
|
|
607
|
+
}
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
async function synchronize(discovered, local, progress) {
|
|
613
|
+
for (const room of discovered){
|
|
614
|
+
if (closed || authorization === undefined) {
|
|
615
|
+
progress.complete = false;
|
|
280
616
|
return;
|
|
281
617
|
}
|
|
618
|
+
const withheld = await advance(room, local, authorization);
|
|
619
|
+
if (withheld !== null) {
|
|
620
|
+
progress.complete = false;
|
|
621
|
+
if (withheld.kind === 'transport-uncertainty') {
|
|
622
|
+
progress.reachable = false;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (withheld.kind === 'room-not-readable') {
|
|
626
|
+
unreadable.add(room.locator);
|
|
627
|
+
list(room, false);
|
|
628
|
+
emitRooms();
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (withheld.kind === 'revoked-authorization') {
|
|
632
|
+
refuse('revoked-authorization');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (await submitRoom(room, local, authorization, progress) === 'stop') return;
|
|
282
638
|
}
|
|
283
639
|
}
|
|
284
640
|
async function cycle(local, progress) {
|
|
285
|
-
if (closed || authorization === undefined)
|
|
641
|
+
if (closed || authorization === undefined) {
|
|
642
|
+
notifyReady();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
286
645
|
moveTo(transferring(status));
|
|
287
|
-
|
|
288
|
-
|
|
646
|
+
let result;
|
|
647
|
+
try {
|
|
648
|
+
result = await options.adapter.discover(authorization);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
journal.record('connection discover transport-uncertainty');
|
|
651
|
+
notifyReady();
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
if (closed) {
|
|
655
|
+
notifyReady();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
journal.record(`connection discover ${result.kind}`);
|
|
289
659
|
if (result.kind === 'transport-uncertainty') {
|
|
290
660
|
progress.reachable = false;
|
|
661
|
+
notifyReady();
|
|
291
662
|
return;
|
|
292
663
|
}
|
|
293
664
|
if (result.kind === 'revoked-authorization') {
|
|
294
665
|
refuse('revoked-authorization');
|
|
666
|
+
notifyReady();
|
|
295
667
|
return;
|
|
296
668
|
}
|
|
297
669
|
if (!result.supportedVersions.includes(ROOM_CONTRACT_VERSION)) {
|
|
298
670
|
refuse('no-matching-version');
|
|
671
|
+
notifyReady();
|
|
299
672
|
return;
|
|
300
673
|
}
|
|
301
674
|
if (result.mdModel.identity !== MD_MODEL.identity || result.mdModel.version !== MD_MODEL.version) {
|
|
302
675
|
refuse('md-model-version-mismatch');
|
|
676
|
+
notifyReady();
|
|
303
677
|
return;
|
|
304
678
|
}
|
|
305
679
|
const declared = new Set(result.capabilities.map((capability)=>capability.name));
|
|
306
680
|
for (const required of options.requiredGuarantees){
|
|
307
681
|
if (!knownCapabilities.has(required) || !declared.has(required)) {
|
|
308
682
|
refuse('missing-required-guarantee');
|
|
683
|
+
notifyReady();
|
|
309
684
|
return;
|
|
310
685
|
}
|
|
311
686
|
}
|
|
@@ -313,6 +688,8 @@ export function createMdRuntime(options) {
|
|
|
313
688
|
refusal = null;
|
|
314
689
|
emitRefusal();
|
|
315
690
|
}
|
|
691
|
+
capabilities = result.capabilities;
|
|
692
|
+
notifyReady();
|
|
316
693
|
if (JSON.stringify(result.providerLimits) !== JSON.stringify(limits)) {
|
|
317
694
|
limits = result.providerLimits;
|
|
318
695
|
await local.declare(limits);
|
|
@@ -325,10 +702,19 @@ export function createMdRuntime(options) {
|
|
|
325
702
|
}
|
|
326
703
|
async function ensureAuthorization() {
|
|
327
704
|
if (authorization !== undefined) return;
|
|
328
|
-
|
|
705
|
+
try {
|
|
706
|
+
authorization = await options.adapter.authorize();
|
|
707
|
+
journal.record('connection authorize');
|
|
708
|
+
} catch (error) {
|
|
709
|
+
journal.record('connection authorize transport-uncertainty');
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
329
712
|
}
|
|
330
713
|
async function connect() {
|
|
331
|
-
if (closed || discoveryPaused)
|
|
714
|
+
if (closed || discoveryPaused) {
|
|
715
|
+
notifyReady();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
332
718
|
startTimer();
|
|
333
719
|
const progress = {
|
|
334
720
|
complete: false,
|
|
@@ -348,9 +734,11 @@ export function createMdRuntime(options) {
|
|
|
348
734
|
}
|
|
349
735
|
} catch {
|
|
350
736
|
progress.complete = false;
|
|
737
|
+
notifyReady();
|
|
351
738
|
return;
|
|
352
739
|
} finally{
|
|
353
|
-
|
|
740
|
+
notifyReady();
|
|
741
|
+
moveTo(settled(status, progress, completedAt, pending(), attention()));
|
|
354
742
|
}
|
|
355
743
|
}
|
|
356
744
|
async function run() {
|
|
@@ -420,8 +808,32 @@ export function createMdRuntime(options) {
|
|
|
420
808
|
drafts.delete(at(locator, path));
|
|
421
809
|
}
|
|
422
810
|
},
|
|
423
|
-
save (locator, changes) {
|
|
424
|
-
return exclusive(()=>write(locator, changes));
|
|
811
|
+
save (locator, changes, replacing) {
|
|
812
|
+
return exclusive(()=>write(locator, changes, replacing));
|
|
813
|
+
},
|
|
814
|
+
outcome (identifier) {
|
|
815
|
+
const recorded = outcomes.get(identifier);
|
|
816
|
+
if (recorded !== undefined) return recorded;
|
|
817
|
+
for (const replica of replicas.values()){
|
|
818
|
+
if (replica.queue.some((batch)=>batch.id === identifier)) return {
|
|
819
|
+
kind: 'pending'
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
return null;
|
|
823
|
+
},
|
|
824
|
+
queue: {
|
|
825
|
+
discard (locator, identifier) {
|
|
826
|
+
return exclusive(()=>discardHead(locator, identifier));
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
history (locator, path, query) {
|
|
830
|
+
return readHistory(locator, path, query);
|
|
831
|
+
},
|
|
832
|
+
call (name, input) {
|
|
833
|
+
return invokeAction(name, input);
|
|
834
|
+
},
|
|
835
|
+
log () {
|
|
836
|
+
return journal.export();
|
|
425
837
|
},
|
|
426
838
|
observe (topic, second, third) {
|
|
427
839
|
if (topic === 'rooms') {
|
|
@@ -454,7 +866,7 @@ export function createMdRuntime(options) {
|
|
|
454
866
|
notify: third
|
|
455
867
|
};
|
|
456
868
|
queueSubscriptions.add(queue);
|
|
457
|
-
queue.notify(
|
|
869
|
+
queue.notify(entriesOf(queue.locator));
|
|
458
870
|
return ()=>{
|
|
459
871
|
queueSubscriptions.delete(queue);
|
|
460
872
|
};
|
|
@@ -472,6 +884,7 @@ export function createMdRuntime(options) {
|
|
|
472
884
|
},
|
|
473
885
|
async reauthorize () {
|
|
474
886
|
if (refusal === null || !refusal.reauthorizationOffered) return;
|
|
887
|
+
journal.record('connection reauthorize');
|
|
475
888
|
authorization = undefined;
|
|
476
889
|
discoveryPaused = false;
|
|
477
890
|
await enqueue();
|