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