@agentshouse/mdruntime-adapter 0.1.0-ceda7e87cc6e8 → 0.1.0-cedb8e57837a7

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.js CHANGED
@@ -1,14 +1,16 @@
1
+ import { sortedKeys, stepped } from '@agentshouse/mdruntime';
1
2
  import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
2
3
  import { partModels, streamedModels, viewModels } from './model.js';
3
4
  import { gathered, gatheredEach, selections } from './gather.js';
4
5
  import { createSiteAdapter } from './site.js';
5
- import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, } from './read.js';
6
+ import { authorityRoot, bootstrapping, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
7
+ import { sha256Hex } from './digest.js';
6
8
  import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
7
9
  import { houseAction } from './actions.js';
8
10
  import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
9
11
  import { houseNotifications } from './stream.js';
10
12
  import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
11
- import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
+ import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
12
14
  import { houseReferrer } from './referrer.js';
13
15
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
14
16
  const UNCERTAIN = { kind: 'transport-uncertainty' };
@@ -16,9 +18,56 @@ const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
16
18
  function lapsedGrant(outcome) {
17
19
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
18
20
  }
21
+ const heldPaths = new WeakMap();
22
+ async function sortedHeld(held) {
23
+ const known = heldPaths.get(held.documents);
24
+ if (known !== undefined)
25
+ return known;
26
+ const sorted = await stepped(sortedKeys(held.documents));
27
+ heldPaths.set(held.documents, sorted);
28
+ return sorted;
29
+ }
30
+ function heldFiles(authority, held, paths, after, bound) {
31
+ let low = 0;
32
+ for (let high = paths.length; low < high;) {
33
+ const middle = (low + high) >>> 1;
34
+ if (after !== null && paths[middle] <= after)
35
+ low = middle + 1;
36
+ else
37
+ high = middle;
38
+ }
39
+ const files = [];
40
+ let through = null;
41
+ for (let at = low; at < paths.length && through === null; at += 1) {
42
+ const revision = held.revisions[paths[at]];
43
+ if (revision === undefined)
44
+ continue;
45
+ if (files.length === bound)
46
+ through = files.at(-1).path;
47
+ else
48
+ files.push({ path: paths[at], revision });
49
+ }
50
+ return files.length === 0 ? null : { room: authority, through, files };
51
+ }
52
+ async function reused(scope, held) {
53
+ const sent = new Set(scope.contents.map((entry) => entry.path));
54
+ const contents = [...scope.contents];
55
+ for (const file of scope.files) {
56
+ if (file.binary !== undefined || sent.has(file.path))
57
+ continue;
58
+ const content = held?.documents[file.path];
59
+ if (content === undefined ||
60
+ held?.revisions[file.path] !== file.revision ||
61
+ (await sha256Hex(content)) !== file.sha256) {
62
+ return null;
63
+ }
64
+ contents.push({ path: file.path, content });
65
+ }
66
+ return { ...scope, contents };
67
+ }
19
68
  export function createAgentsHouseAdapter(options) {
20
69
  if ('site' in options)
21
- return createSiteAdapter(options.site);
70
+ return createSiteAdapter(options.site, options.snapshot === true);
22
71
  const transport = options.transport;
23
72
  const notifications = houseNotifications(transport, 'credential');
24
73
  const tracked = new Map();
@@ -29,12 +78,36 @@ export function createAgentsHouseAdapter(options) {
29
78
  throw new Error('no Discovery declared the bounds');
30
79
  return bounds;
31
80
  }
32
- async function bootstrappedRoom(envelope, scope, entry, continued) {
33
- if (scope === undefined)
81
+ function hand(locator, steps) {
82
+ const held = handed.get(locator);
83
+ const next = stepped(steps);
84
+ if (!(next instanceof Promise)) {
85
+ handed.set(locator, next);
86
+ return;
87
+ }
88
+ return next.then((room) => {
89
+ if (handed.get(locator) === held)
90
+ handed.set(locator, room);
91
+ else
92
+ handed.delete(locator);
93
+ });
94
+ }
95
+ async function bootstrappedRoom(envelope, sent, asked, continued) {
96
+ const { entry } = asked;
97
+ if (sent === undefined)
34
98
  return { kind: 'room-not-readable', locator: entry.locator };
35
- const next = envelope.next?.room === entry.authority ? envelope.next.path : null;
99
+ const scope = await reused(sent, asked.held);
100
+ if (scope === null) {
101
+ return asked.held === undefined
102
+ ? UNCERTAIN_READ
103
+ : continued === null
104
+ ? bootstrapTogether({ entry, held: undefined })
105
+ : continuedBootstrap({ entry, held: undefined }, continued);
106
+ }
107
+ const cursor = envelope.next?.room === entry.authority ? envelope.next : undefined;
108
+ const next = cursor?.path ?? null;
36
109
  if (continued === null && next === null)
37
- handed.set(entry.locator, scopeHanded(scope));
110
+ await hand(entry.locator, scopeHanded(scope));
38
111
  else
39
112
  handed.delete(entry.locator);
40
113
  notifications.reach(entry.authority, scope);
@@ -43,22 +116,45 @@ export function createAgentsHouseAdapter(options) {
43
116
  ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
44
117
  ];
45
118
  const models = await partModels(scope, entry, new Set(seen), next === null);
46
- const continuation = next === null ? null : { after: next, seen };
119
+ const continuation = next === null
120
+ ? null
121
+ : { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
47
122
  return {
48
- ...bootstrapOf(scope),
123
+ ...(await stepped(bootstrapping(scope))),
49
124
  ...(models === undefined ? {} : { models }),
50
125
  ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
51
126
  };
52
127
  }
128
+ async function enumeratedPage(ask, page) {
129
+ const answer = await transport.door(DOOR_ENUMERATE, {
130
+ prefixes: [authorityRoot(ask.entry.handle)],
131
+ positions: [
132
+ {
133
+ ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
134
+ page: { through: page.through, read: page.read },
135
+ },
136
+ ],
137
+ });
138
+ const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
139
+ if (failure !== null)
140
+ return failure;
141
+ const enumeration = parseEnumeration(answer.body, 'credential');
142
+ if (enumeration === null)
143
+ return UNCERTAIN_READ;
144
+ return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
145
+ }
53
146
  async function enumeratedRoom(enumeration, ask) {
54
- const view = viewOf(enumeration, ask.entry);
55
- if (view === undefined)
147
+ const first = viewOf(enumeration, ask.entry);
148
+ if (first === undefined)
56
149
  return { kind: 'room-not-readable', locator: ask.entry.locator };
150
+ const view = await wholeView(first, (page) => enumeratedPage(ask, page));
151
+ if ('kind' in view)
152
+ return view;
57
153
  notifications.reach(ask.entry.authority, view);
58
- const answer = refreshFrom(view, ask.fromRoomVersion);
154
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
59
155
  if (answer.kind !== 'net-advancement')
60
156
  return answer;
61
- handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
157
+ await hand(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
62
158
  const models = await viewModels(view, ask.entry);
63
159
  return models === undefined ? answer : { ...answer, models };
64
160
  }
@@ -66,10 +162,10 @@ export function createAgentsHouseAdapter(options) {
66
162
  const held = handed.get(entry.locator);
67
163
  if (held?.roomVersion !== request.fromRoomVersion)
68
164
  return null;
69
- const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
165
+ const advanced = await stepped(notifications.advance(entry, from, (path, revision) => held.revisions[path] === revision));
70
166
  if (advanced === null)
71
167
  return null;
72
- handed.set(entry.locator, streamedHanded(held, advanced));
168
+ await hand(entry.locator, streamedHanded(held, advanced));
73
169
  const models = await streamedModels(advanced.schemas, entry, 'credential');
74
170
  return models === undefined ? advanced.page : { ...advanced.page, models };
75
171
  }
@@ -85,14 +181,20 @@ export function createAgentsHouseAdapter(options) {
85
181
  });
86
182
  notifications.send(kept);
87
183
  }
88
- function adopted(entry, outcome) {
184
+ async function adoptedMove(entry, into, outcome) {
185
+ if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
186
+ await adopted(into, { kind: 'accepted', ...outcome.destination });
187
+ }
188
+ return adopted(entry, outcome);
189
+ }
190
+ async function adopted(entry, outcome) {
89
191
  if (outcome.kind !== 'accepted')
90
192
  return outcome;
91
193
  const held = handed.get(entry.locator);
92
194
  if (held === undefined)
93
195
  return outcome;
94
196
  const follows = followsVersion(held.roomVersion, outcome.roomVersion);
95
- handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
197
+ await hand(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
96
198
  const at = logPosition(outcome.roomVersion);
97
199
  if (follows && at !== null)
98
200
  notifications.reach(entry.authority, at);
@@ -115,19 +217,31 @@ export function createAgentsHouseAdapter(options) {
115
217
  return result;
116
218
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
117
219
  }
118
- function weight(entry) {
119
- return contentPrefixes(entry).length;
220
+ async function holding(asks, after) {
221
+ const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
222
+ const held = [];
223
+ for (const ask of asks) {
224
+ if (ask.held === undefined || bound === 0)
225
+ continue;
226
+ const paths = await sortedHeld(ask.held);
227
+ const files = heldFiles(ask.entry.authority, ask.held, paths, after, bound);
228
+ if (files !== null)
229
+ held.push(files);
230
+ }
231
+ return held;
120
232
  }
121
- async function bootstrapped(entries, answer) {
122
- let open = entries.map((entry, ask) => ({ entry, ask }));
233
+ async function bootstrapped(asks, answer) {
234
+ let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
123
235
  while (open.length > 0) {
124
236
  const unreached = [];
125
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
237
+ for (const part of selections(open, declared().max_prefixes)) {
238
+ const held = await holding(part.map(({ asked }) => asked), null);
126
239
  const answered = await transport.door(DOOR_BOOTSTRAP, {
127
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
240
+ prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
241
+ ...(held.length === 0 ? {} : { held }),
128
242
  });
129
243
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
130
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
244
+ const envelope = classified[0] === null ? await stepped(parseEnvelope(answered.body)) : null;
131
245
  for (const [index, asked] of part.entries()) {
132
246
  const failure = classified[index];
133
247
  if (failure !== null && failure !== undefined) {
@@ -143,71 +257,79 @@ export function createAgentsHouseAdapter(options) {
143
257
  unreached.push(asked);
144
258
  continue;
145
259
  }
146
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.entry, null));
260
+ answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
147
261
  }
148
262
  }
149
263
  open = unreached;
150
264
  }
151
265
  }
152
- async function continuedBootstrap(entry, continued) {
153
- const after = { room: entry.authority, path: continued.after };
266
+ async function continuedBootstrap(asked, continued) {
267
+ const { entry } = asked;
268
+ const after = {
269
+ room: entry.authority,
270
+ path: continued.after,
271
+ ...(continued.cut === undefined ? {} : { cut: continued.cut }),
272
+ };
273
+ const held = await holding([asked], continued.after);
154
274
  const answered = await transport.door(DOOR_BOOTSTRAP, {
155
- prefixes: contentPrefixes(entry),
275
+ prefixes: [authorityRoot(entry.handle)],
156
276
  after,
277
+ ...(held.length === 0 ? {} : { held }),
157
278
  });
158
- const failure = classifyDoor(answered.status, answered.body, entry.locator);
279
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
159
280
  if (failure !== null)
160
281
  return failure;
161
- const envelope = parseEnvelope(answered.body);
282
+ const envelope = await stepped(parseEnvelope(answered.body));
162
283
  if (envelope === null)
163
284
  return UNCERTAIN_READ;
164
285
  const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
165
- return bootstrappedRoom(envelope, scope, entry, continued);
286
+ return bootstrappedRoom(envelope, scope, asked, continued);
166
287
  }
167
288
  function layered(asks) {
168
289
  const depths = new Map();
169
290
  const layers = [];
170
291
  for (const ask of asks) {
171
- const depth = depths.get(ask.entry.authority) ?? 0;
172
- depths.set(ask.entry.authority, depth + 1);
292
+ const depth = depths.get(ask.ask.entry.authority) ?? 0;
293
+ depths.set(ask.ask.entry.authority, depth + 1);
173
294
  (layers[depth] ??= []).push(ask);
174
295
  }
175
296
  return layers;
176
297
  }
177
298
  async function enumerated(asks) {
178
- const answers = new Map();
179
- const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
299
+ const answers = [];
300
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
180
301
  for (const part of parts) {
181
302
  let open = [...part];
182
303
  while (open.length > 0) {
183
304
  const answer = await transport.door(DOOR_ENUMERATE, {
184
- prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
185
- positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
305
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
306
+ positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
186
307
  });
187
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
308
+ const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
188
309
  if (classified[0]?.kind === 'beyond-horizon') {
189
310
  const named = doorRefusal(answer.body)?.fields.authority;
190
- const expired = open.filter((ask) => ask.entry.authority === named);
311
+ const expired = open.filter(({ ask }) => ask.entry.authority === named);
191
312
  const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
192
313
  const gone = expired.length === 0 ? open : expired;
193
314
  for (const ask of gone)
194
- answers.set(ask, answered);
315
+ answers[ask.index] = answered;
195
316
  open = open.filter((ask) => !gone.includes(ask));
196
317
  continue;
197
318
  }
198
319
  const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
199
- for (const [index, ask] of open.entries()) {
200
- const failure = classified[index];
320
+ for (const [answerIndex, { ask, index }] of open.entries()) {
321
+ const failure = classified[answerIndex];
201
322
  if (failure !== null && failure !== undefined) {
202
- answers.set(ask, failure);
323
+ answers[index] = failure;
203
324
  continue;
204
325
  }
205
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
326
+ answers[index] =
327
+ enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
206
328
  }
207
329
  open = [];
208
330
  }
209
331
  }
210
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
332
+ return answers;
211
333
  }
212
334
  const bootstrapTogether = gatheredEach(bootstrapped);
213
335
  const enumerateTogether = gathered(enumerated);
@@ -219,7 +341,9 @@ export function createAgentsHouseAdapter(options) {
219
341
  }
220
342
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
221
343
  if (minted.status !== 200) {
222
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
344
+ return {
345
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
346
+ };
223
347
  }
224
348
  const grant = parseGrant(minted.body);
225
349
  if (grant === null || grant.method !== 'POST')
@@ -261,7 +385,7 @@ export function createAgentsHouseAdapter(options) {
261
385
  }
262
386
  return true;
263
387
  }
264
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
388
+ async function submit(batch, head, changes, delta, bytes, entry, handed, keep, into) {
265
389
  const inline = { ...head, changes, runtime: notifications.runtime };
266
390
  const prepared = uploadable(inline, bytes, declared())
267
391
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -275,9 +399,14 @@ export function createAgentsHouseAdapter(options) {
275
399
  const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
276
400
  if (stagedFiles === true && (await staged(settled, batch)) !== null)
277
401
  return UNCERTAIN;
278
- return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
402
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
403
+ ? undefined
404
+ : {
405
+ source: { authority: entry.authority, handle: entry.handle },
406
+ destination: { authority: into.authority, handle: into.handle },
407
+ }));
279
408
  }
280
- async function downloaded(answer, locator) {
409
+ function granted(answer, locator) {
281
410
  const failure = classifyDoor(answer.status, answer.body, locator);
282
411
  if (failure !== null) {
283
412
  return failure.kind === 'scoped-refusal'
@@ -290,19 +419,40 @@ export function createAgentsHouseAdapter(options) {
290
419
  : failure;
291
420
  }
292
421
  const grant = parseGrant(answer.body);
293
- if (grant === null || grant.method !== 'GET')
422
+ const name = answer.body.name;
423
+ if (grant === null || grant.method !== 'GET' || typeof name !== 'string') {
294
424
  return UNCERTAIN_READ;
295
- const read = await transport.download(grant.url);
425
+ }
426
+ return { kind: 'link', url: grant.url, name };
427
+ }
428
+ async function downloaded(answer, locator) {
429
+ const link = granted(answer, locator);
430
+ if (link.kind !== 'link')
431
+ return link;
432
+ const read = await transport.download(link.url);
296
433
  if (read.status !== 200 || read.blob === null)
297
434
  return UNCERTAIN_READ;
298
435
  return { kind: 'binary', blob: read.blob };
299
436
  }
437
+ function pathGrant(request) {
438
+ const entry = authorityFor(request.locator);
439
+ if (entry === undefined)
440
+ return null;
441
+ return transport.door(DOOR_FILES_GET, {
442
+ room_ref: entry.authority,
443
+ path: request.path,
444
+ revision: request.revision,
445
+ });
446
+ }
300
447
  return {
301
448
  async authorize() {
302
449
  return options.authorize();
303
450
  },
304
- async discover(_authorization) {
305
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
451
+ async discover(_authorization, scope) {
452
+ const answer = await transport.door(DOOR_DISCOVER, {
453
+ prefixes: [...DISCOVER_PREFIXES],
454
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
455
+ });
306
456
  const failed = discoveryFailure(answer.status, answer.body);
307
457
  if (failed !== null)
308
458
  return failed;
@@ -311,26 +461,42 @@ export function createAgentsHouseAdapter(options) {
311
461
  return { kind: 'transport-uncertainty' };
312
462
  bounds = document.bounds;
313
463
  const entries = trackedFrom(document);
314
- tracked.clear();
464
+ const covered = scope === undefined
465
+ ? null
466
+ : [
467
+ ...new Set([
468
+ ...[...tracked.values()]
469
+ .filter((entry) => scope.authorities.includes(entry.authority))
470
+ .map((entry) => entry.locator),
471
+ ...entries.map((entry) => entry.locator),
472
+ ]),
473
+ ];
474
+ if (covered === null)
475
+ tracked.clear();
476
+ else
477
+ for (const locator of covered)
478
+ tracked.delete(locator);
315
479
  for (const entry of entries)
316
480
  tracked.set(entry.locator, entry);
317
481
  for (const locator of handed.keys())
318
482
  if (!tracked.has(locator))
319
483
  handed.delete(locator);
320
- notifications.track(entries);
484
+ notifications.track([...tracked.values()]);
321
485
  return {
322
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
486
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
323
487
  member: document.member,
488
+ ...(covered === null ? {} : { covered }),
324
489
  };
325
490
  },
326
491
  async bootstrap(_authorization, request) {
327
492
  const entry = authorityFor(request.locator);
328
493
  if (entry === undefined)
329
494
  return { kind: 'room-not-readable', locator: request.locator };
495
+ const asked = { entry, held: request.held };
330
496
  if (request.pageToken !== undefined) {
331
- return continuedBootstrap(entry, JSON.parse(request.pageToken));
497
+ return continuedBootstrap(asked, JSON.parse(request.pageToken));
332
498
  }
333
- return bootstrapTogether(entry);
499
+ return bootstrapTogether(asked);
334
500
  },
335
501
  async refresh(_authorization, request) {
336
502
  const entry = authorityFor(request.locator);
@@ -350,18 +516,27 @@ export function createAgentsHouseAdapter(options) {
350
516
  const entry = authorityFor(request.locator);
351
517
  if (entry === undefined)
352
518
  return { kind: 'room-not-readable', locator: request.locator };
353
- const mapped = houseChanges(request.changeBatch, entry.handle);
519
+ const batch = request.changeBatch;
520
+ const destination = batch.between?.destination.room;
521
+ const into = destination === undefined ? undefined : authorityFor(destination);
522
+ if (destination !== undefined && into === undefined) {
523
+ return { kind: 'room-not-readable', locator: destination };
524
+ }
525
+ const mapped = into === undefined
526
+ ? houseChanges(batch, entry.handle)
527
+ : houseMove(batch, entry.handle, into.handle);
354
528
  if (mapped.kind === 'unmapped')
355
529
  return rejectedMapping(mapped);
356
- const head = operationHead(request.changeBatch);
530
+ const head = operationHead(batch);
357
531
  const delta = deltaBody(mapped.changes);
358
532
  const bytes = deltaBytes(delta);
359
533
  const handed = request.upload;
360
- const batch = request.changeBatch;
361
- const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
534
+ const answer = (upload) => submit(batch, head, mapped.changes, delta, bytes, entry, upload, keep, into);
535
+ const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
536
+ const outcome = await answer(handed);
362
537
  if (handed === undefined || !lapsedGrant(outcome))
363
- return adopted(entry, outcome);
364
- return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
538
+ return settle(outcome);
539
+ return settle(await answer(undefined));
365
540
  },
366
541
  async history(_authorization, request) {
367
542
  const entry = authorityFor(request.locator);
@@ -380,15 +555,10 @@ export function createAgentsHouseAdapter(options) {
380
555
  },
381
556
  async binary(_authorization, request) {
382
557
  if (request.kind === 'path') {
383
- const entry = authorityFor(request.locator);
384
- if (entry === undefined)
558
+ const asked = pathGrant(request);
559
+ if (asked === null)
385
560
  return { kind: 'room-not-readable', locator: request.locator };
386
- const answer = await transport.door(DOOR_FILES_GET, {
387
- room_ref: entry.authority,
388
- path: request.path,
389
- revision: request.revision,
390
- });
391
- return downloaded(answer, request.locator);
561
+ return downloaded(await asked, request.locator);
392
562
  }
393
563
  const referrer = houseReferrer(request.referrer, tracked);
394
564
  if (referrer === null) {
@@ -400,6 +570,12 @@ export function createAgentsHouseAdapter(options) {
400
570
  });
401
571
  return downloaded(answer, referrer.locator);
402
572
  },
573
+ async link(_authorization, request) {
574
+ const asked = pathGrant(request);
575
+ if (asked === null)
576
+ return { kind: 'room-not-readable', locator: request.locator };
577
+ return granted(await asked, request.locator);
578
+ },
403
579
  referrerScope(referrer) {
404
580
  const held = houseReferrer(referrer, tracked);
405
581
  if (held === null)
@@ -418,7 +594,16 @@ export function createAgentsHouseAdapter(options) {
418
594
  async action(_authorization, request) {
419
595
  return withAt(await houseAction(transport, request));
420
596
  },
421
- roomStream: { hold: held, reopen: notifications.reopen, observe: notifications.observe },
597
+ roomStream: {
598
+ hold: held,
599
+ reopen: notifications.reopen,
600
+ observe: notifications.observe,
601
+ published(locator, roomVersion) {
602
+ const at = logPosition(roomVersion);
603
+ if (at !== null)
604
+ notifications.published(locator, at);
605
+ },
606
+ },
422
607
  conversations: notifications.conversations,
423
608
  serviceMode: notifications.serviceMode,
424
609
  };
@@ -13,15 +13,16 @@ export type HouseBounds = {
13
13
  max_inline_body_bytes: number;
14
14
  max_upload_bytes: number;
15
15
  max_prefixes: number;
16
+ max_held_files: number;
16
17
  change_retention_seconds: number;
17
18
  operation_retention_seconds: number;
19
+ max_move_documents?: number;
18
20
  };
19
21
  export type HouseOperations = {
20
- read: readonly string[];
21
- sync: readonly string[];
22
22
  write: readonly string[];
23
23
  delete: readonly string[];
24
24
  };
25
+ export type HouseReadLevel = 'mcp' | 'online' | 'local';
25
26
  export type HouseModel = {
26
27
  root: string;
27
28
  paths: readonly string[];
@@ -34,6 +35,7 @@ export type HouseAuthority = LogPosition & {
34
35
  houseModel: HouseModel | null;
35
36
  managedReplication: 'admitted' | 'unavailable';
36
37
  protectedPaths: readonly string[];
38
+ read: HouseReadLevel;
37
39
  operations: HouseOperations;
38
40
  };
39
41
  export type HouseDiscoveryDocument = {
@@ -65,7 +67,7 @@ export declare function siteTracked(document: HouseSiteDiscovery): TrackedAuthor
65
67
  export declare function trackedFrom(document: HouseDiscoveryDocument): TrackedAuthority[];
66
68
  export declare function providerLimits(bounds: HouseBounds): readonly ProviderLimit[];
67
69
  export declare function declaredRetention(bounds: HouseBounds): Retention;
68
- export declare function capabilities(document: HouseDiscoveryDocument): readonly Capability[];
70
+ export declare function capabilities(actions: HouseDiscoveryDocument['actions'], rooms: readonly Pick<TrackedAuthority, 'authority' | 'houseModel'>[]): readonly Capability[];
69
71
  export declare function siteCapabilities(): readonly Capability[];
70
72
  export declare function discoveredRoom(authority: HouseAuthority): DiscoveredRoom;
71
73
  export declare function mapDiscovery(document: Pick<HouseDiscoveryDocument, 'bounds'>, rooms: readonly DiscoveredRoom[], declared: readonly Capability[]): Discovery;
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,SAAS,EACT,MAAM,EACN,aAAa,EACb,SAAS,EACV,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3E,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAAU,CAAC;AAE1D,MAAM,MAAM,WAAW,GAAG;IACxB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,2BAA2B,EAAE,MAAM,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAExD,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,kBAAkB,EAAE,UAAU,GAAG,aAAa,CAAC;IAC/C,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,UAAU,EAAE,eAAe,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;IACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC5C,CAAC;AA8GF,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,sBAAsB,GAAG,IAAI,CAsB3E;AAmBD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAa3E;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,gBAAgB,EAAE,CAW5E;AAQD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,sBAAsB,GAAG,gBAAgB,EAAE,CAWhF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,aAAa,EAAE,CAa5E;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAMhE;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,sBAAsB,CAAC,SAAS,CAAC,EAC1C,KAAK,EAAE,SAAS,IAAI,CAAC,gBAAgB,EAAE,WAAW,GAAG,YAAY,CAAC,EAAE,GACnE,SAAS,UAAU,EAAE,CAWvB;AAED,wBAAgB,gBAAgB,IAAI,SAAS,UAAU,EAAE,CAOxD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,cAAc,GAAG,cAAc,CAqBxE;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,QAAQ,CAAC,EAChD,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,QAAQ,EAAE,SAAS,UAAU,EAAE,GAC9B,SAAS,CAQX"}