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