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