@agentshouse/mdruntime-adapter 0.1.0-cfd03209dcd82 → 0.1.0-cfded86738bb2
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 +294 -276
- package/dist/actions.d.ts +16 -5
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +92 -141
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +307 -138
- package/dist/discovery.d.ts +5 -3
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +29 -10
- package/dist/gather.d.ts +1 -1
- package/dist/gather.d.ts.map +1 -1
- package/dist/gather.js +4 -14
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/model.d.ts +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -3
- package/dist/part.d.ts +15 -0
- package/dist/part.d.ts.map +1 -0
- package/dist/part.js +82 -0
- package/dist/read.d.ts +65 -18
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +302 -122
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +2 -1
- 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 +149 -82
- package/dist/stream.d.ts +5 -4
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +349 -43
- package/dist/transport.d.ts +58 -13
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +1 -1
- 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 +12 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +98 -57
- package/package.json +2 -2
- package/dist/handed.d.ts +0 -15
- package/dist/handed.d.ts.map +0 -1
- package/dist/handed.js +0 -38
- package/dist/referrer.d.ts +0 -7
- package/dist/referrer.d.ts.map +0 -1
- package/dist/referrer.js +0 -35
package/dist/adapter.js
CHANGED
|
@@ -1,75 +1,175 @@
|
|
|
1
|
+
import { stepped } from '@agentshouse/mdruntime';
|
|
1
2
|
import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
|
|
2
3
|
import { partModels, streamedModels, viewModels } from './model.js';
|
|
3
4
|
import { gathered, gatheredEach, selections } from './gather.js';
|
|
4
5
|
import { createSiteAdapter } from './site.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
13
|
import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
|
|
11
|
-
import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
|
|
12
|
-
import {
|
|
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';
|
|
13
16
|
import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
|
|
14
17
|
const UNCERTAIN = { kind: 'transport-uncertainty' };
|
|
15
18
|
const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
|
|
19
|
+
const EXPIRED_ADDRESS = 403;
|
|
16
20
|
function lapsedGrant(outcome) {
|
|
17
21
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
18
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
|
+
}
|
|
19
49
|
export function createAgentsHouseAdapter(options) {
|
|
20
50
|
if ('site' in options)
|
|
21
|
-
return createSiteAdapter(options.site);
|
|
51
|
+
return createSiteAdapter(options.site, options.snapshot === true);
|
|
22
52
|
const transport = options.transport;
|
|
23
53
|
const notifications = houseNotifications(transport, 'credential');
|
|
24
54
|
const tracked = new Map();
|
|
25
55
|
const handed = new Map();
|
|
56
|
+
let heldRevisions;
|
|
26
57
|
let bounds;
|
|
27
58
|
function declared() {
|
|
28
59
|
if (bounds === undefined)
|
|
29
60
|
throw new Error('no Discovery declared the bounds');
|
|
30
61
|
return bounds;
|
|
31
62
|
}
|
|
32
|
-
async function
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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));
|
|
38
114
|
else
|
|
39
115
|
handed.delete(entry.locator);
|
|
40
116
|
notifications.reach(entry.authority, scope);
|
|
41
117
|
const seen = [
|
|
42
|
-
...
|
|
118
|
+
...continued.seen,
|
|
43
119
|
...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
|
|
44
120
|
];
|
|
45
|
-
const models = await partModels(scope, entry, new Set(seen),
|
|
46
|
-
const continuation = next === null ? null : { after: next, seen };
|
|
121
|
+
const models = await partModels(scope, entry, new Set(seen), !more);
|
|
47
122
|
return {
|
|
48
|
-
...
|
|
123
|
+
...(await stepped(bootstrapping(scope))),
|
|
49
124
|
...(models === undefined ? {} : { models }),
|
|
50
|
-
...(
|
|
125
|
+
...(more ? { pageToken: JSON.stringify({ ...continued, next, seen }) } : {}),
|
|
126
|
+
...(sent.frame === null ? {} : { received: sent.frame }),
|
|
51
127
|
};
|
|
52
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 });
|
|
146
|
+
}
|
|
53
147
|
async function enumeratedRoom(enumeration, ask) {
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
148
|
+
const first = viewOf(enumeration, ask.entry);
|
|
149
|
+
if (first === undefined)
|
|
56
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;
|
|
57
154
|
notifications.reach(ask.entry.authority, view);
|
|
58
|
-
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
155
|
+
const answer = await refreshFrom(view, ask.fromRoomVersion);
|
|
59
156
|
if (answer.kind !== 'net-advancement')
|
|
60
157
|
return answer;
|
|
61
|
-
handed.set(ask.entry.locator,
|
|
158
|
+
handed.set(ask.entry.locator, roomVersion(view));
|
|
62
159
|
const models = await viewModels(view, ask.entry);
|
|
63
160
|
return models === undefined ? answer : { ...answer, models };
|
|
64
161
|
}
|
|
65
162
|
async function streamed(entry, request, from) {
|
|
66
|
-
|
|
67
|
-
if (held?.roomVersion !== request.fromRoomVersion)
|
|
163
|
+
if (handed.get(entry.locator) !== request.fromRoomVersion)
|
|
68
164
|
return null;
|
|
69
|
-
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));
|
|
70
170
|
if (advanced === null)
|
|
71
171
|
return null;
|
|
72
|
-
handed.set(entry.locator,
|
|
172
|
+
handed.set(entry.locator, advanced.page.roomVersion);
|
|
73
173
|
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
74
174
|
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
75
175
|
}
|
|
@@ -79,20 +179,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
79
179
|
const at = logPosition(room.roomVersion);
|
|
80
180
|
if (entry === undefined || at === null)
|
|
81
181
|
return [];
|
|
82
|
-
handed.set(room.locator,
|
|
182
|
+
handed.set(room.locator, room.roomVersion);
|
|
83
183
|
notifications.reach(entry.authority, at);
|
|
84
184
|
return [presented(entry.authority, at)];
|
|
85
185
|
});
|
|
86
186
|
notifications.send(kept);
|
|
87
187
|
}
|
|
88
|
-
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) {
|
|
89
195
|
if (outcome.kind !== 'accepted')
|
|
90
196
|
return outcome;
|
|
91
197
|
const held = handed.get(entry.locator);
|
|
92
198
|
if (held === undefined)
|
|
93
199
|
return outcome;
|
|
94
|
-
const follows = followsVersion(held
|
|
95
|
-
|
|
200
|
+
const follows = followsVersion(held, outcome.roomVersion);
|
|
201
|
+
if (follows)
|
|
202
|
+
handed.set(entry.locator, outcome.roomVersion);
|
|
96
203
|
const at = logPosition(outcome.roomVersion);
|
|
97
204
|
if (follows && at !== null)
|
|
98
205
|
notifications.reach(entry.authority, at);
|
|
@@ -115,99 +222,106 @@ export function createAgentsHouseAdapter(options) {
|
|
|
115
222
|
return result;
|
|
116
223
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
117
224
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
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;
|
|
120
236
|
}
|
|
121
|
-
async function bootstrapped(
|
|
122
|
-
let open =
|
|
237
|
+
async function bootstrapped(asks, answer) {
|
|
238
|
+
let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
|
|
123
239
|
while (open.length > 0) {
|
|
124
240
|
const unreached = [];
|
|
125
|
-
for (const part of selections(open,
|
|
241
|
+
for (const part of selections(open, declared().max_prefixes)) {
|
|
242
|
+
const held = await holding(part.map(({ asked }) => asked));
|
|
126
243
|
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
127
|
-
prefixes: part.
|
|
244
|
+
prefixes: part.map(({ entry }) => authorityRoot(entry.handle)),
|
|
245
|
+
...(held.length === 0 ? {} : { held }),
|
|
128
246
|
});
|
|
129
247
|
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
130
|
-
const
|
|
248
|
+
const parted = classified[0] === null ? parseParted(answered.body) : null;
|
|
131
249
|
for (const [index, asked] of part.entries()) {
|
|
132
250
|
const failure = classified[index];
|
|
133
251
|
if (failure !== null && failure !== undefined) {
|
|
134
252
|
answer(asked.ask, failure);
|
|
135
253
|
continue;
|
|
136
254
|
}
|
|
137
|
-
if (
|
|
255
|
+
if (parted === null) {
|
|
138
256
|
answer(asked.ask, UNCERTAIN_READ);
|
|
139
257
|
continue;
|
|
140
258
|
}
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
259
|
+
const room = parted.rooms.find((candidate) => candidate.anchor === asked.entry.authority);
|
|
260
|
+
if (room === undefined && parted.next !== undefined) {
|
|
143
261
|
unreached.push(asked);
|
|
144
262
|
continue;
|
|
145
263
|
}
|
|
146
|
-
|
|
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
|
+
}));
|
|
147
275
|
}
|
|
148
276
|
}
|
|
149
277
|
open = unreached;
|
|
150
278
|
}
|
|
151
279
|
}
|
|
152
|
-
async function continuedBootstrap(entry, continued) {
|
|
153
|
-
const after = { room: entry.authority, path: continued.after };
|
|
154
|
-
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
155
|
-
prefixes: contentPrefixes(entry),
|
|
156
|
-
after,
|
|
157
|
-
});
|
|
158
|
-
const failure = classifyDoor(answered.status, answered.body, entry.locator);
|
|
159
|
-
if (failure !== null)
|
|
160
|
-
return failure;
|
|
161
|
-
const envelope = parseEnvelope(answered.body);
|
|
162
|
-
if (envelope === null)
|
|
163
|
-
return UNCERTAIN_READ;
|
|
164
|
-
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
|
|
165
|
-
return bootstrappedRoom(envelope, scope, entry, continued);
|
|
166
|
-
}
|
|
167
280
|
function layered(asks) {
|
|
168
281
|
const depths = new Map();
|
|
169
282
|
const layers = [];
|
|
170
283
|
for (const ask of asks) {
|
|
171
|
-
const depth = depths.get(ask.entry.authority) ?? 0;
|
|
172
|
-
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);
|
|
173
286
|
(layers[depth] ??= []).push(ask);
|
|
174
287
|
}
|
|
175
288
|
return layers;
|
|
176
289
|
}
|
|
177
290
|
async function enumerated(asks) {
|
|
178
|
-
const answers =
|
|
179
|
-
const parts = layered(asks
|
|
291
|
+
const answers = [];
|
|
292
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, declared().max_prefixes));
|
|
180
293
|
for (const part of parts) {
|
|
181
294
|
let open = [...part];
|
|
182
295
|
while (open.length > 0) {
|
|
183
296
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
184
|
-
prefixes: open.
|
|
185
|
-
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)),
|
|
186
299
|
});
|
|
187
|
-
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));
|
|
188
301
|
if (classified[0]?.kind === 'beyond-horizon') {
|
|
189
302
|
const named = doorRefusal(answer.body)?.fields.authority;
|
|
190
|
-
const expired = open.filter((ask) => ask.entry.authority === named);
|
|
303
|
+
const expired = open.filter(({ ask }) => ask.entry.authority === named);
|
|
191
304
|
const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
|
|
192
305
|
const gone = expired.length === 0 ? open : expired;
|
|
193
306
|
for (const ask of gone)
|
|
194
|
-
answers.
|
|
307
|
+
answers[ask.index] = answered;
|
|
195
308
|
open = open.filter((ask) => !gone.includes(ask));
|
|
196
309
|
continue;
|
|
197
310
|
}
|
|
198
311
|
const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
|
|
199
|
-
for (const [
|
|
200
|
-
const failure = classified[
|
|
312
|
+
for (const [answerIndex, { ask, index }] of open.entries()) {
|
|
313
|
+
const failure = classified[answerIndex];
|
|
201
314
|
if (failure !== null && failure !== undefined) {
|
|
202
|
-
answers
|
|
315
|
+
answers[index] = failure;
|
|
203
316
|
continue;
|
|
204
317
|
}
|
|
205
|
-
answers
|
|
318
|
+
answers[index] =
|
|
319
|
+
enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
|
|
206
320
|
}
|
|
207
321
|
open = [];
|
|
208
322
|
}
|
|
209
323
|
}
|
|
210
|
-
return
|
|
324
|
+
return answers;
|
|
211
325
|
}
|
|
212
326
|
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
213
327
|
const enumerateTogether = gathered(enumerated);
|
|
@@ -219,10 +333,12 @@ export function createAgentsHouseAdapter(options) {
|
|
|
219
333
|
}
|
|
220
334
|
const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
|
|
221
335
|
if (minted.status !== 200) {
|
|
222
|
-
return {
|
|
336
|
+
return {
|
|
337
|
+
outcome: await stepped(mutationOutcome(minted.status, minted.body, entry.locator, entry.handle)),
|
|
338
|
+
};
|
|
223
339
|
}
|
|
224
340
|
const grant = parseGrant(minted.body);
|
|
225
|
-
if (grant === null
|
|
341
|
+
if (grant === null)
|
|
226
342
|
return { outcome: UNCERTAIN };
|
|
227
343
|
const delivered = await transport.transfer({
|
|
228
344
|
act: 'authored_batch_upload',
|
|
@@ -232,36 +348,31 @@ export function createAgentsHouseAdapter(options) {
|
|
|
232
348
|
if (delivered.status !== 200)
|
|
233
349
|
return { outcome: UNCERTAIN };
|
|
234
350
|
const holding = parseGrant(delivered.body);
|
|
235
|
-
if (holding === null
|
|
351
|
+
if (holding === null)
|
|
236
352
|
return { outcome: UNCERTAIN };
|
|
237
353
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
238
354
|
await keep(upload);
|
|
239
355
|
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
240
356
|
}
|
|
241
|
-
async function staged(answer, batch) {
|
|
357
|
+
async function staged(answer, { changeBatch: batch, file }) {
|
|
242
358
|
const refusal = doorRefusal(answer.body);
|
|
243
359
|
if (refusal?.code !== 'files_unstaged')
|
|
244
360
|
return null;
|
|
245
|
-
const
|
|
246
|
-
if (!Array.isArray(
|
|
361
|
+
const uploads = refusal.fields.upload;
|
|
362
|
+
if (!Array.isArray(uploads))
|
|
247
363
|
return false;
|
|
248
|
-
for (const
|
|
249
|
-
const
|
|
250
|
-
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
251
|
-
if (
|
|
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)
|
|
252
368
|
return false;
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
url,
|
|
256
|
-
body: change.blob,
|
|
257
|
-
operation,
|
|
258
|
-
});
|
|
259
|
-
if (delivered.status !== 200)
|
|
369
|
+
const finished = await finishedUpload(transport, upload, await file(change.localId));
|
|
370
|
+
if (finished?.body?.staged !== true)
|
|
260
371
|
return false;
|
|
261
372
|
}
|
|
262
373
|
return true;
|
|
263
374
|
}
|
|
264
|
-
async function submit(
|
|
375
|
+
async function submit(request, head, changes, delta, bytes, entry, handed, keep, into) {
|
|
265
376
|
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
266
377
|
const prepared = uploadable(inline, bytes, declared())
|
|
267
378
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
@@ -269,15 +380,20 @@ export function createAgentsHouseAdapter(options) {
|
|
|
269
380
|
if ('outcome' in prepared)
|
|
270
381
|
return prepared.outcome;
|
|
271
382
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
272
|
-
const stagedFiles = await staged(answer,
|
|
383
|
+
const stagedFiles = await staged(answer, request);
|
|
273
384
|
if (stagedFiles === false)
|
|
274
385
|
return UNCERTAIN;
|
|
275
386
|
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
276
|
-
if (stagedFiles === true && (await staged(settled,
|
|
387
|
+
if (stagedFiles === true && (await staged(settled, request)) !== null)
|
|
277
388
|
return UNCERTAIN;
|
|
278
|
-
return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle
|
|
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
|
+
}));
|
|
279
395
|
}
|
|
280
|
-
|
|
396
|
+
function addressed(answer, locator) {
|
|
281
397
|
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
282
398
|
if (failure !== null) {
|
|
283
399
|
return failure.kind === 'scoped-refusal'
|
|
@@ -289,20 +405,55 @@ export function createAgentsHouseAdapter(options) {
|
|
|
289
405
|
}
|
|
290
406
|
: failure;
|
|
291
407
|
}
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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)
|
|
297
429
|
return UNCERTAIN_READ;
|
|
298
|
-
return {
|
|
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
|
+
});
|
|
299
446
|
}
|
|
300
447
|
return {
|
|
448
|
+
roomTypes: HOUSE_ROOM_TYPES,
|
|
301
449
|
async authorize() {
|
|
302
450
|
return options.authorize();
|
|
303
451
|
},
|
|
304
|
-
async discover(_authorization) {
|
|
305
|
-
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
|
+
});
|
|
306
457
|
const failed = discoveryFailure(answer.status, answer.body);
|
|
307
458
|
if (failed !== null)
|
|
308
459
|
return failed;
|
|
@@ -311,26 +462,42 @@ export function createAgentsHouseAdapter(options) {
|
|
|
311
462
|
return { kind: 'transport-uncertainty' };
|
|
312
463
|
bounds = document.bounds;
|
|
313
464
|
const entries = trackedFrom(document);
|
|
314
|
-
|
|
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);
|
|
315
480
|
for (const entry of entries)
|
|
316
481
|
tracked.set(entry.locator, entry);
|
|
317
482
|
for (const locator of handed.keys())
|
|
318
483
|
if (!tracked.has(locator))
|
|
319
484
|
handed.delete(locator);
|
|
320
|
-
notifications.track(
|
|
485
|
+
notifications.track([...tracked.values()]);
|
|
321
486
|
return {
|
|
322
|
-
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
487
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document.actions, [...tracked.values()])),
|
|
323
488
|
member: document.member,
|
|
489
|
+
...(covered === null ? {} : { covered }),
|
|
324
490
|
};
|
|
325
491
|
},
|
|
326
492
|
async bootstrap(_authorization, request) {
|
|
327
493
|
const entry = authorityFor(request.locator);
|
|
328
494
|
if (entry === undefined)
|
|
329
495
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
496
|
+
const asked = { entry, held: request.held };
|
|
330
497
|
if (request.pageToken !== undefined) {
|
|
331
|
-
return
|
|
498
|
+
return partPage(asked, JSON.parse(request.pageToken), request.received, request.arriving);
|
|
332
499
|
}
|
|
333
|
-
return bootstrapTogether(
|
|
500
|
+
return bootstrapTogether(asked);
|
|
334
501
|
},
|
|
335
502
|
async refresh(_authorization, request) {
|
|
336
503
|
const entry = authorityFor(request.locator);
|
|
@@ -350,18 +517,27 @@ export function createAgentsHouseAdapter(options) {
|
|
|
350
517
|
const entry = authorityFor(request.locator);
|
|
351
518
|
if (entry === undefined)
|
|
352
519
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
353
|
-
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);
|
|
354
529
|
if (mapped.kind === 'unmapped')
|
|
355
530
|
return rejectedMapping(mapped);
|
|
356
|
-
const head = operationHead(
|
|
531
|
+
const head = operationHead(batch);
|
|
357
532
|
const delta = deltaBody(mapped.changes);
|
|
358
533
|
const bytes = deltaBytes(delta);
|
|
359
534
|
const handed = request.upload;
|
|
360
|
-
const
|
|
361
|
-
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);
|
|
362
538
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
363
|
-
return
|
|
364
|
-
return
|
|
539
|
+
return settle(outcome);
|
|
540
|
+
return settle(await answer(undefined));
|
|
365
541
|
},
|
|
366
542
|
async history(_authorization, request) {
|
|
367
543
|
const entry = authorityFor(request.locator);
|
|
@@ -379,35 +555,16 @@ export function createAgentsHouseAdapter(options) {
|
|
|
379
555
|
});
|
|
380
556
|
},
|
|
381
557
|
async binary(_authorization, request) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const answer = await transport.door(DOOR_FILES_GET, {
|
|
387
|
-
room_ref: entry.authority,
|
|
388
|
-
path: request.path,
|
|
389
|
-
revision: request.revision,
|
|
390
|
-
});
|
|
391
|
-
return downloaded(answer, request.locator);
|
|
392
|
-
}
|
|
393
|
-
const referrer = houseReferrer(request.referrer, tracked);
|
|
394
|
-
if (referrer === null) {
|
|
395
|
-
return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
|
|
396
|
-
}
|
|
397
|
-
const answer = await transport.door(DOOR_FILES_GET, {
|
|
398
|
-
file: request.fileRef,
|
|
399
|
-
referrer: referrer.referrer,
|
|
400
|
-
});
|
|
401
|
-
return downloaded(answer, referrer.locator);
|
|
558
|
+
const ask = pathAddress(request);
|
|
559
|
+
if (ask === null)
|
|
560
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
561
|
+
return downloaded(ask, request.locator);
|
|
402
562
|
},
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
return
|
|
407
|
-
|
|
408
|
-
return { kind: 'conversations' };
|
|
409
|
-
const { path } = held.referrer;
|
|
410
|
-
return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
|
|
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);
|
|
411
568
|
},
|
|
412
569
|
reached(locator, version, target) {
|
|
413
570
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
@@ -418,7 +575,19 @@ export function createAgentsHouseAdapter(options) {
|
|
|
418
575
|
async action(_authorization, request) {
|
|
419
576
|
return withAt(await houseAction(transport, request));
|
|
420
577
|
},
|
|
421
|
-
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
|
+
},
|
|
422
591
|
conversations: notifications.conversations,
|
|
423
592
|
serviceMode: notifications.serviceMode,
|
|
424
593
|
};
|