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