@agentshouse/mdruntime-adapter 0.1.0-cd23c45caa798 → 0.1.0-cd24cf2286fd6

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 (73) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +4 -72
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +7 -319
  5. package/dist/adapter.d.ts.map +1 -1
  6. package/dist/adapter.js +204 -417
  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/chunks.d.ts +38 -0
  11. package/dist/chunks.d.ts.map +1 -0
  12. package/dist/chunks.js +247 -0
  13. package/dist/crockford.d.ts +4 -0
  14. package/dist/crockford.d.ts.map +1 -0
  15. package/dist/crockford.js +3 -0
  16. package/dist/discovery.d.ts +22 -15
  17. package/dist/discovery.d.ts.map +1 -1
  18. package/dist/discovery.js +75 -56
  19. package/dist/history.d.ts +12 -0
  20. package/dist/history.d.ts.map +1 -0
  21. package/dist/history.js +92 -0
  22. package/dist/house-model.d.ts +641 -0
  23. package/dist/house-model.d.ts.map +1 -0
  24. package/dist/house-model.js +361 -0
  25. package/dist/index.d.ts +10 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +10 -1
  28. package/dist/model.d.ts +5 -4
  29. package/dist/model.d.ts.map +1 -1
  30. package/dist/model.js +34 -41
  31. package/dist/read.d.ts +31 -54
  32. package/dist/read.d.ts.map +1 -1
  33. package/dist/read.js +82 -300
  34. package/dist/refusal.d.ts +0 -3
  35. package/dist/refusal.d.ts.map +1 -1
  36. package/dist/refusal.js +0 -5
  37. package/dist/room-types.d.ts +7 -0
  38. package/dist/room-types.d.ts.map +1 -0
  39. package/dist/room-types.js +123 -0
  40. package/dist/search.d.ts +5 -0
  41. package/dist/search.d.ts.map +1 -0
  42. package/dist/search.js +47 -0
  43. package/dist/site.d.ts.map +1 -1
  44. package/dist/site.js +82 -185
  45. package/dist/space-schema.d.ts +7 -0
  46. package/dist/space-schema.d.ts.map +1 -0
  47. package/dist/space-schema.js +18 -0
  48. package/dist/stream.d.ts +16 -14
  49. package/dist/stream.d.ts.map +1 -1
  50. package/dist/stream.js +230 -408
  51. package/dist/transport.d.ts +50 -40
  52. package/dist/transport.d.ts.map +1 -1
  53. package/dist/transport.js +54 -11
  54. package/dist/upload.d.ts +12 -0
  55. package/dist/upload.d.ts.map +1 -0
  56. package/dist/upload.js +63 -0
  57. package/dist/write.d.ts +6 -1
  58. package/dist/write.d.ts.map +1 -1
  59. package/dist/write.js +28 -39
  60. package/package.json +8 -4
  61. package/AGENTS.md +0 -304
  62. package/dist/gather.d.ts +0 -4
  63. package/dist/gather.d.ts.map +0 -1
  64. package/dist/gather.js +0 -35
  65. package/dist/handed.d.ts +0 -15
  66. package/dist/handed.d.ts.map +0 -1
  67. package/dist/handed.js +0 -45
  68. package/dist/part.d.ts +0 -13
  69. package/dist/part.d.ts.map +0 -1
  70. package/dist/part.js +0 -80
  71. package/dist/referrer.d.ts +0 -7
  72. package/dist/referrer.d.ts.map +0 -1
  73. package/dist/referrer.js +0 -33
package/dist/adapter.js CHANGED
@@ -1,212 +1,123 @@
1
- import { sortedKeys, stepped } from '@agentshouse/mdruntime';
2
- import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
3
- import { partModels, streamedModels, viewModels } from './model.js';
4
- import { gathered, gatheredEach, selections } from './gather.js';
1
+ import { stepped } from '@agentshouse/mdruntime';
2
+ import { capabilities, discoveredRoom, mapDiscovery, parseAuthorityChange, parseDiscovery, trackedFrom, } from './discovery.js';
5
3
  import { createSiteAdapter } from './site.js';
6
- import { authorityRoot, bootstrapping, emptyScope, housePart, parseEnumeration, parseGrant, parseParted, refreshFrom, viewOf, wholeView, } from './read.js';
7
- import { PART_ATTEMPTS, unverifiedPart } from './part.js';
8
- import { sha256Hex } from './digest.js';
9
- import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
4
+ import { parseFileAddress, parseGrant } from './read.js';
5
+ import { chunkedHead, chunkLister, chunkPart } from './chunks.js';
6
+ import { spaceSchemas } from './space-schema.js';
7
+ import { finishedUpload, houseUpload } from './upload.js';
10
8
  import { houseAction } from './actions.js';
11
- import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
12
- import { houseNotifications } from './stream.js';
13
- import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
9
+ import { houseSearch } from './search.js';
10
+ import { classifyDoor, doorRefusal } from './refusal.js';
11
+ import { houseNotifications, } from './stream.js';
12
+ import { houseDoorRoute, } from './transport.js';
14
13
  import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
15
- import { houseReferrer } from './referrer.js';
16
- import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
14
+ import { HOUSE_TYPED_PATHS } from './house-model.js';
15
+ import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
16
+ import { followsVersion, reachedVersion, roomVersion } from './version.js';
17
17
  const UNCERTAIN = { kind: 'transport-uncertainty' };
18
18
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
19
+ const OCTET_STREAM = 'application/octet-stream';
20
+ const EXPIRED_ADDRESS = 403;
19
21
  function lapsedGrant(outcome) {
20
22
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
21
23
  }
22
- const heldPaths = new WeakMap();
23
- async function sortedHeld(held) {
24
- const known = heldPaths.get(held.documents);
25
- if (known !== undefined)
26
- return known;
27
- const sorted = await stepped(sortedKeys(held.documents));
28
- heldPaths.set(held.documents, sorted);
29
- return sorted;
30
- }
31
- function heldFiles(authority, held, paths, bound) {
32
- const files = [];
33
- let through = null;
34
- for (let at = 0; at < paths.length && through === null; at += 1) {
35
- const revision = held.revisions[paths[at]];
36
- if (revision === undefined)
37
- continue;
38
- if (files.length === bound)
39
- through = files.at(-1).path;
40
- else
41
- files.push({ path: paths[at], revision });
42
- }
43
- return files.length === 0 ? null : { room: authority, through, files };
44
- }
45
- async function reused(scope, held) {
46
- const sent = new Set(scope.contents.map((entry) => entry.path));
47
- const contents = [...scope.contents];
48
- for (const file of scope.files) {
49
- if (file.binary !== undefined || sent.has(file.path))
50
- continue;
51
- const content = held?.documents[file.path];
52
- if (content === undefined ||
53
- held?.revisions[file.path] !== file.revision ||
54
- (await sha256Hex(content)) !== file.sha256) {
55
- return null;
56
- }
57
- contents.push({ path: file.path, content });
58
- }
59
- return { ...scope, contents };
60
- }
61
24
  export function createAgentsHouseAdapter(options) {
62
25
  if ('site' in options)
63
26
  return createSiteAdapter(options.site, options.snapshot === true);
64
27
  const transport = options.transport;
65
- const notifications = houseNotifications(transport, 'credential');
28
+ const spaces = spaceSchemas();
29
+ const notifications = houseNotifications(transport, 'credential', {
30
+ opened: async (frame) => (frame === null ? null : opened(frame)),
31
+ changed,
32
+ expired: (authorities) => lister.askAll(authorities.flatMap((authority) => [...tracked.values()].filter((entry) => entry.authority === authority))),
33
+ }, spaces);
66
34
  const tracked = new Map();
67
- const handed = new Map();
35
+ const lister = chunkLister(transport, () => declared().max_prefixes);
36
+ const chunked = new Map();
37
+ const renewals = new WeakMap();
68
38
  let bounds;
69
39
  function declared() {
70
40
  if (bounds === undefined)
71
41
  throw new Error('no Discovery declared the bounds');
72
42
  return bounds;
73
43
  }
74
- function hand(locator, steps) {
75
- const held = handed.get(locator);
76
- const next = stepped(steps);
77
- if (!(next instanceof Promise)) {
78
- handed.set(locator, next);
79
- return;
80
- }
81
- return next.then((room) => {
82
- if (handed.get(locator) === held)
83
- handed.set(locator, room);
84
- else
85
- handed.delete(locator);
86
- });
87
- }
88
- async function fetchedPart(entry, continued, listed) {
89
- for (let attempt = 0; attempt < PART_ATTEMPTS; attempt += 1) {
90
- const answered = await transport.door(DOOR_BOOTSTRAP, {
91
- prefixes: [authorityRoot(entry.handle)],
92
- part: listed.part,
93
- ...(continued.held === undefined ? {} : { held: [continued.held] }),
94
- });
95
- const failure = classifyRead(answered.status, answered.body, entry.locator);
96
- if (failure !== null)
97
- return failure;
98
- const scope = await housePart(continued.room, answered.body, listed, 'credential');
99
- if (scope !== null)
100
- return scope;
101
- }
102
- return unverifiedPart(entry.locator);
103
- }
104
- async function partPage(asked, continued) {
105
- const { entry } = asked;
106
- const listed = continued.room.parts[continued.next];
107
- const sent = listed === undefined
108
- ? emptyScope(continued.room)
109
- : await fetchedPart(entry, continued, listed);
110
- if ('kind' in sent)
111
- return sent;
112
- const scope = await reused(sent, asked.held);
113
- if (scope === null) {
114
- return asked.held === undefined
115
- ? UNCERTAIN_READ
116
- : bootstrapTogether({ entry, held: undefined });
117
- }
118
- const next = continued.next + 1;
119
- const more = next < continued.room.parts.length;
120
- if (continued.next === 0 && !more)
121
- await hand(entry.locator, scopeHanded(scope));
122
- else
123
- handed.delete(entry.locator);
124
- notifications.reach(entry.authority, scope);
125
- const seen = [
126
- ...continued.seen,
127
- ...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
128
- ];
129
- const models = await partModels(scope, entry, new Set(seen), !more);
44
+ function opened(frame) {
45
+ const document = parseDiscovery(frame);
46
+ if (document === null)
47
+ return null;
48
+ bounds = document.bounds;
49
+ tracked.clear();
50
+ lister.forget();
51
+ for (const entry of trackedFrom(document))
52
+ tracked.set(entry.locator, entry);
53
+ notifications.track([...tracked.values()]);
130
54
  return {
131
- ...(await stepped(bootstrapping(scope))),
132
- ...(models === undefined ? {} : { models }),
133
- ...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
55
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions)),
56
+ member: document.member,
134
57
  };
135
58
  }
136
- async function enumeratedPage(ask, page) {
137
- const answer = await transport.door(DOOR_ENUMERATE, {
138
- prefixes: [authorityRoot(ask.entry.handle)],
139
- positions: [
140
- {
141
- ...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
142
- page: { through: page.through, read: page.read },
143
- },
144
- ],
145
- });
146
- const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
147
- if (failure !== null)
148
- return failure;
149
- const enumeration = parseEnumeration(answer.body, 'credential');
150
- if (enumeration === null)
151
- return UNCERTAIN_READ;
152
- return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
59
+ function changed(frame) {
60
+ const change = parseAuthorityChange(frame);
61
+ if (change === null)
62
+ return null;
63
+ for (const locator of change.left)
64
+ tracked.delete(locator);
65
+ for (const entry of trackedFrom(change))
66
+ tracked.set(entry.locator, entry);
67
+ notifications.track([...tracked.values()]);
68
+ return {
69
+ kind: 'authority-changed',
70
+ rooms: change.authorities.map((authority) => discoveredRoom(authority)),
71
+ left: change.left,
72
+ member: change.member,
73
+ };
153
74
  }
154
- async function enumeratedRoom(enumeration, ask) {
155
- const first = viewOf(enumeration, ask.entry);
156
- if (first === undefined)
157
- return { kind: 'room-not-readable', locator: ask.entry.locator };
158
- const view = await wholeView(first, (page) => enumeratedPage(ask, page));
159
- if ('kind' in view)
160
- return view;
161
- notifications.reach(ask.entry.authority, view);
162
- const answer = await refreshFrom(view, ask.fromRoomVersion);
163
- if (answer.kind !== 'net-advancement')
164
- return answer;
165
- await hand(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
166
- const models = await viewModels(view, ask.entry);
167
- return models === undefined ? answer : { ...answer, models };
75
+ async function listedRoom(entry) {
76
+ const listed = await lister.list(entry);
77
+ if ('kind' in listed)
78
+ return listed;
79
+ const head = await chunkedHead(listed, entry, spaces);
80
+ if (head.kind === 'bootstrap')
81
+ chunked.set(entry.locator, listed);
82
+ return head;
168
83
  }
169
- async function streamed(entry, request, from) {
170
- const held = handed.get(entry.locator);
171
- if (held?.roomVersion !== request.fromRoomVersion)
84
+ async function chunkRead(entry, id) {
85
+ const room = chunked.get(entry.locator);
86
+ const address = room?.chunks.get(id);
87
+ if (room === undefined || address === undefined)
172
88
  return null;
173
- const advanced = await stepped(notifications.advance(entry, from, (path, revision) => held.revisions[path] === revision));
174
- if (advanced === null)
89
+ const read = await transport.download(address.url);
90
+ if (read.status !== EXPIRED_ADDRESS)
91
+ return { room, read };
92
+ const renewed = await renewal(entry, room);
93
+ if ('kind' in renewed || !renewed.chunks.has(id))
175
94
  return null;
176
- await hand(entry.locator, streamedHanded(held, advanced));
177
- const models = await streamedModels(advanced.schemas, entry, 'credential');
178
- return models === undefined ? advanced.page : { ...advanced.page, models };
95
+ chunked.set(entry.locator, renewed);
96
+ return { room: renewed, read: await transport.download(renewed.chunks.get(id).url) };
179
97
  }
180
- function held(rooms) {
181
- const kept = rooms.flatMap((room) => {
182
- const entry = tracked.get(room.locator);
183
- const at = logPosition(room.roomVersion);
184
- if (entry === undefined || at === null)
185
- return [];
186
- handed.set(room.locator, heldHanded(room));
187
- notifications.reach(entry.authority, at);
188
- return [presented(entry.authority, at)];
189
- });
190
- notifications.send(kept);
98
+ function renewal(entry, stale) {
99
+ const held = renewals.get(stale);
100
+ if (held !== undefined)
101
+ return held;
102
+ const renewed = lister.renew(entry, [...chunked.keys()].flatMap((locator) => tracked.get(locator) ?? []));
103
+ renewals.set(stale, renewed);
104
+ return renewed;
191
105
  }
192
- async function adoptedMove(entry, into, outcome) {
193
- if (outcome.kind === 'accepted' && outcome.destination !== undefined) {
194
- await adopted(into, { kind: 'accepted', ...outcome.destination });
106
+ function hold(rooms) {
107
+ for (const room of rooms) {
108
+ const listed = chunked.get(room.locator);
109
+ if (listed !== undefined &&
110
+ room.roomVersion !== null &&
111
+ reachedVersion(room.roomVersion, roomVersion(listed), listed.logEpoch))
112
+ chunked.delete(room.locator);
195
113
  }
196
- return adopted(entry, outcome);
114
+ notifications.send(rooms);
197
115
  }
198
- async function adopted(entry, outcome) {
199
- if (outcome.kind !== 'accepted')
200
- return outcome;
201
- const held = handed.get(entry.locator);
202
- if (held === undefined)
203
- return outcome;
204
- const follows = followsVersion(held.roomVersion, outcome.roomVersion);
205
- await hand(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
206
- const at = logPosition(outcome.roomVersion);
207
- if (follows && at !== null)
208
- notifications.reach(entry.authority, at);
209
- return outcome;
116
+ async function chunkPage(entry, id, arriving) {
117
+ const fetched = await chunkRead(entry, id);
118
+ if (fetched === null || fetched.read.status !== 200)
119
+ return UNCERTAIN_READ;
120
+ return chunkPart(fetched.read.body, fetched.room, id, arriving);
210
121
  }
211
122
  function authorityFor(locator) {
212
123
  return tracked.get(locator);
@@ -225,124 +136,25 @@ export function createAgentsHouseAdapter(options) {
225
136
  return result;
226
137
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
227
138
  }
228
- async function holding(asks) {
229
- const bound = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
230
- const held = [];
231
- for (const ask of asks) {
232
- if (ask.held === undefined || bound === 0)
233
- continue;
234
- const paths = await sortedHeld(ask.held);
235
- const files = heldFiles(ask.entry.authority, ask.held, paths, bound);
236
- if (files !== null)
237
- held.push(files);
238
- }
239
- return held;
240
- }
241
- async function bootstrapped(asks, answer) {
242
- let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
243
- while (open.length > 0) {
244
- const unreached = [];
245
- for (const part of selections(open, declared().max_prefixes)) {
246
- const held = await holding(part.map(({ asked }) => asked));
247
- const answered = await transport.door(DOOR_BOOTSTRAP, {
248
- prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
249
- ...(held.length === 0 ? {} : { held }),
250
- });
251
- const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
252
- const parted = classified[0] === null ? parseParted(answered.body) : null;
253
- for (const [index, asked] of part.entries()) {
254
- const failure = classified[index];
255
- if (failure !== null && failure !== undefined) {
256
- answer(asked.ask, failure);
257
- continue;
258
- }
259
- if (parted === null) {
260
- answer(asked.ask, UNCERTAIN_READ);
261
- continue;
262
- }
263
- const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
264
- if (room === undefined && parted.next !== undefined) {
265
- unreached.push(asked);
266
- continue;
267
- }
268
- if (room === undefined) {
269
- answer(asked.ask, { kind: 'room-not-readable', locator: asked.entry.locator });
270
- continue;
271
- }
272
- const kept = held.find((files) => files.room === asked.entry.authority);
273
- answer(asked.ask, await partPage(asked.asked, {
274
- room,
275
- next: 0,
276
- seen: [],
277
- ...(kept === undefined ? {} : { held: kept }),
278
- }));
279
- }
280
- }
281
- open = unreached;
282
- }
283
- }
284
- function layered(asks) {
285
- const depths = new Map();
286
- const layers = [];
287
- for (const ask of asks) {
288
- const depth = depths.get(ask.ask.entry.authority) ?? 0;
289
- depths.set(ask.ask.entry.authority, depth + 1);
290
- (layers[depth] ??= []).push(ask);
291
- }
292
- return layers;
293
- }
294
- async function enumerated(asks) {
295
- const answers = [];
296
- const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
297
- for (const part of parts) {
298
- let open = [...part];
299
- while (open.length > 0) {
300
- const answer = await transport.door(DOOR_ENUMERATE, {
301
- prefixes: open.map(({ ask }) => authorityRoot(ask.entry.handle)),
302
- positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
303
- });
304
- const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
305
- if (classified[0]?.kind === 'beyond-horizon') {
306
- const named = doorRefusal(answer.body)?.fields.authority;
307
- const expired = open.filter(({ ask }) => ask.entry.authority === named);
308
- const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
309
- const gone = expired.length === 0 ? open : expired;
310
- for (const ask of gone)
311
- answers[ask.index] = answered;
312
- open = open.filter((ask) => !gone.includes(ask));
313
- continue;
314
- }
315
- const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
316
- for (const [answerIndex, { ask, index }] of open.entries()) {
317
- const failure = classified[answerIndex];
318
- if (failure !== null && failure !== undefined) {
319
- answers[index] = failure;
320
- continue;
321
- }
322
- answers[index] =
323
- enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
324
- }
325
- open = [];
326
- }
327
- }
328
- return answers;
329
- }
330
- const bootstrapTogether = gatheredEach(bootstrapped);
331
- const enumerateTogether = gathered(enumerated);
332
139
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
333
140
  const paths = batchPaths(changes);
334
141
  const sha256 = await deltaDigest(delta);
335
142
  if (handed !== undefined && handed.sha256 === sha256) {
336
143
  return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
337
144
  }
338
- const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
145
+ const minted = await transport.door(houseDoorRoute('batch-upload'), {
146
+ ...head,
147
+ paths,
148
+ bytes,
149
+ sha256,
150
+ });
339
151
  if (minted.status !== 200) {
340
152
  return {
341
153
  outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
342
154
  };
343
155
  }
344
156
  const grant = parseGrant(minted.body);
345
- if (grant === null || grant.method !== 'POST')
157
+ if (grant === null)
346
158
  return { outcome: UNCERTAIN };
347
159
  const delivered = await transport.transfer({
348
160
  act: 'authored_batch_upload',
@@ -352,48 +164,43 @@ export function createAgentsHouseAdapter(options) {
352
164
  if (delivered.status !== 200)
353
165
  return { outcome: UNCERTAIN };
354
166
  const holding = parseGrant(delivered.body);
355
- if (holding === null || holding.method !== 'POST')
167
+ if (holding === null)
356
168
  return { outcome: UNCERTAIN };
357
169
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
358
170
  await keep(upload);
359
171
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
360
172
  }
361
- async function staged(answer, batch) {
173
+ async function staged(answer, { changeBatch: batch, file }) {
362
174
  const refusal = doorRefusal(answer.body);
363
175
  if (refusal?.code !== 'files_unstaged')
364
176
  return null;
365
- const grants = refusal.fields.upload;
366
- if (!Array.isArray(grants))
177
+ const uploads = refusal.fields.upload;
178
+ if (!Array.isArray(uploads))
367
179
  return false;
368
- for (const grant of grants) {
369
- const { url, operation, sha256 } = grant;
370
- const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
371
- if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
180
+ for (const named of uploads) {
181
+ const upload = houseUpload(named);
182
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
183
+ if (upload === null || change === undefined)
372
184
  return false;
373
- const delivered = await transport.transfer({
374
- act: 'binary_stage',
375
- url,
376
- body: change.blob,
377
- operation,
378
- });
379
- if (delivered.status !== 200)
185
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
186
+ if (finished?.body?.staged !== true)
380
187
  return false;
381
188
  }
382
189
  return true;
383
190
  }
384
- async function submit(batch, head, changes, delta, bytes, entry, handed, keep, into) {
191
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
385
192
  const inline = { ...head, changes, runtime: notifications.runtime };
386
193
  const prepared = uploadable(inline, bytes, declared())
387
194
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
388
195
  : { body: inline };
389
196
  if ('outcome' in prepared)
390
197
  return prepared.outcome;
391
- const answer = await transport.door(DOOR_BATCH, prepared.body);
392
- const stagedFiles = await staged(answer, batch);
198
+ const answer = await transport.door(houseDoorRoute('batch'), prepared.body);
199
+ const stagedFiles = await staged(answer, request);
393
200
  if (stagedFiles === false)
394
201
  return UNCERTAIN;
395
- const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
396
- if (stagedFiles === true && (await staged(settled, batch)) !== null)
202
+ const settled = stagedFiles === null ? answer : await transport.door(houseDoorRoute('batch'), prepared.body);
203
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
397
204
  return UNCERTAIN;
398
205
  return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
399
206
  ? undefined
@@ -402,7 +209,7 @@ export function createAgentsHouseAdapter(options) {
402
209
  destination: { authority: into.authority, handle: into.handle },
403
210
  }));
404
211
  }
405
- function granted(answer, locator) {
212
+ function addressed(answer, locator) {
406
213
  const failure = classifyDoor(answer.status, answer.body, locator);
407
214
  if (failure !== null) {
408
215
  return failure.kind === 'scoped-refusal'
@@ -414,99 +221,72 @@ export function createAgentsHouseAdapter(options) {
414
221
  }
415
222
  : failure;
416
223
  }
417
- const grant = parseGrant(answer.body);
418
- const name = answer.body.name;
419
- if (grant === null || grant.method !== 'GET' || typeof name !== 'string') {
420
- return UNCERTAIN_READ;
421
- }
422
- return { kind: 'link', url: grant.url, name };
224
+ const address = parseFileAddress(answer.body);
225
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
226
+ }
227
+ function linked(answer, locator) {
228
+ const address = addressed(answer, locator);
229
+ return address.kind === 'address'
230
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
231
+ : address;
232
+ }
233
+ function refusedAttach(answer, locator) {
234
+ const failure = classifyDoor(answer.status, answer.body, locator);
235
+ if (failure === null)
236
+ return null;
237
+ return failure.kind === 'scoped-refusal'
238
+ ? {
239
+ kind: 'rejected',
240
+ code: failure.code,
241
+ fields: failure.fields,
242
+ retryable: failure.retryable,
243
+ }
244
+ : failure;
423
245
  }
424
- async function downloaded(answer, locator) {
425
- const link = granted(answer, locator);
426
- if (link.kind !== 'link')
427
- return link;
428
- const read = await transport.download(link.url);
429
- if (read.status !== 200 || read.blob === null)
246
+ async function downloaded(ask, locator) {
247
+ let address = addressed(await ask(), locator);
248
+ if (address.kind !== 'address')
249
+ return address;
250
+ let read = await transport.download(address.url);
251
+ if (read.status === EXPIRED_ADDRESS) {
252
+ address = addressed(await ask(), locator);
253
+ if (address.kind !== 'address')
254
+ return address;
255
+ read = await transport.download(address.url);
256
+ }
257
+ if (read.status !== 200 || read.body === null)
430
258
  return UNCERTAIN_READ;
431
- return { kind: 'binary', blob: read.blob };
259
+ return {
260
+ kind: 'binary',
261
+ body: read.body,
262
+ mediaType: address.mediaType,
263
+ sha256: address.sha256,
264
+ };
432
265
  }
433
- function pathGrant(request) {
266
+ function pathAddress(request) {
434
267
  const entry = authorityFor(request.locator);
435
268
  if (entry === undefined)
436
269
  return null;
437
- return transport.door(DOOR_FILES_GET, {
270
+ return () => transport.door(houseDoorRoute('files/get'), {
438
271
  room_ref: entry.authority,
439
272
  path: request.path,
440
273
  revision: request.revision,
441
274
  });
442
275
  }
443
276
  return {
277
+ roomTypes: HOUSE_ROOM_TYPES,
278
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
279
+ typedPaths: HOUSE_TYPED_PATHS,
444
280
  async authorize() {
445
281
  return options.authorize();
446
282
  },
447
- async discover(_authorization, scope) {
448
- const answer = await transport.door(DOOR_DISCOVER, {
449
- prefixes: [...DISCOVER_PREFIXES],
450
- ...(scope === undefined ? {} : { rooms: scope.authorities }),
451
- });
452
- const failed = discoveryFailure(answer.status, answer.body);
453
- if (failed !== null)
454
- return failed;
455
- const document = parseDiscovery(answer.body);
456
- if (document === null)
457
- return { kind: 'transport-uncertainty' };
458
- bounds = document.bounds;
459
- const entries = trackedFrom(document);
460
- const covered = scope === undefined
461
- ? null
462
- : [
463
- ...new Set([
464
- ...[...tracked.values()]
465
- .filter((entry) => scope.authorities.includes(entry.authority))
466
- .map((entry) => entry.locator),
467
- ...entries.map((entry) => entry.locator),
468
- ]),
469
- ];
470
- if (covered === null)
471
- tracked.clear();
472
- else
473
- for (const locator of covered)
474
- tracked.delete(locator);
475
- for (const entry of entries)
476
- tracked.set(entry.locator, entry);
477
- for (const locator of handed.keys())
478
- if (!tracked.has(locator))
479
- handed.delete(locator);
480
- notifications.track([...tracked.values()]);
481
- return {
482
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
483
- member: document.member,
484
- ...(covered === null ? {} : { covered }),
485
- };
486
- },
487
283
  async bootstrap(_authorization, request) {
488
284
  const entry = authorityFor(request.locator);
489
285
  if (entry === undefined)
490
286
  return { kind: 'room-not-readable', locator: request.locator };
491
- const asked = { entry, held: request.held };
492
- if (request.pageToken !== undefined) {
493
- return partPage(asked, JSON.parse(request.pageToken));
494
- }
495
- return bootstrapTogether(asked);
496
- },
497
- async refresh(_authorization, request) {
498
- const entry = authorityFor(request.locator);
499
- if (entry === undefined)
500
- return { kind: 'room-not-readable', locator: request.locator };
501
- if (request.pageToken !== undefined)
502
- return { kind: 'transport-uncertainty' };
503
- const from = logPosition(request.fromRoomVersion);
504
- if (from === null)
505
- return { kind: 'beyond-horizon' };
506
- const answered = await streamed(entry, request, from);
507
- if (answered !== null)
508
- return answered;
509
- return enumerateTogether({ entry, from, fromRoomVersion: request.fromRoomVersion });
287
+ return request.pageToken === undefined
288
+ ? listedRoom(entry)
289
+ : chunkPage(entry, request.pageToken, request.arriving);
510
290
  },
511
291
  async mutate(_authorization, request, keep) {
512
292
  const entry = authorityFor(request.locator);
@@ -523,22 +303,21 @@ export function createAgentsHouseAdapter(options) {
523
303
  : houseMove(batch, entry.handle, into.handle);
524
304
  if (mapped.kind === 'unmapped')
525
305
  return rejectedMapping(mapped);
526
- const head = operationHead(batch);
306
+ const head = operationHead(batch, entry.authority);
527
307
  const delta = deltaBody(mapped.changes);
528
308
  const bytes = deltaBytes(delta);
529
309
  const handed = request.upload;
530
- const answer = (upload) => submit(batch, head, mapped.changes, delta, bytes, entry, upload, keep, into);
531
- const settle = (outcome) => into === undefined ? adopted(entry, outcome) : adoptedMove(entry, into, outcome);
310
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
532
311
  const outcome = await answer(handed);
533
312
  if (handed === undefined || !lapsedGrant(outcome))
534
- return settle(outcome);
535
- return settle(await answer(undefined));
313
+ return outcome;
314
+ return answer(undefined);
536
315
  },
537
316
  async history(_authorization, request) {
538
317
  const entry = authorityFor(request.locator);
539
318
  if (entry === undefined)
540
319
  return { kind: 'room-not-readable', locator: request.locator };
541
- const answer = await transport.door(DOOR_REVISIONS, revisionBody({
320
+ const answer = await transport.door(houseDoorRoute('revisions'), revisionBody({
542
321
  path: request.path,
543
322
  handle: entry.handle,
544
323
  ...(request.revision === undefined ? {} : { revision: request.revision }),
@@ -550,34 +329,45 @@ export function createAgentsHouseAdapter(options) {
550
329
  });
551
330
  },
552
331
  async binary(_authorization, request) {
553
- if (request.kind === 'path') {
554
- const asked = pathGrant(request);
555
- if (asked === null)
556
- return { kind: 'room-not-readable', locator: request.locator };
557
- return downloaded(await asked, request.locator);
558
- }
559
- const referrer = houseReferrer(request.referrer, tracked);
560
- if (referrer === null) {
561
- return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
562
- }
563
- const answer = await transport.door(DOOR_FILES_GET, {
564
- file: request.fileRef,
565
- referrer: referrer.referrer,
566
- });
567
- return downloaded(answer, referrer.locator);
332
+ const ask = pathAddress(request);
333
+ if (ask === null)
334
+ return { kind: 'room-not-readable', locator: request.locator };
335
+ return downloaded(ask, request.locator);
568
336
  },
569
337
  async link(_authorization, request) {
570
- const asked = pathGrant(request);
571
- if (asked === null)
338
+ const ask = pathAddress(request);
339
+ if (ask === null)
572
340
  return { kind: 'room-not-readable', locator: request.locator };
573
- return granted(await asked, request.locator);
341
+ return linked(await ask(), request.locator);
574
342
  },
575
- referrerScope(referrer) {
576
- const held = houseReferrer(referrer, tracked);
577
- if (held === null)
578
- return null;
579
- const { path } = held.referrer;
580
- return { locator: held.locator, ...(path === undefined ? {} : { path }) };
343
+ async attach(_authorization, request) {
344
+ const entry = authorityFor(request.locator);
345
+ if (entry === undefined)
346
+ return { kind: 'room-not-readable', locator: request.locator };
347
+ const declared = await transport.door(houseDoorRoute('files/attach'), {
348
+ room_ref: entry.authority,
349
+ name: request.name,
350
+ bytes: request.byteLength,
351
+ sha256: request.sha256,
352
+ media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
353
+ });
354
+ const refused = refusedAttach(declared, request.locator);
355
+ if (refused !== null)
356
+ return refused;
357
+ const upload = houseUpload(declared.body?.upload);
358
+ if (upload === null)
359
+ return UNCERTAIN_READ;
360
+ const finished = await finishedUpload(transport, upload, request.blob);
361
+ if (finished === null)
362
+ return UNCERTAIN_READ;
363
+ const unfinished = refusedAttach(finished, request.locator);
364
+ if (unfinished !== null)
365
+ return unfinished;
366
+ const { path, revision } = (finished.body ?? {});
367
+ const root = `/rooms/${entry.handle}/`;
368
+ return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
369
+ ? { kind: 'attached', path: path.slice(root.length), revision }
370
+ : UNCERTAIN_READ;
581
371
  },
582
372
  reached(locator, version, target) {
583
373
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
@@ -588,15 +378,12 @@ export function createAgentsHouseAdapter(options) {
588
378
  async action(_authorization, request) {
589
379
  return withAt(await houseAction(transport, request));
590
380
  },
381
+ search(_authorization, request) {
382
+ return houseSearch(transport, [...tracked.values()], request);
383
+ },
591
384
  roomStream: {
592
- hold: held,
593
- reopen: notifications.reopen,
385
+ hold,
594
386
  observe: notifications.observe,
595
- published(locator, roomVersion) {
596
- const at = logPosition(roomVersion);
597
- if (at !== null)
598
- notifications.published(locator, at);
599
- },
600
387
  },
601
388
  conversations: notifications.conversations,
602
389
  serviceMode: notifications.serviceMode,