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