@agentshouse/mdruntime-adapter 0.1.0-cd61dfca5d699 → 0.1.0-cda4dff0d1d23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +14 -21
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +83 -133
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +212 -163
  7. package/dist/app-reference.d.ts +2 -0
  8. package/dist/app-reference.d.ts.map +1 -0
  9. package/dist/app-reference.js +2 -0
  10. package/dist/crockford.d.ts +4 -0
  11. package/dist/crockford.d.ts.map +1 -0
  12. package/dist/crockford.js +3 -0
  13. package/dist/discovery.d.ts +6 -10
  14. package/dist/discovery.d.ts.map +1 -1
  15. package/dist/discovery.js +34 -29
  16. package/dist/gather.d.ts +1 -1
  17. package/dist/gather.d.ts.map +1 -1
  18. package/dist/gather.js +4 -14
  19. package/dist/house-model.d.ts +2 -0
  20. package/dist/house-model.d.ts.map +1 -0
  21. package/dist/house-model.js +443 -0
  22. package/dist/index.d.ts +6 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -1
  25. package/dist/model.d.ts +5 -1
  26. package/dist/model.d.ts.map +1 -1
  27. package/dist/model.js +11 -13
  28. package/dist/part.d.ts +15 -0
  29. package/dist/part.d.ts.map +1 -0
  30. package/dist/part.js +82 -0
  31. package/dist/read.d.ts +57 -26
  32. package/dist/read.d.ts.map +1 -1
  33. package/dist/read.js +268 -152
  34. package/dist/room-types.d.ts +7 -0
  35. package/dist/room-types.d.ts.map +1 -0
  36. package/dist/room-types.js +124 -0
  37. package/dist/site.d.ts.map +1 -1
  38. package/dist/site.js +78 -104
  39. package/dist/stream.d.ts +3 -3
  40. package/dist/stream.d.ts.map +1 -1
  41. package/dist/stream.js +38 -35
  42. package/dist/transport.d.ts +10 -10
  43. package/dist/transport.d.ts.map +1 -1
  44. package/dist/transport.js +1 -1
  45. package/dist/upload.d.ts +12 -0
  46. package/dist/upload.d.ts.map +1 -0
  47. package/dist/upload.js +63 -0
  48. package/dist/write.d.ts +12 -1
  49. package/dist/write.d.ts.map +1 -1
  50. package/dist/write.js +92 -55
  51. package/package.json +8 -4
  52. package/AGENTS.md +0 -299
  53. package/dist/handed.d.ts +0 -15
  54. package/dist/handed.d.ts.map +0 -1
  55. package/dist/handed.js +0 -38
  56. package/dist/referrer.d.ts +0 -7
  57. package/dist/referrer.d.ts.map +0 -1
  58. package/dist/referrer.js +0 -35
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
- import { partModels, streamedModels, viewModels } from './model.js';
3
+ import { manifestedModels, 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, manifested, parseEnumeration, parseFileAddress, parseGrant, parseParted, partInventory, refreshFrom, viewOf, wholeView, } from './read.js';
7
+ import { PART_ATTEMPTS, partArrival, partFrame, unverifiedPart } from './part.js';
6
8
  import { sha256Hex } from './digest.js';
7
- import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
9
+ import { finishedUpload, houseUpload } from './upload.js';
8
10
  import { houseAction } from './actions.js';
9
11
  import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
10
12
  import { houseNotifications } from './stream.js';
11
13
  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';
14
+ import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
15
+ import { HOUSE_NAMEABLE_ROOM_TYPES, 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,83 @@ 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, received, arriving) {
64
+ if (received !== undefined) {
65
+ const kept = new Uint8Array(received);
66
+ const held = await housePart(continued.room, kept, listed, 'credential');
67
+ if (held !== null)
68
+ return 'kind' in held ? held : { scope: held, frame: kept };
69
+ }
70
+ for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
71
+ const answered = await transport.door(DOOR_BOOTSTRAP, {
72
+ prefixes: [authorityRoot(entry.handle)],
73
+ part: listed.part,
74
+ ...(continued.held === undefined ? {} : { held: [continued.held] }),
75
+ }, partArrival(listed, arriving));
76
+ const failure = classifyRead(answered.status, answered.body, entry.locator);
77
+ if (failure !== null)
78
+ return failure;
79
+ const frame = partFrame(answered.body);
80
+ if (frame === null)
81
+ continue;
82
+ const scope = await housePart(continued.room, frame, listed, 'credential');
83
+ if (scope !== null)
84
+ return 'kind' in scope ? scope : { scope, frame };
85
+ }
86
+ return unverifiedPart(entry.locator);
87
+ }
88
+ async function manifestPage(asked, continued) {
89
+ const inventory = partInventory(continued.room);
90
+ if (inventory.parts === 0) {
91
+ const page = await partPage(asked, continued, undefined, undefined);
92
+ return page.kind === 'bootstrap' ? { ...page, inventory } : page;
93
+ }
94
+ const { models, seen } = await manifestedModels(continued.room, asked.entry);
95
+ return manifested(continued.room, inventory, JSON.stringify({ ...continued, seen }), models);
96
+ }
97
+ async function partPage(asked, continued, received, arriving) {
61
98
  const { entry } = asked;
62
- if (sent === undefined)
63
- return { kind: 'room-not-readable', locator: entry.locator };
64
- const scope = await reused(sent, asked.held);
99
+ const listed = continued.room.parts[continued.next];
100
+ const sent = listed === undefined
101
+ ? { scope: emptyScope(continued.room), frame: null }
102
+ : await fetchedPart(entry, continued, listed, received, arriving);
103
+ if ('kind' in sent)
104
+ return sent;
105
+ const scope = await reused(sent.scope, asked.held);
65
106
  if (scope === null) {
66
107
  return asked.held === undefined
67
108
  ? UNCERTAIN_READ
68
- : continued === null
69
- ? bootstrapTogether({ entry, held: undefined })
70
- : continuedBootstrap({ entry, held: undefined }, continued);
109
+ : bootstrapTogether({ entry, held: undefined });
71
110
  }
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));
111
+ const next = continued.next + 1;
112
+ const more = next < continued.room.parts.length;
113
+ if (continued.next === 0 && !more)
114
+ handed.set(entry.locator, roomVersion(scope));
76
115
  else
77
116
  handed.delete(entry.locator);
78
117
  notifications.reach(entry.authority, scope);
79
118
  const seen = [
80
- ...(continued?.seen ?? []),
81
- ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
119
+ ...continued.seen,
120
+ ...(scope.files.some((file) => file.path === entry.schema) ? [entry.schema] : []),
82
121
  ];
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 }) };
122
+ const models = await partModels(scope, entry, new Set(seen), !more);
87
123
  return {
88
- ...bootstrapOf(scope),
124
+ ...(await stepped(bootstrapping(scope))),
89
125
  ...(models === undefined ? {} : { models }),
90
- ...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
126
+ ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
127
+ ...(sent.frame === null ? {} : { received: sent.frame }),
91
128
  };
92
129
  }
93
130
  async function enumeratedPage(ask, page) {
94
131
  const answer = await transport.door(DOOR_ENUMERATE, {
95
- prefixes: contentPrefixes(ask.entry),
132
+ prefixes: [authorityRoot(ask.entry.handle)],
96
133
  positions: [
97
134
  {
98
135
  ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
@@ -116,21 +153,24 @@ export function createAgentsHouseAdapter(options) {
116
153
  if ('kind' in view)
117
154
  return view;
118
155
  notifications.reach(ask.entry.authority, view);
119
- const answer = refreshFrom(view, ask.fromRoomVersion);
156
+ const answer = await refreshFrom(view, ask.fromRoomVersion);
120
157
  if (answer.kind !== 'net-advancement')
121
158
  return answer;
122
- handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
159
+ handed.set(ask.entry.locator, roomVersion(view));
123
160
  const models = await viewModels(view, ask.entry);
124
161
  return models === undefined ? answer : { ...answer, models };
125
162
  }
126
163
  async function streamed(entry, request, from) {
127
- const held = handed.get(entry.locator);
128
- if (held?.roomVersion !== request.fromRoomVersion)
164
+ if (handed.get(entry.locator) !== request.fromRoomVersion)
129
165
  return null;
130
- const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
166
+ const asked = notifications.edited(entry, from);
167
+ const kept = asked.length === 0 || heldRevisions === undefined
168
+ ? new Map()
169
+ : await heldRevisions(entry.locator, asked);
170
+ const advanced = await stepped(notifications.advance(entry, from, (path, revision) => kept.get(path) === revision));
131
171
  if (advanced === null)
132
172
  return null;
133
- handed.set(entry.locator, streamedHanded(held, advanced));
173
+ handed.set(entry.locator, advanced.page.roomVersion);
134
174
  const models = await streamedModels(advanced.schemas, entry, 'credential');
135
175
  return models === undefined ? advanced.page : { ...advanced.page, models };
136
176
  }
@@ -140,20 +180,27 @@ export function createAgentsHouseAdapter(options) {
140
180
  const at = logPosition(room.roomVersion);
141
181
  if (entry === undefined || at === null)
142
182
  return [];
143
- handed.set(room.locator, heldHanded(room));
183
+ handed.set(room.locator, room.roomVersion);
144
184
  notifications.reach(entry.authority, at);
145
185
  return [presented(entry.authority, at)];
146
186
  });
147
187
  notifications.send(kept);
148
188
  }
149
- function adopted(entry, outcome) {
189
+ async function adoptedMove(entry, into, outcome) {
190
+ if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
191
+ await adopted(into, { kind: 'accepted', ...outcome.destination });
192
+ }
193
+ return adopted(entry, outcome);
194
+ }
195
+ async function adopted(entry, outcome) {
150
196
  if (outcome.kind !== 'accepted')
151
197
  return outcome;
152
198
  const held = handed.get(entry.locator);
153
199
  if (held === undefined)
154
200
  return outcome;
155
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
156
- handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
201
+ const follows = followsVersion(held, outcome.roomVersion);
202
+ if (follows)
203
+ handed.set(entry.locator, outcome.roomVersion);
157
204
  const at = logPosition(outcome.roomVersion);
158
205
  if (follows && at !== null)
159
206
  notifications.reach(entry.authority, at);
@@ -176,68 +223,61 @@ export function createAgentsHouseAdapter(options) {
176
223
  return result;
177
224
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
178
225
  }
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) ?? []);
226
+ async function holding(asks) {
227
+ const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
228
+ const held = [];
229
+ for (const ask of asks) {
230
+ if (ask.held === undefined || bound === 0)
231
+ continue;
232
+ const files = await heldFiles(ask.entry.authority, ask.held, bound);
233
+ if (files !== null)
234
+ held.push(files);
235
+ }
236
+ return held;
185
237
  }
186
238
  async function bootstrapped(asks, answer) {
187
239
  let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
188
240
  while (open.length > 0) {
189
241
  const unreached = [];
190
- for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
191
- const held = holding(part.map(({ asked }) => asked), null);
242
+ for (const part of selections(open, declared().max_prefixes)) {
243
+ const held = await holding(part.map(({ asked }) => asked));
192
244
  const answered = await transport.door(DOOR_BOOTSTRAP, {
193
- prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
245
+ prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
194
246
  ...(held.length === 0 ? {} : { held }),
195
247
  });
196
248
  const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
197
- const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
249
+ const parted = classified[0] === null ? parseParted(answered.body) : null;
198
250
  for (const [index, asked] of part.entries()) {
199
251
  const failure = classified[index];
200
252
  if (failure !== null && failure !== undefined) {
201
253
  answer(asked.ask, failure);
202
254
  continue;
203
255
  }
204
- if (envelope === null) {
256
+ if (parted === null) {
205
257
  answer(asked.ask, UNCERTAIN_READ);
206
258
  continue;
207
259
  }
208
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
209
- if (scope === undefined && envelope.next !== undefined) {
260
+ const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
261
+ if (room === undefined && parted.next !== undefined) {
210
262
  unreached.push(asked);
211
263
  continue;
212
264
  }
213
- answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
265
+ if (room === undefined) {
266
+ answer(asked.ask, { kind: 'room-not-readable', locator: asked.entry.locator });
267
+ continue;
268
+ }
269
+ const kept = held.find((files) => files.room === asked.entry.authority);
270
+ answer(asked.ask, await manifestPage(asked.asked, {
271
+ room,
272
+ next: 0,
273
+ seen: [],
274
+ ...(kept === undefined ? {} : { held: kept }),
275
+ }));
214
276
  }
215
277
  }
216
278
  open = unreached;
217
279
  }
218
280
  }
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
281
  function layered(asks) {
242
282
  const depths = new Map();
243
283
  const layers = [];
@@ -250,12 +290,12 @@ export function createAgentsHouseAdapter(options) {
250
290
  }
251
291
  async function enumerated(asks) {
252
292
  const answers = [];
253
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
293
+ const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
254
294
  for (const part of parts) {
255
295
  let open = [...part];
256
296
  while (open.length > 0) {
257
297
  const answer = await transport.door(DOOR_ENUMERATE, {
258
- prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
298
+ prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
259
299
  positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
260
300
  });
261
301
  const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
@@ -294,10 +334,12 @@ export function createAgentsHouseAdapter(options) {
294
334
  }
295
335
  const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
296
336
  if (minted.status !== 200) {
297
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
337
+ return {
338
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
339
+ };
298
340
  }
299
341
  const grant = parseGrant(minted.body);
300
- if (grant === null || grant.method !== 'POST')
342
+ if (grant === null)
301
343
  return { outcome: UNCERTAIN };
302
344
  const delivered = await transport.transfer({
303
345
  act: 'authored_batch_upload',
@@ -307,36 +349,31 @@ export function createAgentsHouseAdapter(options) {
307
349
  if (delivered.status !== 200)
308
350
  return { outcome: UNCERTAIN };
309
351
  const holding = parseGrant(delivered.body);
310
- if (holding === null || holding.method !== 'POST')
352
+ if (holding === null)
311
353
  return { outcome: UNCERTAIN };
312
354
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
313
355
  await keep(upload);
314
356
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
315
357
  }
316
- async function staged(answer, batch) {
358
+ async function staged(answer, { changeBatch: batch, file }) {
317
359
  const refusal = doorRefusal(answer.body);
318
360
  if (refusal?.code !== 'files_unstaged')
319
361
  return null;
320
- const grants = refusal.fields.upload;
321
- if (!Array.isArray(grants))
362
+ const uploads = refusal.fields.upload;
363
+ if (!Array.isArray(uploads))
322
364
  return false;
323
- for (const grant of grants) {
324
- const { url, operation, sha256 } = grant;
325
- const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
326
- if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
365
+ for (const named of uploads) {
366
+ const upload = houseUpload(named);
367
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
368
+ if (upload === null || change === undefined)
327
369
  return false;
328
- const delivered = await transport.transfer({
329
- act: 'binary_stage',
330
- url,
331
- body: change.blob,
332
- operation,
333
- });
334
- if (delivered.status !== 200)
370
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
371
+ if (finished?.body?.staged !== true)
335
372
  return false;
336
373
  }
337
374
  return true;
338
375
  }
339
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
376
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
340
377
  const inline = { ...head, changes, runtime: notifications.runtime };
341
378
  const prepared = uploadable(inline, bytes, declared())
342
379
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
@@ -344,15 +381,20 @@ export function createAgentsHouseAdapter(options) {
344
381
  if ('outcome' in prepared)
345
382
  return prepared.outcome;
346
383
  const answer = await transport.door(DOOR_BATCH, prepared.body);
347
- const stagedFiles = await staged(answer, batch);
384
+ const stagedFiles = await staged(answer, request);
348
385
  if (stagedFiles === false)
349
386
  return UNCERTAIN;
350
387
  const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
351
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
388
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
352
389
  return UNCERTAIN;
353
- return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
390
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
391
+ ? undefined
392
+ : {
393
+ source: { authority: entry.authority, handle: entry.handle },
394
+ destination: { authority: into.authority, handle: into.handle },
395
+ }));
354
396
  }
355
- function granted(answer, locator) {
397
+ function addressed(answer, locator) {
356
398
  const failure = classifyDoor(answer.status, answer.body, locator);
357
399
  if (failure !== null) {
358
400
  return failure.kind === 'scoped-refusal'
@@ -364,33 +406,48 @@ export function createAgentsHouseAdapter(options) {
364
406
  }
365
407
  : failure;
366
408
  }
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 };
409
+ const address = parseFileAddress(answer.body);
410
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
411
+ }
412
+ function linked(answer, locator) {
413
+ const address = addressed(answer, locator);
414
+ return address.kind === 'address'
415
+ ? { kind: 'link', url: address.url, name: address.name }
416
+ : address;
373
417
  }
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)
418
+ async function downloaded(ask, locator) {
419
+ let address = addressed(await ask(), locator);
420
+ if (address.kind !== 'address')
421
+ return address;
422
+ let read = await transport.download(address.url);
423
+ if (read.status === EXPIRED_ADDRESS) {
424
+ address = addressed(await ask(), locator);
425
+ if (address.kind !== 'address')
426
+ return address;
427
+ read = await transport.download(address.url);
428
+ }
429
+ if (read.status !== 200 || read.body === null)
380
430
  return UNCERTAIN_READ;
381
- return { kind: 'binary', blob: read.blob };
431
+ return {
432
+ kind: 'binary',
433
+ body: read.body,
434
+ mediaType: address.mediaType,
435
+ sha256: address.sha256,
436
+ };
382
437
  }
383
- function pathGrant(request) {
438
+ function pathAddress(request) {
384
439
  const entry = authorityFor(request.locator);
385
440
  if (entry === undefined)
386
441
  return null;
387
- return transport.door(DOOR_FILES_GET, {
442
+ return () => transport.door(DOOR_FILES_GET, {
388
443
  room_ref: entry.authority,
389
444
  path: request.path,
390
445
  revision: request.revision,
391
446
  });
392
447
  }
393
448
  return {
449
+ roomTypes: HOUSE_ROOM_TYPES,
450
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
394
451
  async authorize() {
395
452
  return options.authorize();
396
453
  },
@@ -440,7 +497,7 @@ export function createAgentsHouseAdapter(options) {
440
497
  return { kind: 'room-not-readable', locator: request.locator };
441
498
  const asked = { entry, held: request.held };
442
499
  if (request.pageToken !== undefined) {
443
- return continuedBootstrap(asked, JSON.parse(request.pageToken));
500
+ return partPage(asked, JSON.parse(request.pageToken), request.received, request.arriving);
444
501
  }
445
502
  return bootstrapTogether(asked);
446
503
  },
@@ -462,18 +519,27 @@ export function createAgentsHouseAdapter(options) {
462
519
  const entry = authorityFor(request.locator);
463
520
  if (entry === undefined)
464
521
  return { kind: 'room-not-readable', locator: request.locator };
465
- const mapped = houseChanges(request.changeBatch, entry.handle);
522
+ const batch = request.changeBatch;
523
+ const destination = batch.between?.destination.room;
524
+ const into = destination === undefined ? undefined : authorityFor(destination);
525
+ if (destination !== undefined && into === undefined) {
526
+ return { kind: 'room-not-readable', locator: destination };
527
+ }
528
+ const mapped = into === undefined
529
+ ? houseChanges(batch, entry.handle)
530
+ : houseMove(batch, entry.handle, into.handle);
466
531
  if (mapped.kind === 'unmapped')
467
532
  return rejectedMapping(mapped);
468
- const head = operationHead(request.changeBatch);
533
+ const head = operationHead(batch);
469
534
  const delta = deltaBody(mapped.changes);
470
535
  const bytes = deltaBytes(delta);
471
536
  const handed = request.upload;
472
- const batch = request.changeBatch;
473
- const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
537
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
538
+ const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
539
+ const outcome = await answer(handed);
474
540
  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));
541
+ return settle(outcome);
542
+ return settle(await answer(undefined));
477
543
  },
478
544
  async history(_authorization, request) {
479
545
  const entry = authorityFor(request.locator);
@@ -491,36 +557,16 @@ export function createAgentsHouseAdapter(options) {
491
557
  });
492
558
  },
493
559
  async binary(_authorization, request) {
494
- if (request.kind === 'path') {
495
- const asked = pathGrant(request);
496
- if (asked === null)
497
- return { kind: 'room-not-readable', locator: request.locator };
498
- return downloaded(await asked, request.locator);
499
- }
500
- const referrer = houseReferrer(request.referrer, tracked);
501
- if (referrer === null) {
502
- return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
503
- }
504
- const answer = await transport.door(DOOR_FILES_GET, {
505
- file: request.fileRef,
506
- referrer: referrer.referrer,
507
- });
508
- return downloaded(answer, referrer.locator);
560
+ const ask = pathAddress(request);
561
+ if (ask === null)
562
+ return { kind: 'room-not-readable', locator: request.locator };
563
+ return downloaded(ask, request.locator);
509
564
  },
510
565
  async link(_authorization, request) {
511
- const asked = pathGrant(request);
512
- if (asked === null)
566
+ const ask = pathAddress(request);
567
+ if (ask === null)
513
568
  return { kind: 'room-not-readable', locator: request.locator };
514
- return granted(await asked, request.locator);
515
- },
516
- 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 }) };
569
+ return linked(await ask(), request.locator);
524
570
  },
525
571
  reached(locator, version, target) {
526
572
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -534,7 +580,10 @@ export function createAgentsHouseAdapter(options) {
534
580
  roomStream: {
535
581
  hold: held,
536
582
  reopen: notifications.reopen,
537
- observe: notifications.observe,
583
+ observe(notify, receipts, revisions) {
584
+ heldRevisions = revisions;
585
+ return notifications.observe(notify, receipts);
586
+ },
538
587
  published(locator, roomVersion) {
539
588
  const at = logPosition(roomVersion);
540
589
  if (at !== null)
@@ -0,0 +1,2 @@
1
+ export declare const APP_REFERENCE_PATTERN: RegExp;
2
+ //# sourceMappingURL=app-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-reference.d.ts","sourceRoot":"","sources":["../src/app-reference.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,qBAAqB,QAEjC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { CROCKFORD_CHARACTER } from './crockford.js';
2
+ export const APP_REFERENCE_PATTERN = new RegExp(`^a_(${CROCKFORD_CHARACTER}+)\\.(${CROCKFORD_CHARACTER})$`);
@@ -0,0 +1,4 @@
1
+ export declare const CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
2
+ export declare const CROCKFORD_CHARACTER = "[0123456789abcdefghjkmnpqrstvwxyz]";
3
+ export declare const CROCKFORD_LEADING_CHARACTER: string;
4
+ //# sourceMappingURL=crockford.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crockford.d.ts","sourceRoot":"","sources":["../src/crockford.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,qCAAqC,CAAC;AAErE,eAAO,MAAM,mBAAmB,uCAA4B,CAAC;AAE7D,eAAO,MAAM,2BAA2B,QAAqC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const CROCKFORD_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz';
2
+ export const CROCKFORD_CHARACTER = `[${CROCKFORD_ALPHABET}]`;
3
+ export const CROCKFORD_LEADING_CHARACTER = `[${CROCKFORD_ALPHABET.slice(1)}]`;