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