@agentshouse/mdruntime-adapter 0.1.0-cea44707d3919 → 0.1.0-ced64d1139179

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 (66) hide show
  1. package/README.md +2 -2
  2. package/dist/actions.d.ts +163 -3
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +121 -215
  5. package/dist/adapter.d.ts +1 -0
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/adapter.js +267 -192
  8. package/dist/app-reference.d.ts +2 -0
  9. package/dist/app-reference.d.ts.map +1 -0
  10. package/dist/app-reference.js +2 -0
  11. package/dist/crockford.d.ts +4 -0
  12. package/dist/crockford.d.ts.map +1 -0
  13. package/dist/crockford.js +3 -0
  14. package/dist/discovery.d.ts +7 -10
  15. package/dist/discovery.d.ts.map +1 -1
  16. package/dist/discovery.js +40 -40
  17. package/dist/house-model.d.ts +2951 -0
  18. package/dist/house-model.d.ts.map +1 -0
  19. package/dist/house-model.js +1627 -0
  20. package/dist/index.d.ts +9 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +8 -0
  23. package/dist/model.d.ts +6 -3
  24. package/dist/model.d.ts.map +1 -1
  25. package/dist/model.js +15 -30
  26. package/dist/part.d.ts +14 -0
  27. package/dist/part.d.ts.map +1 -0
  28. package/dist/part.js +90 -0
  29. package/dist/read.d.ts +51 -41
  30. package/dist/read.d.ts.map +1 -1
  31. package/dist/read.js +244 -232
  32. package/dist/refusal.d.ts +2 -8
  33. package/dist/refusal.d.ts.map +1 -1
  34. package/dist/refusal.js +6 -14
  35. package/dist/room-stream.d.ts +19 -0
  36. package/dist/room-stream.d.ts.map +1 -0
  37. package/dist/room-stream.js +57 -0
  38. package/dist/room-types.d.ts +7 -0
  39. package/dist/room-types.d.ts.map +1 -0
  40. package/dist/room-types.js +126 -0
  41. package/dist/site.d.ts +1 -1
  42. package/dist/site.d.ts.map +1 -1
  43. package/dist/site.js +109 -92
  44. package/dist/stream.d.ts +2 -11
  45. package/dist/stream.d.ts.map +1 -1
  46. package/dist/stream.js +139 -140
  47. package/dist/transport.d.ts +70 -22
  48. package/dist/transport.d.ts.map +1 -1
  49. package/dist/transport.js +50 -9
  50. package/dist/upload.d.ts +12 -0
  51. package/dist/upload.d.ts.map +1 -0
  52. package/dist/upload.js +63 -0
  53. package/dist/version.d.ts +1 -0
  54. package/dist/version.d.ts.map +1 -1
  55. package/dist/version.js +1 -1
  56. package/dist/write.d.ts +18 -9
  57. package/dist/write.d.ts.map +1 -1
  58. package/dist/write.js +164 -41
  59. package/package.json +9 -6
  60. package/AGENTS.md +0 -253
  61. package/dist/gather.d.ts +0 -3
  62. package/dist/gather.d.ts.map +0 -1
  63. package/dist/gather.js +0 -40
  64. package/dist/links.d.ts +0 -20
  65. package/dist/links.d.ts.map +0 -1
  66. package/dist/links.js +0 -104
package/dist/adapter.js CHANGED
@@ -1,96 +1,99 @@
1
+ import { stepped } from '@agentshouse/mdruntime';
1
2
  import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
2
- import { scopeModels, streamedModels, viewModels } from './model.js';
3
- import { gathered, selections } from './gather.js';
4
3
  import { createSiteAdapter } from './site.js';
5
- import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, } from './read.js';
6
- import { acceptedLinks, enumeratedLinks, heldLinks, scopeLinks, streamedLinks, } from './links.js';
4
+ import { authorityRoot, bootstrapping, parseFileAddress, parseGrant, } from './read.js';
5
+ import { roomStreams } from './room-stream.js';
6
+ import { sha256Hex } from './digest.js';
7
+ import { finishedUpload, houseUpload } from './upload.js';
7
8
  import { houseAction } from './actions.js';
8
- import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
9
+ import { classifyDoor, discoveryFailure, doorRefusal } from './refusal.js';
9
10
  import { houseNotifications } from './stream.js';
10
- import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_REVISIONS, } from './transport.js';
11
- import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
12
- import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
11
+ import { houseDoorRoute, } from './transport.js';
12
+ import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, houseMove, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
13
+ import { HOUSE_NAMEABLE_ROOM_TYPES, HOUSE_ROOM_TYPES } from './room-types.js';
14
+ import { followsVersion, logPosition, presented, reachedVersion, roomVersion } from './version.js';
13
15
  const UNCERTAIN = { kind: 'transport-uncertainty' };
14
16
  const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
17
+ const OCTET_STREAM = 'application/octet-stream';
18
+ const EXPIRED_ADDRESS = 403;
15
19
  function lapsedGrant(outcome) {
16
20
  return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
17
21
  }
22
+ async function heldFiles(authority, held, bound) {
23
+ const listed = await held.files(null, bound + 1);
24
+ const files = listed.slice(0, bound);
25
+ const through = listed.length > bound ? (files.at(-1)?.path ?? null) : null;
26
+ return files.length === 0 ? null : { room: authority, through, files };
27
+ }
28
+ async function reused(scope, held) {
29
+ const sent = new Set(scope.contents.map((entry) => entry.path));
30
+ const contents = [...scope.contents];
31
+ const asked = scope.files.filter((file) => file.binary === undefined && !sent.has(file.path));
32
+ if (asked.length === 0)
33
+ return scope;
34
+ if (held === undefined)
35
+ return null;
36
+ const kept = await held.contents(asked.map(({ path }) => path));
37
+ for (const file of asked) {
38
+ const found = kept.get(file.path);
39
+ if (found === undefined ||
40
+ found.revision !== file.revision ||
41
+ (await sha256Hex(found.content)) !== file.sha256) {
42
+ return null;
43
+ }
44
+ contents.push({ path: file.path, content: found.content });
45
+ }
46
+ return { ...scope, contents };
47
+ }
18
48
  export function createAgentsHouseAdapter(options) {
19
49
  if ('site' in options)
20
- return createSiteAdapter(options.site);
50
+ return createSiteAdapter(options.site, options.snapshot === true);
21
51
  const transport = options.transport;
22
52
  const notifications = houseNotifications(transport, 'credential');
23
53
  const tracked = new Map();
24
- const handed = new Map();
54
+ const rooms = roomStreams('credential');
55
+ const unreused = new Set();
25
56
  let bounds;
26
57
  function declared() {
27
58
  if (bounds === undefined)
28
59
  throw new Error('no Discovery declared the bounds');
29
60
  return bounds;
30
61
  }
31
- async function bootstrappedRoom(envelope, entry) {
32
- const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
33
- if (scope === undefined)
34
- return { kind: 'room-not-readable', locator: entry.locator };
35
- handed.set(entry.locator, scopeLinks(scope));
36
- notifications.reach(entry.authority, scope);
37
- const models = await scopeModels(scope, entry);
38
- return { ...bootstrapOf(scope), ...(models === undefined ? {} : { models }) };
62
+ async function streamedPage(entry, token, held) {
63
+ const part = await rooms.part(entry, token);
64
+ if ('kind' in part)
65
+ return part;
66
+ const scope = await reused(part.scope, held);
67
+ if (scope === null) {
68
+ unreused.add(entry.locator);
69
+ return UNCERTAIN_READ;
70
+ }
71
+ return {
72
+ ...(await stepped(bootstrapping(scope))),
73
+ ...(part.models === undefined ? {} : { models: part.models }),
74
+ ...(part.pageToken === undefined ? {} : { pageToken: part.pageToken }),
75
+ bytes: part.bytes,
76
+ };
39
77
  }
40
- async function enumeratedRoom(enumeration, ask) {
41
- const view = viewOf(enumeration, ask.entry);
42
- if (view === undefined)
43
- return { kind: 'room-not-readable', locator: ask.entry.locator };
44
- notifications.reach(ask.entry.authority, view);
45
- const answer = refreshFrom(view, ask.fromRoomVersion);
46
- if (answer.kind !== 'net-advancement')
47
- return answer;
48
- handed.set(ask.entry.locator, enumeratedLinks(view, handed.get(ask.entry.locator)));
49
- const models = await viewModels(view, ask.entry);
50
- return models === undefined ? answer : { ...answer, models };
51
- }
52
- async function streamed(entry, request, from) {
53
- const links = handed.get(entry.locator);
54
- if (links?.roomVersion !== request.fromRoomVersion)
55
- return null;
56
- const advanced = notifications.advance(entry, from, (path, revision) => links.revisions.get(path) === revision);
57
- if (advanced === null)
58
- return null;
59
- const relinked = streamedLinks(links, advanced);
60
- if (relinked === null)
61
- return null;
62
- handed.set(entry.locator, relinked);
63
- const models = await streamedModels(advanced.schemas, entry, 'credential');
64
- return models === undefined ? advanced.page : { ...advanced.page, models };
78
+ async function openedRoom(entry, request) {
79
+ const reusing = unreused.delete(entry.locator) ? undefined : request.held;
80
+ const files = reusing === undefined
81
+ ? null
82
+ : await heldFiles(entry.authority, reusing, declared().max_held_files);
83
+ const answered = await transport.streamed(houseDoorRoute('bootstrap'), {
84
+ prefixes: [authorityRoot(entry.handle)],
85
+ ...(files === null ? {} : { held: [files] }),
86
+ }, request.signal ?? new AbortController().signal);
87
+ if (answered.room === null)
88
+ return classifyDoor(answered.status, answered.body, entry.locator) ?? UNCERTAIN_READ;
89
+ return rooms.head(entry, answered.room, request.arriving, request.signal);
65
90
  }
66
91
  function held(rooms) {
67
- const kept = rooms.flatMap((room) => {
92
+ notifications.send(rooms.flatMap((room) => {
68
93
  const entry = tracked.get(room.locator);
69
94
  const at = logPosition(room.roomVersion);
70
- if (entry === undefined || at === null)
71
- return [];
72
- handed.set(room.locator, heldLinks(room));
73
- notifications.reach(entry.authority, at);
74
- return [presented(entry.authority, at)];
75
- });
76
- notifications.send(kept);
77
- }
78
- function adopted(entry, outcome) {
79
- if (outcome.kind !== 'accepted')
80
- return outcome;
81
- const { originals, ...accepted } = outcome;
82
- const links = handed.get(entry.locator);
83
- if (links === undefined)
84
- return accepted;
85
- const follows = followsVersion(links.roomVersion, accepted.roomVersion);
86
- const relinked = acceptedLinks(links, accepted, follows ? accepted.roomVersion : links.roomVersion, originals);
87
- handed.set(entry.locator, relinked.links);
88
- const at = logPosition(accepted.roomVersion);
89
- if (follows && at !== null)
90
- notifications.reach(entry.authority, at);
91
- return relinked.attachments === undefined
92
- ? accepted
93
- : { ...accepted, attachments: relinked.attachments };
95
+ return entry === undefined || at === null ? [] : [presented(entry.authority, at)];
96
+ }));
94
97
  }
95
98
  function authorityFor(locator) {
96
99
  return tracked.get(locator);
@@ -109,134 +112,154 @@ export function createAgentsHouseAdapter(options) {
109
112
  return result;
110
113
  return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
111
114
  }
112
- async function bundled(body) {
113
- const grant = parseGrant(body);
114
- if (grant === null)
115
- return body;
116
- if (grant.method !== 'GET')
117
- return null;
118
- const answer = await transport.transfer({
119
- act: 'local_copy_bundle',
120
- method: 'GET',
121
- url: grant.url,
122
- answer: 'json',
123
- });
124
- return answer.status === 200 ? answer.body : null;
125
- }
126
- function weight(entry) {
127
- return contentPrefixes(entry).length;
128
- }
129
- async function bootstrapped(entries) {
130
- const answers = [];
131
- for (const part of selections(entries, weight, declared().max_prefixes)) {
132
- const answer = await transport.door(DOOR_BOOTSTRAP, {
133
- prefixes: part.flatMap((entry) => contentPrefixes(entry)),
134
- });
135
- const classified = part.map((entry) => classifyDoor(answer.status, answer.body, entry.locator));
136
- const envelope = classified[0] === null ? parseEnvelope(await bundled(answer.body)) : null;
137
- for (const [index, entry] of part.entries()) {
138
- const failure = classified[index];
139
- if (failure === null || failure === undefined) {
140
- answers.push(envelope === null ? UNCERTAIN_READ : await bootstrappedRoom(envelope, entry));
141
- continue;
142
- }
143
- answers.push(failure);
144
- }
145
- }
146
- return answers;
147
- }
148
- function layered(asks) {
149
- const depths = new Map();
150
- const layers = [];
151
- for (const ask of asks) {
152
- const depth = depths.get(ask.entry.authority) ?? 0;
153
- depths.set(ask.entry.authority, depth + 1);
154
- (layers[depth] ??= []).push(ask);
155
- }
156
- return layers;
157
- }
158
- async function enumerated(asks) {
159
- const answers = new Map();
160
- const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
161
- for (const part of parts) {
162
- let open = [...part];
163
- while (open.length > 0) {
164
- const answer = await transport.door(DOOR_ENUMERATE, {
165
- prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
166
- positions: open.map((ask) => presented(ask.entry.authority, ask.from)),
167
- });
168
- const classified = open.map((ask) => classifyRead(answer.status, answer.body, ask.entry.locator));
169
- if (classified[0]?.kind === 'beyond-horizon') {
170
- const named = doorRefusal(answer.body)?.fields.authority;
171
- const expired = open.filter((ask) => ask.entry.authority === named);
172
- const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
173
- const gone = expired.length === 0 ? open : expired;
174
- for (const ask of gone)
175
- answers.set(ask, answered);
176
- open = open.filter((ask) => !gone.includes(ask));
177
- continue;
178
- }
179
- const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
180
- for (const [index, ask] of open.entries()) {
181
- const failure = classified[index];
182
- if (failure !== null && failure !== undefined) {
183
- answers.set(ask, failure);
184
- continue;
185
- }
186
- answers.set(ask, enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask));
187
- }
188
- open = [];
189
- }
190
- }
191
- return asks.map((ask) => answers.get(ask) ?? UNCERTAIN_READ);
192
- }
193
- const bootstrapTogether = gathered(bootstrapped);
194
- const enumerateTogether = gathered(enumerated);
195
115
  async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
196
116
  const paths = batchPaths(changes);
197
117
  const sha256 = await deltaDigest(delta);
198
118
  if (handed !== undefined && handed.sha256 === sha256) {
199
119
  return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
200
120
  }
201
- const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
121
+ const minted = await transport.door(houseDoorRoute('batch-upload'), {
122
+ ...head,
123
+ paths,
124
+ bytes,
125
+ sha256,
126
+ });
202
127
  if (minted.status !== 200) {
203
- return { outcome: mutationOutcome(minted.status, minted.body, entry.locator, entry.handle) };
128
+ return {
129
+ outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
130
+ };
204
131
  }
205
132
  const grant = parseGrant(minted.body);
206
- if (grant === null || grant.method !== 'POST')
133
+ if (grant === null)
207
134
  return { outcome: UNCERTAIN };
208
135
  const delivered = await transport.transfer({
209
136
  act: 'authored_batch_upload',
210
- method: 'POST',
211
137
  url: grant.url,
212
- answer: 'json',
213
138
  body: `${grant.operation}\n${delta}`,
214
139
  });
215
140
  if (delivered.status !== 200)
216
141
  return { outcome: UNCERTAIN };
217
142
  const holding = parseGrant(delivered.body);
218
- if (holding === null || holding.method !== 'POST')
143
+ if (holding === null)
219
144
  return { outcome: UNCERTAIN };
220
145
  const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
221
146
  await keep(upload);
222
147
  return { body: { ...head, paths, upload, runtime: notifications.runtime } };
223
148
  }
224
- async function submit(head, changes, delta, bytes, entry, handed, keep) {
149
+ async function staged(answer, { changeBatch: batch, file }) {
150
+ const refusal = doorRefusal(answer.body);
151
+ if (refusal?.code !== 'files_unstaged')
152
+ return null;
153
+ const uploads = refusal.fields.upload;
154
+ if (!Array.isArray(uploads))
155
+ return false;
156
+ for (const named of uploads) {
157
+ const upload = houseUpload(named);
158
+ const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === named.sha256);
159
+ if (upload === null || change === undefined)
160
+ return false;
161
+ const finished = await finishedUpload(transport, upload, await file(change.localId));
162
+ if (finished?.body?.staged !== true)
163
+ return false;
164
+ }
165
+ return true;
166
+ }
167
+ async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
225
168
  const inline = { ...head, changes, runtime: notifications.runtime };
226
169
  const prepared = uploadable(inline, bytes, declared())
227
170
  ? await uploaded(head, changes, delta, bytes, entry, handed, keep)
228
171
  : { body: inline };
229
172
  if ('outcome' in prepared)
230
173
  return prepared.outcome;
231
- const answer = await transport.door(DOOR_BATCH, prepared.body);
232
- return mutationOutcome(answer.status, answer.body, entry.locator, entry.handle);
174
+ const answer = await transport.door(houseDoorRoute('batch'), prepared.body);
175
+ const stagedFiles = await staged(answer, request);
176
+ if (stagedFiles === false)
177
+ return UNCERTAIN;
178
+ const settled = stagedFiles === null ? answer : await transport.door(houseDoorRoute('batch'), prepared.body);
179
+ if (stagedFiles === true && (await staged(settled, request)) !== null)
180
+ return UNCERTAIN;
181
+ return stepped(mutationOutcome(settled.status, settled.body, entry.locator, entry.handle, into === undefined
182
+ ? undefined
183
+ : {
184
+ source: { authority: entry.authority, handle: entry.handle },
185
+ destination: { authority: into.authority, handle: into.handle },
186
+ }));
187
+ }
188
+ function addressed(answer, locator) {
189
+ const failure = classifyDoor(answer.status, answer.body, locator);
190
+ if (failure !== null) {
191
+ return failure.kind === 'scoped-refusal'
192
+ ? {
193
+ kind: 'rejected',
194
+ code: failure.code,
195
+ fields: failure.fields,
196
+ retryable: failure.retryable,
197
+ }
198
+ : failure;
199
+ }
200
+ const address = parseFileAddress(answer.body);
201
+ return address === null ? UNCERTAIN_READ : { kind: 'address', ...address };
202
+ }
203
+ function linked(answer, locator) {
204
+ const address = addressed(answer, locator);
205
+ return address.kind === 'address'
206
+ ? { kind: 'link', url: address.url, name: address.name, mediaType: address.mediaType }
207
+ : address;
208
+ }
209
+ function refusedAttach(answer, locator) {
210
+ const failure = classifyDoor(answer.status, answer.body, locator);
211
+ if (failure === null)
212
+ return null;
213
+ return failure.kind === 'scoped-refusal'
214
+ ? {
215
+ kind: 'rejected',
216
+ code: failure.code,
217
+ fields: failure.fields,
218
+ retryable: failure.retryable,
219
+ }
220
+ : failure;
221
+ }
222
+ async function downloaded(ask, locator) {
223
+ let address = addressed(await ask(), locator);
224
+ if (address.kind !== 'address')
225
+ return address;
226
+ let read = await transport.download(address.url);
227
+ if (read.status === EXPIRED_ADDRESS) {
228
+ address = addressed(await ask(), locator);
229
+ if (address.kind !== 'address')
230
+ return address;
231
+ read = await transport.download(address.url);
232
+ }
233
+ if (read.status !== 200 || read.body === null)
234
+ return UNCERTAIN_READ;
235
+ return {
236
+ kind: 'binary',
237
+ body: read.body,
238
+ mediaType: address.mediaType,
239
+ sha256: address.sha256,
240
+ };
241
+ }
242
+ function pathAddress(request) {
243
+ const entry = authorityFor(request.locator);
244
+ if (entry === undefined)
245
+ return null;
246
+ return () => transport.door(houseDoorRoute('files/get'), {
247
+ room_ref: entry.authority,
248
+ path: request.path,
249
+ revision: request.revision,
250
+ });
233
251
  }
234
252
  return {
253
+ roomTypes: HOUSE_ROOM_TYPES,
254
+ nameableRoomTypes: HOUSE_NAMEABLE_ROOM_TYPES,
235
255
  async authorize() {
236
256
  return options.authorize();
237
257
  },
238
- async discover(_authorization) {
239
- const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
258
+ async discover(_authorization, scope) {
259
+ const answer = await transport.door(houseDoorRoute('discover'), {
260
+ prefixes: [...DISCOVER_PREFIXES],
261
+ ...(scope === undefined ? {} : { rooms: scope.authorities }),
262
+ });
240
263
  const failed = discoveryFailure(answer.status, answer.body);
241
264
  if (failed !== null)
242
265
  return failed;
@@ -245,61 +268,68 @@ export function createAgentsHouseAdapter(options) {
245
268
  return { kind: 'transport-uncertainty' };
246
269
  bounds = document.bounds;
247
270
  const entries = trackedFrom(document);
248
- tracked.clear();
271
+ const covered = scope === undefined
272
+ ? null
273
+ : [
274
+ ...new Set([
275
+ ...[...tracked.values()]
276
+ .filter((entry) => scope.authorities.includes(entry.authority))
277
+ .map((entry) => entry.locator),
278
+ ...entries.map((entry) => entry.locator),
279
+ ]),
280
+ ];
281
+ if (covered === null)
282
+ tracked.clear();
283
+ else
284
+ for (const locator of covered)
285
+ tracked.delete(locator);
249
286
  for (const entry of entries)
250
287
  tracked.set(entry.locator, entry);
251
- for (const locator of handed.keys())
252
- if (!tracked.has(locator))
253
- handed.delete(locator);
254
- notifications.track(entries);
288
+ notifications.track([...tracked.values()]);
255
289
  return {
256
- ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
290
+ ...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions)),
257
291
  member: document.member,
292
+ ...(covered === null ? {} : { covered }),
258
293
  };
259
294
  },
260
295
  async bootstrap(_authorization, request) {
261
296
  const entry = authorityFor(request.locator);
262
297
  if (entry === undefined)
263
298
  return { kind: 'room-not-readable', locator: request.locator };
264
- if (request.pageToken !== undefined)
265
- return { kind: 'transport-uncertainty' };
266
- return bootstrapTogether(entry);
267
- },
268
- async refresh(_authorization, request) {
269
- const entry = authorityFor(request.locator);
270
- if (entry === undefined)
271
- return { kind: 'room-not-readable', locator: request.locator };
272
- if (request.pageToken !== undefined)
273
- return { kind: 'transport-uncertainty' };
274
- const from = logPosition(request.fromRoomVersion);
275
- if (from === null)
276
- return { kind: 'beyond-horizon' };
277
- const answered = await streamed(entry, request, from);
278
- if (answered !== null)
279
- return answered;
280
- return enumerateTogether({ entry, from, fromRoomVersion: request.fromRoomVersion });
299
+ return request.pageToken === undefined
300
+ ? openedRoom(entry, request)
301
+ : streamedPage(entry, request.pageToken, request.held);
281
302
  },
282
303
  async mutate(_authorization, request, keep) {
283
304
  const entry = authorityFor(request.locator);
284
305
  if (entry === undefined)
285
306
  return { kind: 'room-not-readable', locator: request.locator };
286
- const mapped = houseChanges(request.changeBatch, entry.handle);
307
+ const batch = request.changeBatch;
308
+ const destination = batch.between?.destination.room;
309
+ const into = destination === undefined ? undefined : authorityFor(destination);
310
+ if (destination !== undefined && into === undefined) {
311
+ return { kind: 'room-not-readable', locator: destination };
312
+ }
313
+ const mapped = into === undefined
314
+ ? houseChanges(batch, entry.handle)
315
+ : houseMove(batch, entry.handle, into.handle);
287
316
  if (mapped.kind === 'unmapped')
288
317
  return rejectedMapping(mapped);
289
- const head = operationHead(request.changeBatch);
318
+ const head = operationHead(batch);
290
319
  const delta = deltaBody(mapped.changes);
291
320
  const bytes = deltaBytes(delta);
292
321
  const handed = request.upload;
293
- const outcome = await submit(head, mapped.changes, delta, bytes, entry, handed, keep);
322
+ const answer = (upload) => submit(request, head, mapped.changes, delta, bytes, entry, upload, keep, into);
323
+ const outcome = await answer(handed);
294
324
  if (handed === undefined || !lapsedGrant(outcome))
295
- return adopted(entry, outcome);
296
- return adopted(entry, await submit(head, mapped.changes, delta, bytes, entry, undefined, keep));
325
+ return outcome;
326
+ return answer(undefined);
297
327
  },
298
328
  async history(_authorization, request) {
299
329
  const entry = authorityFor(request.locator);
300
330
  if (entry === undefined)
301
331
  return { kind: 'room-not-readable', locator: request.locator };
302
- const answer = await transport.door(DOOR_REVISIONS, revisionBody({
332
+ const answer = await transport.door(houseDoorRoute('revisions'), revisionBody({
303
333
  path: request.path,
304
334
  handle: entry.handle,
305
335
  ...(request.revision === undefined ? {} : { revision: request.revision }),
@@ -310,6 +340,47 @@ export function createAgentsHouseAdapter(options) {
310
340
  ...(request.revision === undefined ? {} : { revision: request.revision }),
311
341
  });
312
342
  },
343
+ async binary(_authorization, request) {
344
+ const ask = pathAddress(request);
345
+ if (ask === null)
346
+ return { kind: 'room-not-readable', locator: request.locator };
347
+ return downloaded(ask, request.locator);
348
+ },
349
+ async link(_authorization, request) {
350
+ const ask = pathAddress(request);
351
+ if (ask === null)
352
+ return { kind: 'room-not-readable', locator: request.locator };
353
+ return linked(await ask(), request.locator);
354
+ },
355
+ async attach(_authorization, request) {
356
+ const entry = authorityFor(request.locator);
357
+ if (entry === undefined)
358
+ return { kind: 'room-not-readable', locator: request.locator };
359
+ const declared = await transport.door(houseDoorRoute('files/attach'), {
360
+ room_ref: entry.authority,
361
+ name: request.name,
362
+ bytes: request.byteLength,
363
+ sha256: request.sha256,
364
+ media_type: request.mediaType.length > 0 ? request.mediaType : OCTET_STREAM,
365
+ });
366
+ const refused = refusedAttach(declared, request.locator);
367
+ if (refused !== null)
368
+ return refused;
369
+ const upload = houseUpload(declared.body?.upload);
370
+ if (upload === null)
371
+ return UNCERTAIN_READ;
372
+ const finished = await finishedUpload(transport, upload, request.blob);
373
+ if (finished === null)
374
+ return UNCERTAIN_READ;
375
+ const unfinished = refusedAttach(finished, request.locator);
376
+ if (unfinished !== null)
377
+ return unfinished;
378
+ const { path, revision } = (finished.body ?? {});
379
+ const root = `/rooms/${entry.handle}/`;
380
+ return typeof path === 'string' && path.startsWith(root) && typeof revision === 'string'
381
+ ? { kind: 'attached', path: path.slice(root.length), revision }
382
+ : UNCERTAIN_READ;
383
+ },
313
384
  reached(locator, version, target) {
314
385
  return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
315
386
  },
@@ -319,7 +390,11 @@ export function createAgentsHouseAdapter(options) {
319
390
  async action(_authorization, request) {
320
391
  return withAt(await houseAction(transport, request));
321
392
  },
322
- roomStream: { hold: held, reopen: notifications.reopen, observe: notifications.observe },
393
+ roomStream: {
394
+ hold: held,
395
+ reopen: notifications.reopen,
396
+ observe: notifications.observe,
397
+ },
323
398
  conversations: notifications.conversations,
324
399
  serviceMode: notifications.serviceMode,
325
400
  };
@@ -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)}]`;
@@ -13,27 +13,25 @@ export type HouseBounds = {
13
13
  max_inline_body_bytes: number;
14
14
  max_upload_bytes: number;
15
15
  max_prefixes: number;
16
+ max_held_files: number;
16
17
  change_retention_seconds: number;
17
18
  operation_retention_seconds: number;
19
+ max_move_documents?: number;
18
20
  };
19
21
  export type HouseOperations = {
20
- read: readonly string[];
21
- sync: readonly string[];
22
22
  write: readonly string[];
23
23
  delete: readonly string[];
24
24
  };
25
- export type HouseModel = {
26
- root: string;
27
- paths: readonly string[];
28
- };
25
+ export type HouseAccessLevel = 'search' | 'open' | 'keep';
29
26
  export type HouseAuthority = LogPosition & {
30
27
  roomRef: string;
31
28
  roomHandle: string;
32
29
  name: string;
33
30
  schema: string;
34
- houseModel: HouseModel | null;
31
+ typedSelection: readonly string[] | null;
35
32
  managedReplication: 'admitted' | 'unavailable';
36
33
  protectedPaths: readonly string[];
34
+ level: HouseAccessLevel;
37
35
  operations: HouseOperations;
38
36
  };
39
37
  export type HouseDiscoveryDocument = {
@@ -47,8 +45,7 @@ export type TrackedAuthority = LogPosition & {
47
45
  authority: string;
48
46
  handle: string;
49
47
  schema: string;
50
- houseModel: HouseModel | null;
51
- schemas: readonly string[];
48
+ typedSelection: readonly string[] | null;
52
49
  };
53
50
  export type HouseSiteAuthority = LogPosition & {
54
51
  roomRef: string;
@@ -65,7 +62,7 @@ export declare function siteTracked(document: HouseSiteDiscovery): TrackedAuthor
65
62
  export declare function trackedFrom(document: HouseDiscoveryDocument): TrackedAuthority[];
66
63
  export declare function providerLimits(bounds: HouseBounds): readonly ProviderLimit[];
67
64
  export declare function declaredRetention(bounds: HouseBounds): Retention;
68
- export declare function capabilities(document: HouseDiscoveryDocument): readonly Capability[];
65
+ export declare function capabilities(actions: HouseDiscoveryDocument['actions']): readonly Capability[];
69
66
  export declare function siteCapabilities(): readonly Capability[];
70
67
  export declare function discoveredRoom(authority: HouseAuthority): DiscoveredRoom;
71
68
  export declare function mapDiscovery(document: Pick<HouseDiscoveryDocument, 'bounds'>, rooms: readonly DiscoveredRoom[], declared: readonly Capability[]): Discovery;