@agentshouse/mdruntime-adapter 0.1.0-ccd2ccb1f7214 → 0.1.0-ccda1a2da11c3

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,46 +1,48 @@
1
+ import { 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, wholeView, } from './read.js';
6
+ import { authorityRoot, bootstrapping, emptyScope, housePart, parseEnumeration, parseFileAddress, parseGrant, parseParted, refreshFrom, viewOf, wholeView, } from './read.js';
7
+ import { PART_ATTEMPTS, unverifiedPart } from './part.js';
6
8
  import { sha256Hex } from './digest.js';
7
- import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
8
9
  import { houseAction } from './actions.js';
9
10
  import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
10
11
  import { houseNotifications } from './stream.js';
11
12
  import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
12
- import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
- import { houseReferrer } from './referrer.js';
13
+ import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
14
+ import { houseReferrer, houseReferrerScope } from './referrer.js';
15
+ import { HOUSE_ROOM_TYPES } from './room-types.js';
14
16
  import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
15
17
  const UNCERTAIN = { kind: 'transport-uncertainty' };
16
18
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
19
+ const EXPIRED_ADDRESS = 403;
17
20
  function lapsedGrant(outcome) {
18
21
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
19
22
  }
20
- function heldFiles(authority, held, after, budget) {
21
- if (held === undefined || budget === 0)
22
- return null;
23
- const paths = Object.keys(held.revisions)
24
- .filter((path) => held.documents[path] !== undefined && (after === null || path > after))
25
- .sort();
26
- if (paths.length === 0)
27
- return null;
28
- const files = paths.slice(0, budget).map((path) => ({ path, revision: held.revisions[path] }));
29
- return { room: authority, through: paths.length > budget ? files.at(-1).path : null, files };
23
+ async function heldFiles(authority, held, bound) {
24
+ const listed = await held.files(null, bound + 1);
25
+ const files = listed.slice(0, bound);
26
+ const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
27
+ return files.length === 0 ? null : { room: authority, through, files };
30
28
  }
31
29
  async function reused(scope, held) {
32
30
  const sent = new Set(scope.contents.map((entry) => entry.path));
33
31
  const contents = [...scope.contents];
34
- for (const file of scope.files) {
35
- if (file.binary !== undefined || sent.has(file.path))
36
- continue;
37
- const content = held?.documents[file.path];
38
- if (content === undefined ||
39
- held?.revisions[file.path] !== file.revision ||
40
- (await sha256Hex(content)) !== file.sha256) {
32
+ const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
33
+ if (asked.length === 0)
34
+ return scope;
35
+ if (held === undefined)
36
+ return null;
37
+ const kept = await held.contents(asked.map(({ path }) => path));
38
+ for (const file of asked) {
39
+ const found = kept.get(file.path);
40
+ if (found === undefined ||
41
+ found.revision !== file.revision ||
42
+ (await sha256Hex(found.content)) !== file.sha256) {
41
43
  return null;
42
44
  }
43
- contents.push({ path: file.path, content });
45
+ contents.push({ path: file.path, content: found.content });
44
46
  }
45
47
  return { ...scope, contents };
46
48
  }
@@ -51,48 +53,64 @@ export function createAgentsHouseAdapter(options) {
51
53
  const notifications = houseNotifications(transport, 'credential');
52
54
  const tracked = new Map();
53
55
  const handed = new Map();
56
+ let heldRevisions;
54
57
  let bounds;
55
58
  function declared() {
56
59
  if (bounds === undefined)
57
60
  throw new Error('no Discovery declared the bounds');
58
61
  return bounds;
59
62
  }
60
- async function bootstrappedRoom(envelope, sent, asked, continued) {
63
+ async function fetchedPart(entry, continued, listed) {
64
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
65
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
66
+ prefixes: [authorityRoot(entry.handle)],
67
+ part: listed.part,
68
+ ...(continued.held === undefined ? {} : { held: [continued.held] }),
69
+ });
70
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
71
+ if (failure !== null)
72
+ return failure;
73
+ const scope = await housePart(continued.room, answered.body, listed, 'credential');
74
+ if (scope !== null)
75
+ return scope;
76
+ }
77
+ return unverifiedPart(entry.locator);
78
+ }
79
+ async function partPage(asked, continued) {
61
80
  const { entry } = asked;
62
- if (sent === undefined)
63
- return { kind: 'room-not-readable', locator: entry.locator };
81
+ const listed = continued.room.parts[continued.next];
82
+ const sent = listed === undefined
83
+ ? emptyScope(continued.room)
84
+ : await fetchedPart(entry, continued, listed);
85
+ if ('kind' in sent)
86
+ return sent;
64
87
  const scope = await reused(sent, asked.held);
65
88
  if (scope === null) {
66
89
  return asked.held === undefined
67
90
  ? UNCERTAIN_READ
68
- : continued === null
69
- ? bootstrapTogether({ entry, held: undefined })
70
- : continuedBootstrap({ entry, held: undefined }, continued);
91
+ : bootstrapTogether({ entry, held: undefined });
71
92
  }
72
- const cursor = envelope.next?.room === entry.authority ? envelope.next : undefined;
73
- const next = cursor?.path ?? null;
74
- if (continued === null && next === null)
75
- handed.set(entry.locator, scopeHanded(scope));
93
+ const next = continued.next + 1;
94
+ const more = next < continued.room.parts.length;
95
+ if (continued.next === 0 && !more)
96
+ handed.set(entry.locator, roomVersion(scope));
76
97
  else
77
98
  handed.delete(entry.locator);
78
99
  notifications.reach(entry.authority, scope);
79
100
  const seen = [
80
- ...(continued?.seen ?? []),
101
+ ...continued.seen,
81
102
  ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
82
103
  ];
83
- const models = await partModels(scope, entry, new Set(seen), next === null);
84
- const continuation = next === null
85
- ? null
86
- : { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
104
+ const models = await partModels(scope, entry, new Set(seen), !more);
87
105
  return {
88
- ...bootstrapOf(scope),
106
+ ...(await stepped(bootstrapping(scope))),
89
107
  ...(models === undefined ? {} : { models }),
90
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
108
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
91
109
  };
92
110
  }
93
111
  async function enumeratedPage(ask, page) {
94
112
  const answer = await transport.door(DOOR_ENUMERATE, {
95
- prefixes: contentPrefixes(ask.entry),
113
+ prefixes: [authorityRoot(ask.entry.handle)],
96
114
  positions: [
97
115
  {
98
116
  ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
@@ -116,21 +134,24 @@ export function createAgentsHouseAdapter(options) {
116
134
  if ('kind' in view)
117
135
  return view;
118
136
  notifications.reach(ask.entry.authority, view);
119
- const answer = refreshFrom(view, ask.fromRoomVersion);
137
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
120
138
  if (answer.kind !== 'net-advancement')
121
139
  return answer;
122
- handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
140
+ handed.set(ask.entry.locator, roomVersion(view));
123
141
  const models = await viewModels(view, ask.entry);
124
142
  return models === undefined ? answer : { ...answer, models };
125
143
  }
126
144
  async function streamed(entry, request, from) {
127
- const held = handed.get(entry.locator);
128
- if (held?.roomVersion !== request.fromRoomVersion)
145
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
129
146
  return null;
130
- const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
147
+ const asked = notifications.edited(entry, from);
148
+ const kept = asked.length === 0 || heldRevisions === undefined
149
+ ? new Map()
150
+ : await heldRevisions(entry.locator, asked);
151
+ const advanced = await stepped(notifications.advance(entry, from, (path, revision) => kept.get(path) === revision));
131
152
  if (advanced === null)
132
153
  return null;
133
- handed.set(entry.locator, streamedHanded(held, advanced));
154
+ handed.set(entry.locator, advanced.page.roomVersion);
134
155
  const models = await streamedModels(advanced.schemas, entry, 'credential');
135
156
  return models === undefined ? advanced.page : { ...advanced.page, models };
136
157
  }
@@ -140,20 +161,27 @@ export function createAgentsHouseAdapter(options) {
140
161
  const at = logPosition(room.roomVersion);
141
162
  if (entry === undefined || at === null)
142
163
  return [];
143
- handed.set(room.locator, heldHanded(room));
164
+ handed.set(room.locator, room.roomVersion);
144
165
  notifications.reach(entry.authority, at);
145
166
  return [presented(entry.authority, at)];
146
167
  });
147
168
  notifications.send(kept);
148
169
  }
149
- function adopted(entry, outcome) {
170
+ async function adoptedMove(entry, into, outcome) {
171
+ if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
172
+ await adopted(into, { kind: 'accepted', ...outcome.destination });
173
+ }
174
+ return adopted(entry, outcome);
175
+ }
176
+ async function adopted(entry, outcome) {
150
177
  if (outcome.kind !== 'accepted')
151
178
  return outcome;
152
179
  const held = handed.get(entry.locator);
153
180
  if (held === undefined)
154
181
  return outcome;
155
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
156
- handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
182
+ const follows = followsVersion(held, outcome.roomVersion);
183
+ if (follows)
184
+ handed.set(entry.locator, outcome.roomVersion);
157
185
  const at = logPosition(outcome.roomVersion);
158
186
  if (follows && at !== null)
159
187
  notifications.reach(entry.authority, at);
@@ -176,68 +204,61 @@ export function createAgentsHouseAdapter(options) {
176
204
  return result;
177
205
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
178
206
  }
179
- function weight(entry) {
180
- return contentPrefixes(entry).length;
181
- }
182
- function holding(asks, after) {
183
- const budget = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
184
- return asks.flatMap((ask) => heldFiles(ask.entry.authority, ask.held, after, budget) ?? []);
207
+ async function holding(asks) {
208
+ const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
209
+ const held = [];
210
+ for (const ask of asks) {
211
+ if (ask.held === undefined || bound === 0)
212
+ continue;
213
+ const files = await heldFiles(ask.entry.authority, ask.held, bound);
214
+ if (files !== null)
215
+ held.push(files);
216
+ }
217
+ return held;
185
218
  }
186
219
  async function bootstrapped(asks, answer) {
187
220
  let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
188
221
  while (open.length > 0) {
189
222
  const unreached = [];
190
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
191
- const held = holding(part.map(({ asked }) => asked), null);
223
+ for (const part of selections(open, declared().max_prefixes)) {
224
+ const held = await holding(part.map(({ asked }) => asked));
192
225
  const answered = await transport.door(DOOR_BOOTSTRAP, {
193
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
226
+ prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
194
227
  ...(held.length === 0 ? {} : { held }),
195
228
  });
196
229
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
197
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
230
+ const parted = classified[0] === null ? parseParted(answered.body) : null;
198
231
  for (const [index, asked] of part.entries()) {
199
232
  const failure = classified[index];
200
233
  if (failure !== null && failure !== undefined) {
201
234
  answer(asked.ask, failure);
202
235
  continue;
203
236
  }
204
- if (envelope === null) {
237
+ if (parted === null) {
205
238
  answer(asked.ask, UNCERTAIN_READ);
206
239
  continue;
207
240
  }
208
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
209
- if (scope === undefined && envelope.next !== undefined) {
241
+ const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
242
+ if (room === undefined && parted.next !== undefined) {
210
243
  unreached.push(asked);
211
244
  continue;
212
245
  }
213
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
246
+ if (room === undefined) {
247
+ answer(asked.ask, { kind: 'room-not-readable', locator: asked.entry.locator });
248
+ continue;
249
+ }
250
+ const kept = held.find((files) => files.room === asked.entry.authority);
251
+ answer(asked.ask, await partPage(asked.asked, {
252
+ room,
253
+ next: 0,
254
+ seen: [],
255
+ ...(kept === undefined ? {} : { held: kept }),
256
+ }));
214
257
  }
215
258
  }
216
259
  open = unreached;
217
260
  }
218
261
  }
219
- async function continuedBootstrap(asked, continued) {
220
- const { entry } = asked;
221
- const after = {
222
- room: entry.authority,
223
- path: continued.after,
224
- ...(continued.cut === undefined ? {} : { cut: continued.cut }),
225
- };
226
- const held = holding([asked], continued.after);
227
- const answered = await transport.door(DOOR_BOOTSTRAP, {
228
- prefixes: contentPrefixes(entry),
229
- after,
230
- ...(held.length === 0 ? {} : { held }),
231
- });
232
- const failure = classifyRead(answered.status, answered.body, entry.locator);
233
- if (failure !== null)
234
- return failure;
235
- const envelope = parseEnvelope(answered.body);
236
- if (envelope === null)
237
- return UNCERTAIN_READ;
238
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
239
- return bootstrappedRoom(envelope, scope, asked, continued);
240
- }
241
262
  function layered(asks) {
242
263
  const depths = new Map();
243
264
  const layers = [];
@@ -250,12 +271,12 @@ export function createAgentsHouseAdapter(options) {
250
271
  }
251
272
  async function enumerated(asks) {
252
273
  const answers = [];
253
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
274
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
254
275
  for (const part of parts) {
255
276
  let open = [...part];
256
277
  while (open.length > 0) {
257
278
  const answer = await transport.door(DOOR_ENUMERATE, {
258
- prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
279
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
259
280
  positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
260
281
  });
261
282
  const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
@@ -294,10 +315,12 @@ export function createAgentsHouseAdapter(options) {
294
315
  }
295
316
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
296
317
  if (minted.status !== 200) {
297
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
318
+ return {
319
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
320
+ };
298
321
  }
299
322
  const grant = parseGrant(minted.body);
300
- if (grant === null || grant.method !== 'POST')
323
+ if (grant === null)
301
324
  return { outcome: UNCERTAIN };
302
325
  const delivered = await transport.transfer({
303
326
  act: 'authored_batch_upload',
@@ -307,13 +330,13 @@ export function createAgentsHouseAdapter(options) {
307
330
  if (delivered.status !== 200)
308
331
  return { outcome: UNCERTAIN };
309
332
  const holding = parseGrant(delivered.body);
310
- if (holding === null || holding.method !== 'POST')
333
+ if (holding === null)
311
334
  return { outcome: UNCERTAIN };
312
335
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
313
336
  await keep(upload);
314
337
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
315
338
  }
316
- async function staged(answer, batch) {
339
+ async function staged(answer, { changeBatch: batch, file }) {
317
340
  const refusal = doorRefusal(answer.body);
318
341
  if (refusal?.code !== 'files_unstaged')
319
342
  return null;
@@ -328,7 +351,7 @@ export function createAgentsHouseAdapter(options) {
328
351
  const delivered = await transport.transfer({
329
352
  act: 'binary_stage',
330
353
  url,
331
- body: change.blob,
354
+ body: await file(change.localId),
332
355
  operation,
333
356
  });
334
357
  if (delivered.status !== 200)
@@ -336,7 +359,7 @@ export function createAgentsHouseAdapter(options) {
336
359
  }
337
360
  return true;
338
361
  }
339
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
362
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
340
363
  const inline = { ...head, changes, runtime: notifications.runtime };
341
364
  const prepared = uploadable(inline, bytes, declared())
342
365
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -344,15 +367,20 @@ export function createAgentsHouseAdapter(options) {
344
367
  if ('outcome' in prepared)
345
368
  return prepared.outcome;
346
369
  const answer = await transport.door(DOOR_BATCH, prepared.body);
347
- const stagedFiles = await staged(answer, batch);
370
+ const stagedFiles = await staged(answer, request);
348
371
  if (stagedFiles === false)
349
372
  return UNCERTAIN;
350
373
  const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
351
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
374
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
352
375
  return UNCERTAIN;
353
- return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
376
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
377
+ ? undefined
378
+ : {
379
+ source: { authority: entry.authority, handle: entry.handle },
380
+ destination: { authority: into.authority, handle: into.handle },
381
+ }));
354
382
  }
355
- function granted(answer, locator) {
383
+ function addressed(answer, locator) {
356
384
  const failure = classifyDoor(answer.status, answer.body, locator);
357
385
  if (failure !== null) {
358
386
  return failure.kind === 'scoped-refusal'
@@ -364,33 +392,47 @@ export function createAgentsHouseAdapter(options) {
364
392
  }
365
393
  : failure;
366
394
  }
367
- const grant = parseGrant(answer.body);
368
- const name = answer.body.name;
369
- if (grant === null || grant.method !== 'GET' || typeof name !== 'string') {
370
- return UNCERTAIN_READ;
371
- }
372
- return { kind: 'link', url: grant.url, name };
395
+ const address = parseFileAddress(answer.body);
396
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
397
+ }
398
+ function linked(answer, locator) {
399
+ const address = addressed(answer, locator);
400
+ return address.kind === 'address'
401
+ ? { kind: 'link', url: address.url, name: address.name }
402
+ : address;
373
403
  }
374
- async function downloaded(answer, locator) {
375
- const link = granted(answer, locator);
376
- if (link.kind !== 'link')
377
- return link;
378
- const read = await transport.download(link.url);
379
- if (read.status !== 200 || read.blob === null)
404
+ async function downloaded(ask, locator) {
405
+ let address = addressed(await ask(), locator);
406
+ if (address.kind !== 'address')
407
+ return address;
408
+ let read = await transport.download(address.url);
409
+ if (read.status === EXPIRED_ADDRESS) {
410
+ address = addressed(await ask(), locator);
411
+ if (address.kind !== 'address')
412
+ return address;
413
+ read = await transport.download(address.url);
414
+ }
415
+ if (read.status !== 200 || read.body === null)
380
416
  return UNCERTAIN_READ;
381
- return { kind: 'binary', blob: read.blob };
417
+ return {
418
+ kind: 'binary',
419
+ body: read.body,
420
+ mediaType: address.mediaType,
421
+ sha256: address.sha256,
422
+ };
382
423
  }
383
- function pathGrant(request) {
424
+ function pathAddress(request) {
384
425
  const entry = authorityFor(request.locator);
385
426
  if (entry === undefined)
386
427
  return null;
387
- return transport.door(DOOR_FILES_GET, {
428
+ return () => transport.door(DOOR_FILES_GET, {
388
429
  room_ref: entry.authority,
389
430
  path: request.path,
390
431
  revision: request.revision,
391
432
  });
392
433
  }
393
434
  return {
435
+ roomTypes: HOUSE_ROOM_TYPES,
394
436
  async authorize() {
395
437
  return options.authorize();
396
438
  },
@@ -440,7 +482,7 @@ export function createAgentsHouseAdapter(options) {
440
482
  return { kind: 'room-not-readable', locator: request.locator };
441
483
  const asked = { entry, held: request.held };
442
484
  if (request.pageToken !== undefined) {
443
- return continuedBootstrap(asked, JSON.parse(request.pageToken));
485
+ return partPage(asked, JSON.parse(request.pageToken));
444
486
  }
445
487
  return bootstrapTogether(asked);
446
488
  },
@@ -462,18 +504,27 @@ export function createAgentsHouseAdapter(options) {
462
504
  const entry = authorityFor(request.locator);
463
505
  if (entry === undefined)
464
506
  return { kind: 'room-not-readable', locator: request.locator };
465
- const mapped = houseChanges(request.changeBatch, entry.handle);
507
+ const batch = request.changeBatch;
508
+ const destination = batch.between?.destination.room;
509
+ const into = destination === undefined ? undefined : authorityFor(destination);
510
+ if (destination !== undefined && into === undefined) {
511
+ return { kind: 'room-not-readable', locator: destination };
512
+ }
513
+ const mapped = into === undefined
514
+ ? houseChanges(batch, entry.handle)
515
+ : houseMove(batch, entry.handle, into.handle);
466
516
  if (mapped.kind === 'unmapped')
467
517
  return rejectedMapping(mapped);
468
- const head = operationHead(request.changeBatch);
518
+ const head = operationHead(batch);
469
519
  const delta = deltaBody(mapped.changes);
470
520
  const bytes = deltaBytes(delta);
471
521
  const handed = request.upload;
472
- const batch = request.changeBatch;
473
- const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
522
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
523
+ const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
524
+ const outcome = await answer(handed);
474
525
  if (handed === undefined || !lapsedGrant(outcome))
475
- return adopted(entry, outcome);
476
- return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
526
+ return settle(outcome);
527
+ return settle(await answer(undefined));
477
528
  },
478
529
  async history(_authorization, request) {
479
530
  const entry = authorityFor(request.locator);
@@ -492,35 +543,25 @@ export function createAgentsHouseAdapter(options) {
492
543
  },
493
544
  async binary(_authorization, request) {
494
545
  if (request.kind === 'path') {
495
- const asked = pathGrant(request);
496
- if (asked === null)
546
+ const ask = pathAddress(request);
547
+ if (ask === null)
497
548
  return { kind: 'room-not-readable', locator: request.locator };
498
- return downloaded(await asked, request.locator);
549
+ return downloaded(ask, request.locator);
499
550
  }
500
551
  const referrer = houseReferrer(request.referrer, tracked);
501
552
  if (referrer === null) {
502
553
  return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
503
554
  }
504
- const answer = await transport.door(DOOR_FILES_GET, {
505
- file: request.fileRef,
506
- referrer: referrer.referrer,
507
- });
508
- return downloaded(answer, referrer.locator);
555
+ return downloaded(() => transport.door(DOOR_FILES_GET, { file: request.fileRef, referrer: referrer.referrer }), referrer.locator);
509
556
  },
510
557
  async link(_authorization, request) {
511
- const asked = pathGrant(request);
512
- if (asked === null)
558
+ const ask = pathAddress(request);
559
+ if (ask === null)
513
560
  return { kind: 'room-not-readable', locator: request.locator };
514
- return granted(await asked, request.locator);
561
+ return linked(await ask(), request.locator);
515
562
  },
516
563
  referrerScope(referrer) {
517
- const held = houseReferrer(referrer, tracked);
518
- if (held === null)
519
- return null;
520
- if (held.locator === '')
521
- return { kind: 'conversations' };
522
- const { path } = held.referrer;
523
- return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
564
+ return houseReferrerScope(referrer, tracked);
524
565
  },
525
566
  reached(locator, version, target) {
526
567
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -534,7 +575,10 @@ export function createAgentsHouseAdapter(options) {
534
575
  roomStream: {
535
576
  hold: held,
536
577
  reopen: notifications.reopen,
537
- observe: notifications.observe,
578
+ observe(notify, receipts, revisions) {
579
+ heldRevisions = revisions;
580
+ return notifications.observe(notify, receipts);
581
+ },
538
582
  published(locator, roomVersion) {
539
583
  const at = logPosition(roomVersion);
540
584
  if (at !== null)
@@ -16,13 +16,13 @@ export type HouseBounds = {
16
16
  max_held_files: number;
17
17
  change_retention_seconds: number;
18
18
  operation_retention_seconds: number;
19
+ max_move_documents?: number;
19
20
  };
20
21
  export type HouseOperations = {
21
- read: readonly string[];
22
- sync: readonly string[];
23
22
  write: readonly string[];
24
23
  delete: readonly string[];
25
24
  };
25
+ export type HouseReadLevel = 'mcp' | 'online' | 'local';
26
26
  export type HouseModel = {
27
27
  root: string;
28
28
  paths: readonly string[];
@@ -35,6 +35,7 @@ export type HouseAuthority = LogPosition & {
35
35
  houseModel: HouseModel | null;
36
36
  managedReplication: 'admitted' | 'unavailable';
37
37
  protectedPaths: readonly string[];
38
+ read: HouseReadLevel;
38
39
  operations: HouseOperations;
39
40
  };
40
41
  export type HouseDiscoveryDocument = {
@@ -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":"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"}