@agentshouse/mdruntime 0.1.0-alpha.7 → 0.1.0-alpha.8
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 +40 -4
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/bootstrap.d.ts +3 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +12 -6
- package/dist/bootstrap.js.map +1 -1
- package/dist/change.d.ts +1 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js.map +1 -1
- package/dist/collection.d.ts +8 -3
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +66 -28
- package/dist/collection.js.map +1 -1
- package/dist/connection.d.ts +10 -3
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +330 -21
- package/dist/connection.js.map +1 -1
- package/dist/identity.d.ts +2 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +16 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts +2 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +27 -6
- package/dist/memory.js.map +1 -1
- package/dist/model.d.ts +70 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +28 -0
- package/dist/model.js.map +1 -0
- package/dist/query.d.ts +12 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +18 -0
- package/dist/query.js.map +1 -0
- package/dist/refresh.d.ts +2 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +6 -4
- package/dist/refresh.js.map +1 -1
- package/dist/replica.d.ts +3 -1
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +10 -6
- package/dist/replica.js.map +1 -1
- package/dist/save.d.ts +6 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +31 -9
- package/dist/save.js.map +1 -1
- package/dist/store.d.ts +7 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +1 -1
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rawQuery } from './query.js';
|
|
2
|
+
import { cached, modelTypes, NO_MODEL, sameModel } from './model.js';
|
|
2
3
|
import { ROOM_CONTRACT_VERSION, VERSION_1_CAPABILITIES } from './adapter.js';
|
|
3
4
|
import { bootstrapRoom } from './bootstrap.js';
|
|
4
|
-
import { MD_MODEL, PRESENTED } from './collection.js';
|
|
5
|
+
import { MD_MODEL, PRESENTED, validRoot } from './collection.js';
|
|
5
6
|
import { createLog } from './log.js';
|
|
6
7
|
import { queuedOutcome, refused } from './outcome.js';
|
|
7
8
|
import { refreshRoom } from './refresh.js';
|
|
@@ -25,6 +26,14 @@ export function createMdRuntime(options) {
|
|
|
25
26
|
let capabilities = [];
|
|
26
27
|
const rooms = new Map();
|
|
27
28
|
const unreadable = new Set();
|
|
29
|
+
const models = new Map();
|
|
30
|
+
const liveModels = new Map();
|
|
31
|
+
const invalidModels = new Set();
|
|
32
|
+
const modelEpoch = new Map();
|
|
33
|
+
function modelOf(locator) {
|
|
34
|
+
return models.get(locator) ?? NO_MODEL;
|
|
35
|
+
}
|
|
36
|
+
const mismatchedRoots = new Set();
|
|
28
37
|
let refusal = null;
|
|
29
38
|
let storageLoss = null;
|
|
30
39
|
let status = STARTING;
|
|
@@ -146,9 +155,9 @@ export function createMdRuntime(options) {
|
|
|
146
155
|
announce();
|
|
147
156
|
}
|
|
148
157
|
function listed() {
|
|
149
|
-
return [
|
|
158
|
+
return structuredClone([
|
|
150
159
|
...rooms.values()
|
|
151
|
-
];
|
|
160
|
+
]);
|
|
152
161
|
}
|
|
153
162
|
function queryRooms() {
|
|
154
163
|
return [
|
|
@@ -156,7 +165,8 @@ export function createMdRuntime(options) {
|
|
|
156
165
|
].map((replica)=>({
|
|
157
166
|
room: replica.confirmed.record.locator,
|
|
158
167
|
roomVersion: replica.confirmed.record.roomVersion,
|
|
159
|
-
documents: replica.visible.collection.documents
|
|
168
|
+
documents: replica.visible.collection.documents,
|
|
169
|
+
model: modelTypes(modelOf(replica.confirmed.record.locator))
|
|
160
170
|
}));
|
|
161
171
|
}
|
|
162
172
|
function emitRooms() {
|
|
@@ -193,7 +203,11 @@ export function createMdRuntime(options) {
|
|
|
193
203
|
announce();
|
|
194
204
|
}
|
|
195
205
|
function visible(request) {
|
|
196
|
-
|
|
206
|
+
if (options.queryBinding === null) {
|
|
207
|
+
if (Object.keys(request).length > 0) throw new Error('query-binding-unavailable');
|
|
208
|
+
return rawQuery(queryRooms());
|
|
209
|
+
}
|
|
210
|
+
return options.queryBinding(queryRooms(), {
|
|
197
211
|
...request,
|
|
198
212
|
selector: {
|
|
199
213
|
$and: [
|
|
@@ -234,6 +248,38 @@ export function createMdRuntime(options) {
|
|
|
234
248
|
}, period);
|
|
235
249
|
}
|
|
236
250
|
function refuse(reason) {
|
|
251
|
+
if (reason === 'revoked-authorization') {
|
|
252
|
+
const revoked = {
|
|
253
|
+
status: 'unavailable',
|
|
254
|
+
reason: 'revoked'
|
|
255
|
+
};
|
|
256
|
+
for (const locator of new Set([
|
|
257
|
+
...models.keys(),
|
|
258
|
+
...rooms.keys()
|
|
259
|
+
])){
|
|
260
|
+
invalidModels.add(locator);
|
|
261
|
+
modelEpoch.set(locator, (modelEpoch.get(locator) ?? 0) + 1);
|
|
262
|
+
models.set(locator, revoked);
|
|
263
|
+
liveModels.delete(locator);
|
|
264
|
+
}
|
|
265
|
+
for (const [locator, room] of rooms)rooms.set(locator, {
|
|
266
|
+
...room,
|
|
267
|
+
model: modelTypes(revoked)
|
|
268
|
+
});
|
|
269
|
+
emitRooms();
|
|
270
|
+
void exclusive(async ()=>{
|
|
271
|
+
const local = await openLocal();
|
|
272
|
+
await acrossTabs('mdruntime-replica', async ()=>{
|
|
273
|
+
for (const record of (await local.all()))await local.publish({
|
|
274
|
+
...record,
|
|
275
|
+
model: revoked
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
announce();
|
|
279
|
+
}).catch(()=>{
|
|
280
|
+
journal.record('model invalidation durable-write failure');
|
|
281
|
+
});
|
|
282
|
+
}
|
|
237
283
|
stopTimer();
|
|
238
284
|
discoveryPaused = true;
|
|
239
285
|
refusal = {
|
|
@@ -246,8 +292,12 @@ export function createMdRuntime(options) {
|
|
|
246
292
|
function list(room, synchronizationAvailable) {
|
|
247
293
|
rooms.set(room.locator, {
|
|
248
294
|
name: room.name,
|
|
295
|
+
collectionRoot: room.collectionRoot,
|
|
249
296
|
locator: room.locator,
|
|
250
|
-
|
|
297
|
+
operations: room.operations,
|
|
298
|
+
replica: room.replica,
|
|
299
|
+
attachedModel: room.attachedModel,
|
|
300
|
+
model: modelTypes(modelOf(room.locator)),
|
|
251
301
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
252
302
|
roomVersion: replicas.get(room.locator)?.confirmed.record.roomVersion ?? null,
|
|
253
303
|
synchronizationAvailable
|
|
@@ -256,7 +306,12 @@ export function createMdRuntime(options) {
|
|
|
256
306
|
function applyDiscovery(discovered) {
|
|
257
307
|
const listedLocators = new Set(discovered.map((room)=>room.locator));
|
|
258
308
|
for (const room of discovered){
|
|
259
|
-
|
|
309
|
+
const held = replicas.get(room.locator)?.confirmed.record;
|
|
310
|
+
if (held !== undefined && held.replica.selection !== room.replica.selection) {
|
|
311
|
+
list(held, false);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
260
315
|
}
|
|
261
316
|
for (const [locator, room] of rooms){
|
|
262
317
|
if (listedLocators.has(locator)) continue;
|
|
@@ -291,7 +346,22 @@ export function createMdRuntime(options) {
|
|
|
291
346
|
}
|
|
292
347
|
replicas.clear();
|
|
293
348
|
for (const record of records){
|
|
294
|
-
const
|
|
349
|
+
const persisted = record.model;
|
|
350
|
+
const priorModel = models.get(record.locator);
|
|
351
|
+
if (priorModel !== undefined && priorModel.status !== 'unavailable' && (persisted.status === 'unavailable' || !sameModel(priorModel.snapshot, persisted.snapshot))) {
|
|
352
|
+
modelEpoch.set(record.locator, (modelEpoch.get(record.locator) ?? 0) + 1);
|
|
353
|
+
invalidModels.add(record.locator);
|
|
354
|
+
}
|
|
355
|
+
const model = persisted.status === 'unavailable' || liveModels.get(record.locator) === JSON.stringify(persisted.snapshot) ? persisted : cached(persisted);
|
|
356
|
+
models.set(record.locator, model);
|
|
357
|
+
if (model.status === 'unavailable') {
|
|
358
|
+
liveModels.delete(record.locator);
|
|
359
|
+
invalidModels.delete(record.locator);
|
|
360
|
+
}
|
|
361
|
+
const confirmed = readConfirmed({
|
|
362
|
+
...record,
|
|
363
|
+
model
|
|
364
|
+
});
|
|
295
365
|
if (confirmed === null) continue;
|
|
296
366
|
replicas.set(record.locator, replicaOf(confirmed, queuedBatches.get(record.locator) ?? []));
|
|
297
367
|
const existing = rooms.get(record.locator);
|
|
@@ -299,7 +369,11 @@ export function createMdRuntime(options) {
|
|
|
299
369
|
else {
|
|
300
370
|
rooms.set(record.locator, {
|
|
301
371
|
...existing,
|
|
302
|
-
roomVersion: record.roomVersion
|
|
372
|
+
roomVersion: record.roomVersion,
|
|
373
|
+
operations: record.operations,
|
|
374
|
+
replica: record.replica,
|
|
375
|
+
attachedModel: record.attachedModel,
|
|
376
|
+
model: modelTypes(model)
|
|
303
377
|
});
|
|
304
378
|
}
|
|
305
379
|
}
|
|
@@ -387,9 +461,14 @@ export function createMdRuntime(options) {
|
|
|
387
461
|
return exclusive(async ()=>{
|
|
388
462
|
await acrossTabs('mdruntime-replica', async ()=>{
|
|
389
463
|
await hydrate(local);
|
|
390
|
-
|
|
464
|
+
const retained = readConfirmed({
|
|
465
|
+
...confirmed.record,
|
|
466
|
+
model: modelOf(room.locator)
|
|
467
|
+
});
|
|
468
|
+
if (retained === null) return;
|
|
469
|
+
await local.publish(retained.record);
|
|
391
470
|
unreadable.delete(room.locator);
|
|
392
|
-
replicas.set(room.locator, replicaOf(
|
|
471
|
+
replicas.set(room.locator, replicaOf(retained, replicas.get(room.locator)?.queue ?? []));
|
|
393
472
|
});
|
|
394
473
|
list(room, true);
|
|
395
474
|
emitRooms();
|
|
@@ -406,9 +485,19 @@ export function createMdRuntime(options) {
|
|
|
406
485
|
if (replica === undefined || room === undefined) {
|
|
407
486
|
throw new Error(`No Local Replica for ${locator}`);
|
|
408
487
|
}
|
|
488
|
+
if (mismatchedRoots.has(locator)) return {
|
|
489
|
+
outcome: 'refused',
|
|
490
|
+
rule: 'collection-root-mismatch'
|
|
491
|
+
};
|
|
409
492
|
const built = buildBatch({
|
|
410
493
|
baseRoomVersion: replica.confirmed.record.roomVersion,
|
|
411
|
-
|
|
494
|
+
collectionRoot: replica.confirmed.record.collectionRoot,
|
|
495
|
+
operations: room.operations,
|
|
496
|
+
model: invalidModels.has(locator) ? {
|
|
497
|
+
status: 'unavailable',
|
|
498
|
+
reason: 'revoked'
|
|
499
|
+
} : modelOf(locator),
|
|
500
|
+
binding: options.modelBinding,
|
|
412
501
|
providerOwnedPaths: room.providerOwnedPaths,
|
|
413
502
|
providerLimits: limits,
|
|
414
503
|
visible: replica.visible,
|
|
@@ -528,6 +617,10 @@ export function createMdRuntime(options) {
|
|
|
528
617
|
}
|
|
529
618
|
});
|
|
530
619
|
journal.record(`outcome history ${result.kind}`);
|
|
620
|
+
if (result.kind === 'scoped-refusal') {
|
|
621
|
+
const room = replicas.get(result.locator)?.confirmed.record;
|
|
622
|
+
if (room !== undefined) await scoped(result, room, await openLocal());
|
|
623
|
+
}
|
|
531
624
|
if (result.kind === 'revoked-authorization') refuse('revoked-authorization');
|
|
532
625
|
return result;
|
|
533
626
|
} catch {
|
|
@@ -571,6 +664,10 @@ export function createMdRuntime(options) {
|
|
|
571
664
|
name,
|
|
572
665
|
input
|
|
573
666
|
});
|
|
667
|
+
if (result.kind === 'scoped-refusal') {
|
|
668
|
+
const room = replicas.get(result.locator)?.confirmed.record;
|
|
669
|
+
if (room !== undefined) await scoped(result, room, await openLocal());
|
|
670
|
+
}
|
|
574
671
|
if (result.kind === 'revoked-authorization') refuse('revoked-authorization');
|
|
575
672
|
journal.record(`outcome action ${result.kind}`);
|
|
576
673
|
if (result.kind === 'rejected') journal.record(`refusal rejected ${result.text}`);
|
|
@@ -584,7 +681,7 @@ export function createMdRuntime(options) {
|
|
|
584
681
|
}
|
|
585
682
|
async function pullRefresh(authorized, confirmed) {
|
|
586
683
|
try {
|
|
587
|
-
const result = await refreshRoom(options.adapter, authorized, confirmed);
|
|
684
|
+
const result = await refreshRoom(options.adapter, authorized, confirmed, options.modelBinding);
|
|
588
685
|
const detail = result.kind === 'advanced' ? result.confirmed.record.roomVersion : result.kind;
|
|
589
686
|
journal.record(`transfer refresh ${confirmed.record.locator} ${detail}`);
|
|
590
687
|
return result;
|
|
@@ -595,7 +692,7 @@ export function createMdRuntime(options) {
|
|
|
595
692
|
}
|
|
596
693
|
async function pullBootstrap(authorized, room) {
|
|
597
694
|
try {
|
|
598
|
-
const result = await bootstrapRoom(options.adapter, authorized, room);
|
|
695
|
+
const result = await bootstrapRoom(options.adapter, authorized, room, modelOf(room.locator), options.modelBinding);
|
|
599
696
|
const detail = result.kind === 'staged' ? result.confirmed.record.roomVersion : result.kind;
|
|
600
697
|
journal.record(`transfer bootstrap ${room.locator} ${detail}`);
|
|
601
698
|
return result;
|
|
@@ -654,7 +751,12 @@ export function createMdRuntime(options) {
|
|
|
654
751
|
return true;
|
|
655
752
|
}
|
|
656
753
|
const remaining = current.queue.filter((batch)=>batch.id !== submitted.id);
|
|
657
|
-
const
|
|
754
|
+
const retainedBase = readConfirmed({
|
|
755
|
+
...base.record,
|
|
756
|
+
model: modelOf(room.locator)
|
|
757
|
+
});
|
|
758
|
+
if (retainedBase === null) return false;
|
|
759
|
+
const applied = overlay ? applyReturned(retainedBase, accepted.documents, accepted.revisions, accepted.removals, accepted.roomVersion, options.modelBinding) : retainedBase;
|
|
658
760
|
if (applied === null) return false;
|
|
659
761
|
await local.publish(applied.record);
|
|
660
762
|
await persistQueue(local, room.locator, remaining);
|
|
@@ -710,6 +812,11 @@ export function createMdRuntime(options) {
|
|
|
710
812
|
refuse('revoked-authorization');
|
|
711
813
|
return 'stop';
|
|
712
814
|
}
|
|
815
|
+
if (result.kind === 'scoped-refusal') {
|
|
816
|
+
await scoped(result, room, local);
|
|
817
|
+
progress.complete = false;
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
713
820
|
if (result.kind === 'conflicted' || result.kind === 'rejected' || result.kind === 'expired') {
|
|
714
821
|
journal.record(`outcome ${room.locator} ${head.id} ${result.kind}`);
|
|
715
822
|
if (result.kind !== 'expired') journal.record(`refusal ${result.kind} ${result.text}`);
|
|
@@ -738,6 +845,11 @@ export function createMdRuntime(options) {
|
|
|
738
845
|
refuse('revoked-authorization');
|
|
739
846
|
return 'stop';
|
|
740
847
|
}
|
|
848
|
+
if (pulled.kind === 'scoped-refusal') {
|
|
849
|
+
await scoped(pulled, room, local);
|
|
850
|
+
progress.complete = false;
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
741
853
|
if (pulled.kind === 'still-current') {
|
|
742
854
|
const committed = await exclusive(()=>commitAccept(room, local, head, result, from, true));
|
|
743
855
|
if (!committed) {
|
|
@@ -761,6 +873,11 @@ export function createMdRuntime(options) {
|
|
|
761
873
|
refuse('revoked-authorization');
|
|
762
874
|
return 'stop';
|
|
763
875
|
}
|
|
876
|
+
if (bootstrapped.kind === 'scoped-refusal') {
|
|
877
|
+
await scoped(bootstrapped, room, local);
|
|
878
|
+
progress.complete = false;
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
764
881
|
if (bootstrapped.kind === 'staged') {
|
|
765
882
|
const overlay = bootstrapped.confirmed.record.roomVersion === result.roomVersion;
|
|
766
883
|
const committed = await exclusive(()=>commitAccept(room, local, head, result, bootstrapped.confirmed, overlay));
|
|
@@ -792,15 +909,172 @@ export function createMdRuntime(options) {
|
|
|
792
909
|
}
|
|
793
910
|
return null;
|
|
794
911
|
}
|
|
912
|
+
async function retainModel(local, room, next) {
|
|
913
|
+
if (next.status === 'unavailable') modelEpoch.set(room.locator, (modelEpoch.get(room.locator) ?? 0) + 1);
|
|
914
|
+
const epoch = modelEpoch.get(room.locator) ?? 0;
|
|
915
|
+
const previous = modelOf(room.locator);
|
|
916
|
+
const heldBefore = replicas.get(room.locator)?.confirmed.record;
|
|
917
|
+
if (JSON.stringify(previous) === JSON.stringify(next) && (heldBefore === undefined || JSON.stringify(heldBefore.operations) === JSON.stringify(room.operations) && JSON.stringify(heldBefore.replica) === JSON.stringify(room.replica) && JSON.stringify(heldBefore.attachedModel) === JSON.stringify(room.attachedModel))) return;
|
|
918
|
+
const queryChanged = previous.status === 'unavailable' || next.status === 'unavailable' ? previous.status !== next.status : JSON.stringify(previous.types) !== JSON.stringify(next.types);
|
|
919
|
+
if (next.status === 'unavailable') {
|
|
920
|
+
invalidModels.add(room.locator);
|
|
921
|
+
liveModels.delete(room.locator);
|
|
922
|
+
}
|
|
923
|
+
await exclusive(async ()=>acrossTabs('mdruntime-replica', async ()=>{
|
|
924
|
+
await hydrate(local);
|
|
925
|
+
if (next.status !== 'unavailable' && epoch !== (modelEpoch.get(room.locator) ?? 0)) return;
|
|
926
|
+
const held = replicas.get(room.locator);
|
|
927
|
+
if (held !== undefined) {
|
|
928
|
+
const record = {
|
|
929
|
+
...held.confirmed.record,
|
|
930
|
+
operations: room.operations,
|
|
931
|
+
replica: room.replica,
|
|
932
|
+
attachedModel: room.attachedModel,
|
|
933
|
+
model: next
|
|
934
|
+
};
|
|
935
|
+
await local.publish(record);
|
|
936
|
+
const confirmed = readConfirmed(record);
|
|
937
|
+
if (confirmed !== null) replicas.set(room.locator, replicaOf(confirmed, held.queue));
|
|
938
|
+
}
|
|
939
|
+
models.set(room.locator, next);
|
|
940
|
+
if (next.status === 'current') {
|
|
941
|
+
liveModels.set(room.locator, JSON.stringify(next.snapshot));
|
|
942
|
+
invalidModels.delete(room.locator);
|
|
943
|
+
}
|
|
944
|
+
list(room, room.replica.admission === 'admitted' && !unreadable.has(room.locator));
|
|
945
|
+
emitRooms();
|
|
946
|
+
if (queryChanged && held !== undefined) emitVisible();
|
|
947
|
+
announce();
|
|
948
|
+
}));
|
|
949
|
+
}
|
|
950
|
+
async function acquireModel(local, room, authorized) {
|
|
951
|
+
let previous = modelOf(room.locator);
|
|
952
|
+
if (previous.status !== 'unavailable' && (room.attachedModel === null || !sameModel(previous.snapshot, room.attachedModel))) {
|
|
953
|
+
previous = {
|
|
954
|
+
status: 'unavailable',
|
|
955
|
+
reason: 'version-changed'
|
|
956
|
+
};
|
|
957
|
+
await retainModel(local, room, previous);
|
|
958
|
+
}
|
|
959
|
+
const epoch = modelEpoch.get(room.locator) ?? 0;
|
|
960
|
+
let answer;
|
|
961
|
+
try {
|
|
962
|
+
answer = await options.adapter.model(authorized, {
|
|
963
|
+
locator: room.locator,
|
|
964
|
+
collectionRoot: room.collectionRoot,
|
|
965
|
+
model: room.attachedModel
|
|
966
|
+
});
|
|
967
|
+
} catch {
|
|
968
|
+
answer = {
|
|
969
|
+
kind: 'transport-uncertainty'
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
if (epoch !== (modelEpoch.get(room.locator) ?? 0)) return;
|
|
973
|
+
if (answer.kind === 'revoked-authorization') {
|
|
974
|
+
await retainModel(local, room, {
|
|
975
|
+
status: 'unavailable',
|
|
976
|
+
reason: 'revoked'
|
|
977
|
+
});
|
|
978
|
+
refuse('revoked-authorization');
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if (answer.kind === 'transport-uncertainty') {
|
|
982
|
+
await retainModel(local, room, cached(previous));
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (answer.kind === 'model-unavailable') {
|
|
986
|
+
await retainModel(local, room, {
|
|
987
|
+
status: 'unavailable',
|
|
988
|
+
reason: answer.reason
|
|
989
|
+
});
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (room.attachedModel === null || !sameModel(answer.snapshot, room.attachedModel)) {
|
|
993
|
+
await retainModel(local, room, {
|
|
994
|
+
status: 'unavailable',
|
|
995
|
+
reason: 'version-changed'
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (previous.status !== 'unavailable' && sameModel(previous.snapshot, answer.snapshot) && JSON.stringify(Object.entries(previous.snapshot.definitions).sort()) !== JSON.stringify(Object.entries(answer.snapshot.definitions).sort())) {
|
|
1000
|
+
await retainModel(local, room, {
|
|
1001
|
+
status: 'unavailable',
|
|
1002
|
+
reason: 'invalid'
|
|
1003
|
+
});
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const verified = options.modelBinding?.verify(answer.snapshot);
|
|
1007
|
+
await retainModel(local, room, verified?.kind === 'verified' ? {
|
|
1008
|
+
status: 'current',
|
|
1009
|
+
snapshot: structuredClone(answer.snapshot),
|
|
1010
|
+
verifiedWith: MD_MODEL,
|
|
1011
|
+
types: verified.types
|
|
1012
|
+
} : {
|
|
1013
|
+
status: 'unavailable',
|
|
1014
|
+
reason: verified === undefined ? 'binding-unavailable' : 'invalid'
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
async function scoped(result, room, local) {
|
|
1018
|
+
if (result.scope === 'model') await retainModel(local, room, {
|
|
1019
|
+
status: 'unavailable',
|
|
1020
|
+
reason: 'not-readable'
|
|
1021
|
+
});
|
|
1022
|
+
else if (result.scope !== 'operation') {
|
|
1023
|
+
unreadable.add(room.locator);
|
|
1024
|
+
const unavailable = {
|
|
1025
|
+
...room,
|
|
1026
|
+
replica: {
|
|
1027
|
+
...room.replica,
|
|
1028
|
+
admission: 'unavailable'
|
|
1029
|
+
},
|
|
1030
|
+
operations: {
|
|
1031
|
+
...room.operations,
|
|
1032
|
+
...result.scope === 'content' ? {
|
|
1033
|
+
read: []
|
|
1034
|
+
} : {},
|
|
1035
|
+
...result.scope === 'sync' ? {
|
|
1036
|
+
sync: []
|
|
1037
|
+
} : {}
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
await retainModel(local, unavailable, modelOf(room.locator));
|
|
1041
|
+
list(unavailable, false);
|
|
1042
|
+
emitRooms();
|
|
1043
|
+
}
|
|
1044
|
+
journal.record(`scoped refusal ${room.locator} ${result.scope} ${result.text}`);
|
|
1045
|
+
}
|
|
795
1046
|
async function synchronize(discovered, local, progress) {
|
|
796
1047
|
for (const room of discovered){
|
|
797
1048
|
if (closed || authorization === undefined) {
|
|
798
1049
|
progress.complete = false;
|
|
799
1050
|
return;
|
|
800
1051
|
}
|
|
1052
|
+
const prior = replicas.get(room.locator)?.confirmed.record;
|
|
1053
|
+
if (prior !== undefined && prior.replica.selection !== room.replica.selection) {
|
|
1054
|
+
list(prior, false);
|
|
1055
|
+
emitRooms();
|
|
1056
|
+
journal.record(`selection changed ${room.locator}`);
|
|
1057
|
+
progress.complete = false;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
await acquireModel(local, room, authorization);
|
|
1061
|
+
if (discoveryPaused) return;
|
|
1062
|
+
if (room.replica.admission !== 'admitted') {
|
|
1063
|
+
progress.complete = false;
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
const held = replicas.get(room.locator);
|
|
1067
|
+
if (held !== undefined && held.confirmed.record.replica.selection !== room.replica.selection) {
|
|
1068
|
+
progress.complete = false;
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
801
1071
|
const withheld = await advance(room, local, authorization);
|
|
802
1072
|
if (withheld !== null) {
|
|
803
1073
|
progress.complete = false;
|
|
1074
|
+
if (withheld.kind === 'scoped-refusal') {
|
|
1075
|
+
await scoped(withheld, room, local);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
804
1078
|
if (withheld.kind === 'transport-uncertainty') {
|
|
805
1079
|
progress.reachable = false;
|
|
806
1080
|
continue;
|
|
@@ -829,10 +1103,10 @@ export function createMdRuntime(options) {
|
|
|
829
1103
|
let result;
|
|
830
1104
|
try {
|
|
831
1105
|
result = await options.adapter.discover(authorization);
|
|
832
|
-
} catch
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1106
|
+
} catch {
|
|
1107
|
+
result = {
|
|
1108
|
+
kind: 'transport-uncertainty'
|
|
1109
|
+
};
|
|
836
1110
|
}
|
|
837
1111
|
if (closed) {
|
|
838
1112
|
notifyReady();
|
|
@@ -840,10 +1114,17 @@ export function createMdRuntime(options) {
|
|
|
840
1114
|
}
|
|
841
1115
|
journal.record(`connection discover ${result.kind}`);
|
|
842
1116
|
if (result.kind === 'transport-uncertainty') {
|
|
1117
|
+
for (const room of rooms.values())await retainModel(local, room, cached(modelOf(room.locator)));
|
|
843
1118
|
progress.reachable = false;
|
|
844
1119
|
notifyReady();
|
|
845
1120
|
return;
|
|
846
1121
|
}
|
|
1122
|
+
if (result.kind === 'contract-unavailable') {
|
|
1123
|
+
journal.record(`connection contract unavailable ${result.text}`);
|
|
1124
|
+
refuse('missing-required-guarantee');
|
|
1125
|
+
notifyReady();
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
847
1128
|
if (result.kind === 'revoked-authorization') {
|
|
848
1129
|
refuse('revoked-authorization');
|
|
849
1130
|
notifyReady();
|
|
@@ -859,6 +1140,34 @@ export function createMdRuntime(options) {
|
|
|
859
1140
|
notifyReady();
|
|
860
1141
|
return;
|
|
861
1142
|
}
|
|
1143
|
+
mismatchedRoots.clear();
|
|
1144
|
+
for (const room of result.rooms){
|
|
1145
|
+
const held = replicas.get(room.locator)?.confirmed.record;
|
|
1146
|
+
const malformed = !validRoot(room.collectionRoot);
|
|
1147
|
+
if (room.operations === undefined || ![
|
|
1148
|
+
'read',
|
|
1149
|
+
'sync',
|
|
1150
|
+
'write',
|
|
1151
|
+
'delete'
|
|
1152
|
+
].every((operation)=>Array.isArray(room.operations[operation]) && room.operations[operation].every((scope)=>typeof scope === 'string')) || room.replica === undefined || typeof room.replica.selection !== 'string' || room.replica.selection.length === 0 || ![
|
|
1153
|
+
'admitted',
|
|
1154
|
+
'unavailable'
|
|
1155
|
+
].includes(room.replica.admission) || room.attachedModel === undefined || room.attachedModel !== null && (typeof room.attachedModel.identity !== 'string' || !room.attachedModel.identity || typeof room.attachedModel.version !== 'string' || !room.attachedModel.version)) {
|
|
1156
|
+
journal.record(`invalid model or replica declaration ${room.locator}`);
|
|
1157
|
+
refuse('missing-required-guarantee');
|
|
1158
|
+
notifyReady();
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (malformed || held !== undefined && held.collectionRoot !== room.collectionRoot) {
|
|
1162
|
+
mismatchedRoots.add(room.locator);
|
|
1163
|
+
journal.record(`collection root ${malformed ? 'invalid' : 'changed'} ${room.locator}`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (mismatchedRoots.size > 0) {
|
|
1167
|
+
refuse('missing-required-guarantee');
|
|
1168
|
+
notifyReady();
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
862
1171
|
const declared = new Set(result.capabilities.map((capability)=>capability.name));
|
|
863
1172
|
for (const required of options.requiredGuarantees){
|
|
864
1173
|
if (!knownCapabilities.has(required) || !declared.has(required)) {
|
|
@@ -1005,7 +1314,7 @@ export function createMdRuntime(options) {
|
|
|
1005
1314
|
return visible(request);
|
|
1006
1315
|
},
|
|
1007
1316
|
types (locator) {
|
|
1008
|
-
return
|
|
1317
|
+
return modelTypes(modelOf(locator));
|
|
1009
1318
|
},
|
|
1010
1319
|
syncStatus () {
|
|
1011
1320
|
return status;
|