@agentshouse/mdruntime-adapter 0.1.0-cc63dd043d0e2 → 0.1.0-cc710a8825617
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 +270 -183
- package/README.md +3 -3
- package/dist/actions.d.ts +63 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +154 -138
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +325 -166
- package/dist/discovery.d.ts +6 -12
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +32 -36
- package/dist/gather.d.ts +1 -0
- package/dist/gather.d.ts.map +1 -1
- package/dist/gather.js +11 -6
- package/dist/handed.d.ts +15 -0
- package/dist/handed.d.ts.map +1 -0
- package/dist/handed.js +38 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +7 -18
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +60 -39
- package/dist/read.d.ts +35 -7
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +164 -86
- package/dist/referrer.d.ts +7 -0
- package/dist/referrer.d.ts.map +1 -0
- package/dist/referrer.js +35 -0
- package/dist/refusal.d.ts +5 -9
- package/dist/refusal.d.ts.map +1 -1
- package/dist/refusal.js +31 -17
- package/dist/site.d.ts +1 -1
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +191 -107
- package/dist/stream.d.ts +10 -4
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +391 -47
- package/dist/transport.d.ts +79 -11
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +3 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -1
- package/dist/write.d.ts +2 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +96 -14
- package/package.json +3 -4
- package/dist/links.d.ts +0 -12
- package/dist/links.d.ts.map +0 -1
- package/dist/links.js +0 -66
package/dist/adapter.js
CHANGED
|
@@ -1,107 +1,171 @@
|
|
|
1
|
-
import { DISCOVER_PREFIXES, capabilities, discoveredRoom,
|
|
2
|
-
import {
|
|
3
|
-
import { gathered, selections } from './gather.js';
|
|
1
|
+
import { DISCOVER_PREFIXES, capabilities, discoveredRoom, mapDiscovery, parseDiscovery, trackedFrom, } from './discovery.js';
|
|
2
|
+
import { partModels, streamedModels, viewModels } from './model.js';
|
|
3
|
+
import { gathered, gatheredEach, selections } from './gather.js';
|
|
4
4
|
import { createSiteAdapter } from './site.js';
|
|
5
|
-
import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom,
|
|
6
|
-
import {
|
|
5
|
+
import { bootstrapOf, contentPrefixes, matchesScope, parseEnumeration, parseEnvelope, parseGrant, refreshFrom, viewOf, wholeView, } from './read.js';
|
|
6
|
+
import { sha256Hex } from './digest.js';
|
|
7
|
+
import { acceptedHanded, enumeratedHanded, heldHanded, scopeHanded, streamedHanded, } from './handed.js';
|
|
7
8
|
import { houseAction } from './actions.js';
|
|
8
|
-
import { classifyDoor, discoveryFailure, doorRefusal } from './refusal.js';
|
|
9
|
+
import { classifyDoor, classifyRead, discoveryFailure, doorRefusal } from './refusal.js';
|
|
9
10
|
import { houseNotifications } from './stream.js';
|
|
10
|
-
import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE,
|
|
11
|
+
import { DOOR_BATCH, DOOR_BATCH_UPLOAD, DOOR_BOOTSTRAP, DOOR_DISCOVER, DOOR_ENUMERATE, DOOR_FILES_GET, DOOR_REVISIONS, } from './transport.js';
|
|
11
12
|
import { batchPaths, deltaBody, deltaBytes, deltaDigest, historyOutcome, houseChanges, mutationOutcome, operationHead, rejectedMapping, revisionBody, uploadable, } from './write.js';
|
|
12
|
-
import {
|
|
13
|
+
import { houseReferrer } from './referrer.js';
|
|
14
|
+
import { followsVersion, logPosition, presented, reachedVersion, roomVersion, } from './version.js';
|
|
13
15
|
const UNCERTAIN = { kind: 'transport-uncertainty' };
|
|
14
16
|
const UNCERTAIN_READ = { kind: 'transport-uncertainty' };
|
|
15
17
|
function lapsedGrant(outcome) {
|
|
16
18
|
return outcome.kind === 'rejected' && outcome.code === 'upload_unavailable';
|
|
17
19
|
}
|
|
18
|
-
function
|
|
19
|
-
if (
|
|
20
|
-
return
|
|
20
|
+
function heldFiles(authority, held, after, budget) {
|
|
21
|
+
if (held === undefined || budget === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const paths = Object.keys(held.revisions)
|
|
24
|
+
.filter((path) => held.documents[path] !== undefined && (after === null || path > after))
|
|
25
|
+
.sort();
|
|
26
|
+
if (paths.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
const files = paths.slice(0, budget).map((path) => ({ path, revision: held.revisions[path] }));
|
|
29
|
+
return { room: authority, through: paths.length > budget ? files.at(-1).path : null, files };
|
|
30
|
+
}
|
|
31
|
+
async function reused(scope, held) {
|
|
32
|
+
const sent = new Set(scope.contents.map((entry) => entry.path));
|
|
33
|
+
const contents = [...scope.contents];
|
|
34
|
+
for (const file of scope.files) {
|
|
35
|
+
if (file.binary !== undefined || sent.has(file.path))
|
|
36
|
+
continue;
|
|
37
|
+
const content = held?.documents[file.path];
|
|
38
|
+
if (content === undefined ||
|
|
39
|
+
held?.revisions[file.path] !== file.revision ||
|
|
40
|
+
(await sha256Hex(content)) !== file.sha256) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
contents.push({ path: file.path, content });
|
|
21
44
|
}
|
|
22
|
-
return
|
|
45
|
+
return { ...scope, contents };
|
|
23
46
|
}
|
|
24
47
|
export function createAgentsHouseAdapter(options) {
|
|
25
48
|
if ('site' in options)
|
|
26
|
-
return createSiteAdapter(options.site);
|
|
49
|
+
return createSiteAdapter(options.site, options.snapshot === true);
|
|
27
50
|
const transport = options.transport;
|
|
28
51
|
const notifications = houseNotifications(transport, 'credential');
|
|
29
52
|
const tracked = new Map();
|
|
30
53
|
const handed = new Map();
|
|
31
|
-
const models = modelShelf();
|
|
32
54
|
let bounds;
|
|
33
55
|
function declared() {
|
|
34
56
|
if (bounds === undefined)
|
|
35
57
|
throw new Error('no Discovery declared the bounds');
|
|
36
58
|
return bounds;
|
|
37
59
|
}
|
|
38
|
-
function bootstrappedRoom(envelope,
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
60
|
+
async function bootstrappedRoom(envelope, sent, asked, continued) {
|
|
61
|
+
const { entry } = asked;
|
|
62
|
+
if (sent === undefined)
|
|
41
63
|
return { kind: 'room-not-readable', locator: entry.locator };
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
64
|
+
const scope = await reused(sent, asked.held);
|
|
65
|
+
if (scope === null) {
|
|
66
|
+
return asked.held === undefined
|
|
67
|
+
? UNCERTAIN_READ
|
|
68
|
+
: continued === null
|
|
69
|
+
? bootstrapTogether({ entry, held: undefined })
|
|
70
|
+
: continuedBootstrap({ entry, held: undefined }, continued);
|
|
71
|
+
}
|
|
72
|
+
const cursor = envelope.next?.room === entry.authority ? envelope.next : undefined;
|
|
73
|
+
const next = cursor?.path ?? null;
|
|
74
|
+
if (continued === null && next === null)
|
|
75
|
+
handed.set(entry.locator, scopeHanded(scope));
|
|
76
|
+
else
|
|
77
|
+
handed.delete(entry.locator);
|
|
78
|
+
notifications.reach(entry.authority, scope);
|
|
79
|
+
const seen = [
|
|
80
|
+
...(continued?.seen ?? []),
|
|
81
|
+
...entry.schemas.filter((schema) => scope.files.some((file) => file.path === schema)),
|
|
82
|
+
];
|
|
83
|
+
const models = await partModels(scope, entry, new Set(seen), next === null);
|
|
84
|
+
const continuation = next === null
|
|
85
|
+
? null
|
|
86
|
+
: { after: next, seen, ...(cursor?.cut === undefined ? {} : { cut: cursor.cut }) };
|
|
87
|
+
return {
|
|
88
|
+
...bootstrapOf(scope),
|
|
89
|
+
...(models === undefined ? {} : { models }),
|
|
90
|
+
...(continuation === null ? {} : { pageToken: JSON.stringify(continuation) }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function enumeratedPage(ask, page) {
|
|
94
|
+
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
95
|
+
prefixes: contentPrefixes(ask.entry),
|
|
96
|
+
positions: [
|
|
97
|
+
{
|
|
98
|
+
...presented(ask.entry.authority, { position: page.after, logEpoch: ask.from.logEpoch }),
|
|
99
|
+
page: { through: page.through, read: page.read },
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
const failure = classifyRead(answer.status, answer.body, ask.entry.locator);
|
|
104
|
+
if (failure !== null)
|
|
105
|
+
return failure;
|
|
106
|
+
const enumeration = parseEnumeration(answer.body, 'credential');
|
|
107
|
+
if (enumeration === null)
|
|
108
|
+
return UNCERTAIN_READ;
|
|
109
|
+
return (viewOf(enumeration, ask.entry) ?? { kind: 'room-not-readable', locator: ask.entry.locator });
|
|
45
110
|
}
|
|
46
|
-
function enumeratedRoom(enumeration, ask) {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
111
|
+
async function enumeratedRoom(enumeration, ask) {
|
|
112
|
+
const first = viewOf(enumeration, ask.entry);
|
|
113
|
+
if (first === undefined)
|
|
49
114
|
return { kind: 'room-not-readable', locator: ask.entry.locator };
|
|
50
|
-
|
|
115
|
+
const view = await wholeView(first, (page) => enumeratedPage(ask, page));
|
|
116
|
+
if ('kind' in view)
|
|
117
|
+
return view;
|
|
118
|
+
notifications.reach(ask.entry.authority, view);
|
|
51
119
|
const answer = refreshFrom(view, ask.fromRoomVersion);
|
|
52
|
-
if (answer.kind
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
120
|
+
if (answer.kind !== 'net-advancement')
|
|
121
|
+
return answer;
|
|
122
|
+
handed.set(ask.entry.locator, enumeratedHanded(view, handed.get(ask.entry.locator)));
|
|
123
|
+
const models = await viewModels(view, ask.entry);
|
|
124
|
+
return models === undefined ? answer : { ...answer, models };
|
|
56
125
|
}
|
|
57
|
-
function streamed(entry, request, from) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
126
|
+
async function streamed(entry, request, from) {
|
|
127
|
+
const held = handed.get(entry.locator);
|
|
128
|
+
if (held?.roomVersion !== request.fromRoomVersion)
|
|
60
129
|
return null;
|
|
61
|
-
const advanced = notifications.advance(entry, from, (path, revision) =>
|
|
130
|
+
const advanced = notifications.advance(entry, from, (path, revision) => held.revisions.get(path) === revision);
|
|
62
131
|
if (advanced === null)
|
|
63
132
|
return null;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const relinked = streamedLinks(links, advanced);
|
|
68
|
-
if (relinked === null)
|
|
69
|
-
return null;
|
|
70
|
-
handed.set(entry.locator, relinked);
|
|
71
|
-
return advanced.page;
|
|
72
|
-
}
|
|
73
|
-
function held(envelope, entry, schema, failure) {
|
|
74
|
-
return heldModel(envelope === null ? null : schemaFrom(envelope, entry, schema), schemaPath(entry.handle, schema), failure);
|
|
75
|
-
}
|
|
76
|
-
function modelPrefixes(entry) {
|
|
77
|
-
return entry.schemas.map((schema) => schemaPath(entry.handle, schema));
|
|
133
|
+
handed.set(entry.locator, streamedHanded(held, advanced));
|
|
134
|
+
const models = await streamedModels(advanced.schemas, entry, 'credential');
|
|
135
|
+
return models === undefined ? advanced.page : { ...advanced.page, models };
|
|
78
136
|
}
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
137
|
+
function held(rooms, demand) {
|
|
138
|
+
const kept = rooms.flatMap((room) => {
|
|
139
|
+
const entry = tracked.get(room.locator);
|
|
140
|
+
const at = logPosition(room.roomVersion);
|
|
141
|
+
if (entry === undefined || at === null)
|
|
142
|
+
return [];
|
|
143
|
+
handed.set(room.locator, heldHanded(room));
|
|
144
|
+
notifications.reach(entry.authority, at);
|
|
145
|
+
return [presented(entry.authority, at)];
|
|
146
|
+
});
|
|
147
|
+
notifications.send(kept, demand === null
|
|
148
|
+
? null
|
|
149
|
+
: {
|
|
150
|
+
rooms: demand.rooms.flatMap((locator) => {
|
|
151
|
+
const entry = tracked.get(locator);
|
|
152
|
+
return entry === undefined ? [] : [entry.authority];
|
|
153
|
+
}),
|
|
154
|
+
conversations: demand.conversations,
|
|
84
155
|
});
|
|
85
|
-
const classified = classifyDoor(answer.status, answer.body, PRIVATE_LOCATOR);
|
|
86
|
-
const envelope = classified === null ? parseEnvelope(await bundled(answer.body)) : null;
|
|
87
|
-
const failure = modelFailure(classified, envelope);
|
|
88
|
-
for (const entry of part) {
|
|
89
|
-
acquired.set(modelKey(entry.locator, 'collection'), await held(envelope, entry, entry.schema, failure));
|
|
90
|
-
if (entry.houseModel === null)
|
|
91
|
-
continue;
|
|
92
|
-
acquired.set(modelKey(entry.locator, 'typed-selection'), await held(envelope, entry, entry.houseModel.root, failure));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return acquired;
|
|
96
156
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
function adopted(entry, outcome) {
|
|
158
|
+
if (outcome.kind !== 'accepted')
|
|
159
|
+
return outcome;
|
|
160
|
+
const held = handed.get(entry.locator);
|
|
161
|
+
if (held === undefined)
|
|
162
|
+
return outcome;
|
|
163
|
+
const follows = followsVersion(held.roomVersion, outcome.roomVersion);
|
|
164
|
+
handed.set(entry.locator, acceptedHanded(held, outcome, follows ? outcome.roomVersion : held.roomVersion));
|
|
165
|
+
const at = logPosition(outcome.roomVersion);
|
|
166
|
+
if (follows && at !== null)
|
|
167
|
+
notifications.reach(entry.authority, at);
|
|
168
|
+
return outcome;
|
|
105
169
|
}
|
|
106
170
|
function authorityFor(locator) {
|
|
107
171
|
return tracked.get(locator);
|
|
@@ -115,116 +179,126 @@ export function createAgentsHouseAdapter(options) {
|
|
|
115
179
|
const { authority, position, log_epoch: logEpoch } = body;
|
|
116
180
|
if (typeof position !== 'string' || typeof logEpoch !== 'string')
|
|
117
181
|
return result;
|
|
118
|
-
const locator = authority ===
|
|
119
|
-
? PRIVATE_LOCATOR
|
|
120
|
-
: [...tracked.values()].find((entry) => entry.authority === authority)?.locator;
|
|
182
|
+
const locator = [...tracked.values()].find((entry) => entry.authority === authority)?.locator;
|
|
121
183
|
if (locator === undefined)
|
|
122
184
|
return result;
|
|
123
185
|
return { ...result, at: { locator, roomVersion: roomVersion({ position, logEpoch }) } };
|
|
124
186
|
}
|
|
125
|
-
async function bundled(body) {
|
|
126
|
-
const grant = parseGrant(body);
|
|
127
|
-
if (grant === null)
|
|
128
|
-
return body;
|
|
129
|
-
if (grant.method !== 'GET')
|
|
130
|
-
return null;
|
|
131
|
-
const answer = await transport.transfer({
|
|
132
|
-
act: 'local_copy_bundle',
|
|
133
|
-
method: 'GET',
|
|
134
|
-
url: grant.url,
|
|
135
|
-
answer: 'json',
|
|
136
|
-
});
|
|
137
|
-
return answer.status === 200 ? answer.body : null;
|
|
138
|
-
}
|
|
139
187
|
function weight(entry) {
|
|
140
188
|
return contentPrefixes(entry).length;
|
|
141
189
|
}
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
part
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
190
|
+
function holding(asks, after) {
|
|
191
|
+
const budget = Math.floor(declared().max_held_files / Math.max(1, asks.filter((ask) => ask.held !== undefined).length));
|
|
192
|
+
return asks.flatMap((ask) => heldFiles(ask.entry.authority, ask.held, after, budget) ?? []);
|
|
193
|
+
}
|
|
194
|
+
async function bootstrapped(asks, answer) {
|
|
195
|
+
let open = asks.map((asked, ask) => ({ entry: asked.entry, asked, ask }));
|
|
196
|
+
while (open.length > 0) {
|
|
197
|
+
const unreached = [];
|
|
198
|
+
for (const part of selections(open, ({ entry }) => weight(entry), declared().max_prefixes)) {
|
|
199
|
+
const held = holding(part.map(({ asked }) => asked), null);
|
|
200
|
+
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
201
|
+
prefixes: part.flatMap(({ entry }) => contentPrefixes(entry)),
|
|
202
|
+
...(held.length === 0 ? {} : { held }),
|
|
203
|
+
});
|
|
204
|
+
const classified = part.map(({ entry }) => classifyDoor(answered.status, answered.body, entry.locator));
|
|
205
|
+
const envelope = classified[0] === null ? parseEnvelope(answered.body) : null;
|
|
206
|
+
for (const [index, asked] of part.entries()) {
|
|
207
|
+
const failure = classified[index];
|
|
208
|
+
if (failure !== null && failure !== undefined) {
|
|
209
|
+
answer(asked.ask, failure);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (envelope === null) {
|
|
213
|
+
answer(asked.ask, UNCERTAIN_READ);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, asked.entry));
|
|
217
|
+
if (scope === undefined && envelope.next !== undefined) {
|
|
218
|
+
unreached.push(asked);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
answer(asked.ask, await bootstrappedRoom(envelope, scope, asked.asked, null));
|
|
155
222
|
}
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
}
|
|
224
|
+
open = unreached;
|
|
158
225
|
}
|
|
159
|
-
|
|
226
|
+
}
|
|
227
|
+
async function continuedBootstrap(asked, continued) {
|
|
228
|
+
const { entry } = asked;
|
|
229
|
+
const after = {
|
|
230
|
+
room: entry.authority,
|
|
231
|
+
path: continued.after,
|
|
232
|
+
...(continued.cut === undefined ? {} : { cut: continued.cut }),
|
|
233
|
+
};
|
|
234
|
+
const held = holding([asked], continued.after);
|
|
235
|
+
const answered = await transport.door(DOOR_BOOTSTRAP, {
|
|
236
|
+
prefixes: contentPrefixes(entry),
|
|
237
|
+
after,
|
|
238
|
+
...(held.length === 0 ? {} : { held }),
|
|
239
|
+
});
|
|
240
|
+
const failure = classifyRead(answered.status, answered.body, entry.locator);
|
|
241
|
+
if (failure !== null)
|
|
242
|
+
return failure;
|
|
243
|
+
const envelope = parseEnvelope(answered.body);
|
|
244
|
+
if (envelope === null)
|
|
245
|
+
return UNCERTAIN_READ;
|
|
246
|
+
const scope = envelope.scopes.find((candidate) => matchesScope(candidate, entry));
|
|
247
|
+
return bootstrappedRoom(envelope, scope, asked, continued);
|
|
160
248
|
}
|
|
161
249
|
function layered(asks) {
|
|
162
250
|
const depths = new Map();
|
|
163
251
|
const layers = [];
|
|
164
252
|
for (const ask of asks) {
|
|
165
|
-
const depth = depths.get(ask.entry.authority) ?? 0;
|
|
166
|
-
depths.set(ask.entry.authority, depth + 1);
|
|
253
|
+
const depth = depths.get(ask.ask.entry.authority) ?? 0;
|
|
254
|
+
depths.set(ask.ask.entry.authority, depth + 1);
|
|
167
255
|
(layers[depth] ??= []).push(ask);
|
|
168
256
|
}
|
|
169
257
|
return layers;
|
|
170
258
|
}
|
|
171
259
|
async function enumerated(asks) {
|
|
172
|
-
const answers =
|
|
173
|
-
const parts = layered(asks).flatMap((layer) => selections(layer, (ask) => weight(ask.entry), declared().max_prefixes));
|
|
260
|
+
const answers = [];
|
|
261
|
+
const parts = layered(asks.map((ask, index) => ({ ask, index }))).flatMap((layer) => selections(layer, ({ ask }) => weight(ask.entry), declared().max_prefixes));
|
|
174
262
|
for (const part of parts) {
|
|
175
263
|
let open = [...part];
|
|
176
264
|
while (open.length > 0) {
|
|
177
265
|
const answer = await transport.door(DOOR_ENUMERATE, {
|
|
178
|
-
prefixes: open.flatMap((ask) => contentPrefixes(ask.entry)),
|
|
179
|
-
positions: open.map((ask) => (
|
|
180
|
-
...presented(ask.entry.authority, ask.from),
|
|
181
|
-
...(ask.minPosition === undefined ? {} : { min_position: ask.minPosition }),
|
|
182
|
-
})),
|
|
266
|
+
prefixes: open.flatMap(({ ask }) => contentPrefixes(ask.entry)),
|
|
267
|
+
positions: open.map(({ ask }) => presented(ask.entry.authority, ask.from)),
|
|
183
268
|
});
|
|
184
|
-
const
|
|
185
|
-
if (refusal?.code === 'replica_behind') {
|
|
186
|
-
const named = open.filter((ask) => ask.entry.authority === refusal.fields.authority);
|
|
187
|
-
const behind = named.length === 0 ? open : named;
|
|
188
|
-
for (const ask of behind) {
|
|
189
|
-
answers.set(ask, refusal.retryable
|
|
190
|
-
? { kind: 'retryable-refusal', code: refusal.code, fields: refusal.fields }
|
|
191
|
-
: (classifyDoor(answer.status, answer.body, ask.entry.locator) ?? UNCERTAIN_READ));
|
|
192
|
-
}
|
|
193
|
-
open = open.filter((ask) => !behind.includes(ask));
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
const classified = open.map((ask) => classifyDoor(answer.status, answer.body, ask.entry.locator));
|
|
269
|
+
const classified = open.map(({ ask }) => classifyRead(answer.status, answer.body, ask.entry.locator));
|
|
197
270
|
if (classified[0]?.kind === 'beyond-horizon') {
|
|
198
271
|
const named = doorRefusal(answer.body)?.fields.authority;
|
|
199
|
-
const expired = open.filter((ask) => ask.entry.authority === named);
|
|
272
|
+
const expired = open.filter(({ ask }) => ask.entry.authority === named);
|
|
200
273
|
const answered = expired.length === 0 ? UNCERTAIN_READ : { kind: 'beyond-horizon' };
|
|
201
274
|
const gone = expired.length === 0 ? open : expired;
|
|
202
275
|
for (const ask of gone)
|
|
203
|
-
answers.
|
|
276
|
+
answers[ask.index] = answered;
|
|
204
277
|
open = open.filter((ask) => !gone.includes(ask));
|
|
205
278
|
continue;
|
|
206
279
|
}
|
|
207
280
|
const enumeration = classified[0] === null ? parseEnumeration(answer.body, 'credential') : null;
|
|
208
|
-
|
|
209
|
-
const failure = classified[
|
|
281
|
+
for (const [answerIndex, { ask, index }] of open.entries()) {
|
|
282
|
+
const failure = classified[answerIndex];
|
|
210
283
|
if (failure !== null && failure !== undefined) {
|
|
211
|
-
answers
|
|
212
|
-
|
|
284
|
+
answers[index] = failure;
|
|
285
|
+
continue;
|
|
213
286
|
}
|
|
214
|
-
answers
|
|
215
|
-
|
|
287
|
+
answers[index] =
|
|
288
|
+
enumeration === null ? UNCERTAIN_READ : await enumeratedRoom(enumeration, ask);
|
|
289
|
+
}
|
|
216
290
|
open = [];
|
|
217
291
|
}
|
|
218
292
|
}
|
|
219
|
-
return
|
|
293
|
+
return answers;
|
|
220
294
|
}
|
|
221
|
-
const bootstrapTogether =
|
|
295
|
+
const bootstrapTogether = gatheredEach(bootstrapped);
|
|
222
296
|
const enumerateTogether = gathered(enumerated);
|
|
223
297
|
async function uploaded(head, changes, delta, bytes, entry, handed, keep) {
|
|
224
298
|
const paths = batchPaths(changes);
|
|
225
299
|
const sha256 = await deltaDigest(delta);
|
|
226
300
|
if (handed !== undefined && handed.sha256 === sha256) {
|
|
227
|
-
return { body: { ...head, paths, upload: handed } };
|
|
301
|
+
return { body: { ...head, paths, upload: handed, runtime: notifications.runtime } };
|
|
228
302
|
}
|
|
229
303
|
const minted = await transport.door(DOOR_BATCH_UPLOAD, { ...head, paths, bytes, sha256 });
|
|
230
304
|
if (minted.status !== 200) {
|
|
@@ -235,9 +309,7 @@ export function createAgentsHouseAdapter(options) {
|
|
|
235
309
|
return { outcome: UNCERTAIN };
|
|
236
310
|
const delivered = await transport.transfer({
|
|
237
311
|
act: 'authored_batch_upload',
|
|
238
|
-
method: 'POST',
|
|
239
312
|
url: grant.url,
|
|
240
|
-
answer: 'json',
|
|
241
313
|
body: `${grant.operation}\n${delta}`,
|
|
242
314
|
});
|
|
243
315
|
if (delivered.status !== 200)
|
|
@@ -247,30 +319,76 @@ export function createAgentsHouseAdapter(options) {
|
|
|
247
319
|
return { outcome: UNCERTAIN };
|
|
248
320
|
const upload = { url: holding.url, operation: holding.operation, bytes, sha256 };
|
|
249
321
|
await keep(upload);
|
|
250
|
-
return { body: { ...head, paths, upload } };
|
|
322
|
+
return { body: { ...head, paths, upload, runtime: notifications.runtime } };
|
|
323
|
+
}
|
|
324
|
+
async function staged(answer, batch) {
|
|
325
|
+
const refusal = doorRefusal(answer.body);
|
|
326
|
+
if (refusal?.code !== 'files_unstaged')
|
|
327
|
+
return null;
|
|
328
|
+
const grants = refusal.fields.upload;
|
|
329
|
+
if (!Array.isArray(grants))
|
|
330
|
+
return false;
|
|
331
|
+
for (const grant of grants) {
|
|
332
|
+
const { url, operation, sha256 } = grant;
|
|
333
|
+
const change = batch.changes.find((held) => held.kind === 'binary' && held.sha256 === sha256);
|
|
334
|
+
if (typeof url !== 'string' || typeof operation !== 'string' || change === undefined)
|
|
335
|
+
return false;
|
|
336
|
+
const delivered = await transport.transfer({
|
|
337
|
+
act: 'binary_stage',
|
|
338
|
+
url,
|
|
339
|
+
body: change.blob,
|
|
340
|
+
operation,
|
|
341
|
+
});
|
|
342
|
+
if (delivered.status !== 200)
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
251
346
|
}
|
|
252
|
-
async function submit(head, changes, delta, bytes, entry, handed, keep) {
|
|
253
|
-
const inline = { ...head, changes };
|
|
347
|
+
async function submit(batch, head, changes, delta, bytes, entry, handed, keep) {
|
|
348
|
+
const inline = { ...head, changes, runtime: notifications.runtime };
|
|
254
349
|
const prepared = uploadable(inline, bytes, declared())
|
|
255
350
|
? await uploaded(head, changes, delta, bytes, entry, handed, keep)
|
|
256
351
|
: { body: inline };
|
|
257
352
|
if ('outcome' in prepared)
|
|
258
353
|
return prepared.outcome;
|
|
259
354
|
const answer = await transport.door(DOOR_BATCH, prepared.body);
|
|
260
|
-
|
|
355
|
+
const stagedFiles = await staged(answer, batch);
|
|
356
|
+
if (stagedFiles === false)
|
|
357
|
+
return UNCERTAIN;
|
|
358
|
+
const settled = stagedFiles === null ? answer : await transport.door(DOOR_BATCH, prepared.body);
|
|
359
|
+
if (stagedFiles === true && (await staged(settled, batch)) !== null)
|
|
360
|
+
return UNCERTAIN;
|
|
361
|
+
return mutationOutcome(settled.status, settled.body, entry.locator, entry.handle);
|
|
362
|
+
}
|
|
363
|
+
async function downloaded(answer, locator) {
|
|
364
|
+
const failure = classifyDoor(answer.status, answer.body, locator);
|
|
365
|
+
if (failure !== null) {
|
|
366
|
+
return failure.kind === 'scoped-refusal'
|
|
367
|
+
? {
|
|
368
|
+
kind: 'rejected',
|
|
369
|
+
code: failure.code,
|
|
370
|
+
fields: failure.fields,
|
|
371
|
+
retryable: failure.retryable,
|
|
372
|
+
}
|
|
373
|
+
: failure;
|
|
374
|
+
}
|
|
375
|
+
const grant = parseGrant(answer.body);
|
|
376
|
+
if (grant === null || grant.method !== 'GET')
|
|
377
|
+
return UNCERTAIN_READ;
|
|
378
|
+
const read = await transport.download(grant.url);
|
|
379
|
+
if (read.status !== 200 || read.blob === null)
|
|
380
|
+
return UNCERTAIN_READ;
|
|
381
|
+
return { kind: 'binary', blob: read.blob };
|
|
261
382
|
}
|
|
262
383
|
return {
|
|
263
384
|
async authorize() {
|
|
264
385
|
return options.authorize();
|
|
265
386
|
},
|
|
266
|
-
async model(_authorization, request) {
|
|
267
|
-
return models.answer(request);
|
|
268
|
-
},
|
|
269
387
|
async discover(_authorization) {
|
|
270
388
|
const answer = await transport.door(DOOR_DISCOVER, { prefixes: [...DISCOVER_PREFIXES] });
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
return
|
|
389
|
+
const failed = discoveryFailure(answer.status, answer.body);
|
|
390
|
+
if (failed !== null)
|
|
391
|
+
return failed;
|
|
274
392
|
const document = parseDiscovery(answer.body);
|
|
275
393
|
if (document === null)
|
|
276
394
|
return { kind: 'transport-uncertainty' };
|
|
@@ -283,17 +401,20 @@ export function createAgentsHouseAdapter(options) {
|
|
|
283
401
|
if (!tracked.has(locator))
|
|
284
402
|
handed.delete(locator);
|
|
285
403
|
notifications.track(entries);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
404
|
+
return {
|
|
405
|
+
...mapDiscovery(document, document.authorities.map((authority) => discoveredRoom(authority)), capabilities(document)),
|
|
406
|
+
member: document.member,
|
|
407
|
+
};
|
|
289
408
|
},
|
|
290
409
|
async bootstrap(_authorization, request) {
|
|
291
410
|
const entry = authorityFor(request.locator);
|
|
292
411
|
if (entry === undefined)
|
|
293
412
|
return { kind: 'room-not-readable', locator: request.locator };
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
413
|
+
const asked = { entry, held: request.held };
|
|
414
|
+
if (request.pageToken !== undefined) {
|
|
415
|
+
return continuedBootstrap(asked, JSON.parse(request.pageToken));
|
|
416
|
+
}
|
|
417
|
+
return bootstrapTogether(asked);
|
|
297
418
|
},
|
|
298
419
|
async refresh(_authorization, request) {
|
|
299
420
|
const entry = authorityFor(request.locator);
|
|
@@ -304,16 +425,10 @@ export function createAgentsHouseAdapter(options) {
|
|
|
304
425
|
const from = logPosition(request.fromRoomVersion);
|
|
305
426
|
if (from === null)
|
|
306
427
|
return { kind: 'beyond-horizon' };
|
|
307
|
-
const answered = streamed(entry, request, from);
|
|
428
|
+
const answered = await streamed(entry, request, from);
|
|
308
429
|
if (answered !== null)
|
|
309
430
|
return answered;
|
|
310
|
-
|
|
311
|
-
return enumerateTogether({
|
|
312
|
-
entry,
|
|
313
|
-
from,
|
|
314
|
-
fromRoomVersion: request.fromRoomVersion,
|
|
315
|
-
...(minPosition?.logEpoch === from.logEpoch ? { minPosition: minPosition.position } : {}),
|
|
316
|
-
});
|
|
431
|
+
return enumerateTogether({ entry, from, fromRoomVersion: request.fromRoomVersion });
|
|
317
432
|
},
|
|
318
433
|
async mutate(_authorization, request, keep) {
|
|
319
434
|
const entry = authorityFor(request.locator);
|
|
@@ -326,10 +441,11 @@ export function createAgentsHouseAdapter(options) {
|
|
|
326
441
|
const delta = deltaBody(mapped.changes);
|
|
327
442
|
const bytes = deltaBytes(delta);
|
|
328
443
|
const handed = request.upload;
|
|
329
|
-
const
|
|
444
|
+
const batch = request.changeBatch;
|
|
445
|
+
const outcome = await submit(batch, head, mapped.changes, delta, bytes, entry, handed, keep);
|
|
330
446
|
if (handed === undefined || !lapsedGrant(outcome))
|
|
331
|
-
return outcome;
|
|
332
|
-
return submit(head, mapped.changes, delta, bytes, entry, undefined, keep);
|
|
447
|
+
return adopted(entry, outcome);
|
|
448
|
+
return adopted(entry, await submit(batch, head, mapped.changes, delta, bytes, entry, undefined, keep));
|
|
333
449
|
},
|
|
334
450
|
async history(_authorization, request) {
|
|
335
451
|
const entry = authorityFor(request.locator);
|
|
@@ -346,13 +462,56 @@ export function createAgentsHouseAdapter(options) {
|
|
|
346
462
|
...(request.revision === undefined ? {} : { revision: request.revision }),
|
|
347
463
|
});
|
|
348
464
|
},
|
|
465
|
+
async binary(_authorization, request) {
|
|
466
|
+
if (request.kind === 'path') {
|
|
467
|
+
const entry = authorityFor(request.locator);
|
|
468
|
+
if (entry === undefined)
|
|
469
|
+
return { kind: 'room-not-readable', locator: request.locator };
|
|
470
|
+
const answer = await transport.door(DOOR_FILES_GET, {
|
|
471
|
+
room_ref: entry.authority,
|
|
472
|
+
path: request.path,
|
|
473
|
+
revision: request.revision,
|
|
474
|
+
});
|
|
475
|
+
return downloaded(answer, request.locator);
|
|
476
|
+
}
|
|
477
|
+
const referrer = houseReferrer(request.referrer, tracked);
|
|
478
|
+
if (referrer === null) {
|
|
479
|
+
return { kind: 'rejected', code: 'referrer_unknown', fields: {}, retryable: false };
|
|
480
|
+
}
|
|
481
|
+
const answer = await transport.door(DOOR_FILES_GET, {
|
|
482
|
+
file: request.fileRef,
|
|
483
|
+
referrer: referrer.referrer,
|
|
484
|
+
});
|
|
485
|
+
return downloaded(answer, referrer.locator);
|
|
486
|
+
},
|
|
487
|
+
referrerScope(referrer) {
|
|
488
|
+
const held = houseReferrer(referrer, tracked);
|
|
489
|
+
if (held === null)
|
|
490
|
+
return null;
|
|
491
|
+
if (held.locator === '')
|
|
492
|
+
return { kind: 'conversations' };
|
|
493
|
+
const { path } = held.referrer;
|
|
494
|
+
return { kind: 'room', locator: held.locator, ...(path === undefined ? {} : { path }) };
|
|
495
|
+
},
|
|
349
496
|
reached(locator, version, target) {
|
|
350
497
|
return reachedVersion(version, target, authorityFor(locator)?.logEpoch);
|
|
351
498
|
},
|
|
499
|
+
follows(_locator, version, next) {
|
|
500
|
+
return followsVersion(version, next);
|
|
501
|
+
},
|
|
352
502
|
async action(_authorization, request) {
|
|
353
503
|
return withAt(await houseAction(transport, request));
|
|
354
504
|
},
|
|
355
|
-
|
|
505
|
+
roomStream: {
|
|
506
|
+
hold: held,
|
|
507
|
+
reopen: notifications.reopen,
|
|
508
|
+
observe: notifications.observe,
|
|
509
|
+
published(locator, roomVersion) {
|
|
510
|
+
const at = logPosition(roomVersion);
|
|
511
|
+
if (at !== null)
|
|
512
|
+
notifications.published(locator, at);
|
|
513
|
+
},
|
|
514
|
+
},
|
|
356
515
|
conversations: notifications.conversations,
|
|
357
516
|
serviceMode: notifications.serviceMode,
|
|
358
517
|
};
|