@agentshouse/mdruntime 0.1.0-c6e4c19c13638 → 0.1.0-c6fd2cbbff187
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 +277 -219
- package/README.md +20 -10
- package/dist/acquisition.d.ts +14 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +24 -0
- package/dist/adapter.d.ts +94 -20
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +15 -2
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +23 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +18 -5
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +113 -37
- package/dist/change-set.d.ts +2 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +49 -16
- package/dist/change.d.ts +15 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +26 -1
- package/dist/chat/acts.d.ts +33 -0
- package/dist/chat/acts.d.ts.map +1 -0
- package/dist/chat/acts.js +36 -0
- package/dist/chat/context.d.ts +3 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +10 -0
- package/dist/chat/earlier.d.ts +10 -0
- package/dist/chat/earlier.d.ts.map +1 -0
- package/dist/chat/earlier.js +54 -0
- package/dist/chat/input.d.ts +9 -0
- package/dist/chat/input.d.ts.map +1 -0
- package/dist/chat/input.js +99 -0
- package/dist/chat/kit.d.ts +61 -0
- package/dist/chat/kit.d.ts.map +1 -0
- package/dist/chat/kit.js +123 -0
- package/dist/chat/lasting.d.ts +6 -0
- package/dist/chat/lasting.d.ts.map +1 -0
- package/dist/chat/lasting.js +19 -0
- package/dist/chat/list.d.ts +27 -0
- package/dist/chat/list.d.ts.map +1 -0
- package/dist/chat/list.js +99 -0
- package/dist/chat/markdown.d.ts +20 -0
- package/dist/chat/markdown.d.ts.map +1 -0
- package/dist/chat/markdown.js +188 -0
- package/dist/chat/message.d.ts +6 -0
- package/dist/chat/message.d.ts.map +1 -0
- package/dist/chat/message.js +192 -0
- package/dist/chat/messages.d.ts +22 -0
- package/dist/chat/messages.d.ts.map +1 -0
- package/dist/chat/messages.js +155 -0
- package/dist/chat/name.d.ts +6 -0
- package/dist/chat/name.d.ts.map +1 -0
- package/dist/chat/name.js +37 -0
- package/dist/chat/opened.d.ts +11 -0
- package/dist/chat/opened.d.ts.map +1 -0
- package/dist/chat/opened.js +17 -0
- package/dist/chat/overlay.d.ts +31 -0
- package/dist/chat/overlay.d.ts.map +1 -0
- package/dist/chat/overlay.js +97 -0
- package/dist/chat/parts.d.ts +57 -0
- package/dist/chat/parts.d.ts.map +1 -0
- package/dist/chat/parts.js +119 -0
- package/dist/chat/process.d.ts +20 -0
- package/dist/chat/process.d.ts.map +1 -0
- package/dist/chat/process.js +32 -0
- package/dist/chat/provider.d.ts +63 -0
- package/dist/chat/provider.d.ts.map +1 -0
- package/dist/chat/provider.js +635 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +69 -0
- package/dist/chat/questions.d.ts +31 -0
- package/dist/chat/questions.d.ts.map +1 -0
- package/dist/chat/questions.js +111 -0
- package/dist/chat/read.d.ts +5 -0
- package/dist/chat/read.d.ts.map +1 -0
- package/dist/chat/read.js +13 -0
- package/dist/chat/ready.d.ts +34 -0
- package/dist/chat/ready.d.ts.map +1 -0
- package/dist/chat/ready.js +91 -0
- package/dist/chat/style.d.ts +4 -0
- package/dist/chat/style.d.ts.map +1 -0
- package/dist/chat/style.js +302 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +8 -0
- package/dist/client.d.ts +27 -12
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +77 -16
- package/dist/collection.d.ts +21 -12
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +271 -23
- package/dist/connection.d.ts +46 -15
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1946 -414
- package/dist/conversation.d.ts +30 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +485 -159
- package/dist/durability.d.ts +7 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +17 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +156 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/host.d.ts +18 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +101 -19
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +19 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/memory.d.ts +12 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +94 -25
- package/dist/model.d.ts +14 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +12 -1
- package/dist/observation.d.ts +7 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +29 -12
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/outcome.d.ts +38 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +45 -1
- package/dist/overlay.d.ts +16 -3
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +118 -77
- package/dist/query.d.ts +60 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +41 -38
- package/dist/reach.d.ts +8 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +63 -24
- package/dist/react.d.ts +31 -18
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +15 -11
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +428 -94
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +98 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +197 -0
- package/dist/refresh.d.ts +13 -3
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +49 -38
- package/dist/replica.d.ts +13 -6
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +143 -70
- package/dist/room-draft.d.ts +38 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +134 -25
- package/dist/rows.d.ts +10 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +19 -14
- package/dist/save.d.ts +32 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +392 -45
- package/dist/search.d.ts +52 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +192 -47
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +368 -0
- package/dist/status.d.ts +6 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +89 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +573 -200
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/windows.d.ts +19 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +83 -0
- package/dist/wire.d.ts +149 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +63 -31
- package/package.json +26 -14
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
package/dist/save.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { definitionOf } from './model.js';
|
|
2
2
|
import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
-
import { immutable, } from './change.js';
|
|
4
|
-
import { governing,
|
|
3
|
+
import { immutable, queuedFile, departure, } from './change.js';
|
|
4
|
+
import { available, changedCleanly, collectionOf, documentsOf, governing, referring, validateCollection, } from './collection.js';
|
|
5
5
|
import { changeBatchIdentity } from './identity.js';
|
|
6
6
|
import { overlay } from './overlay.js';
|
|
7
7
|
import { covered, normalize } from './paths.js';
|
|
8
|
-
function refuse(
|
|
8
|
+
function refuse(code, file, location) {
|
|
9
9
|
return {
|
|
10
10
|
kind: 'refused',
|
|
11
11
|
refusal: {
|
|
12
|
-
|
|
12
|
+
code,
|
|
13
|
+
retryable: false,
|
|
13
14
|
...(file === undefined ? {} : { file }),
|
|
14
15
|
...(location === undefined ? {} : { location }),
|
|
15
16
|
},
|
|
@@ -90,7 +91,8 @@ function relinks(path, moved, before, moves) {
|
|
|
90
91
|
return {
|
|
91
92
|
kind: 'refused',
|
|
92
93
|
refusal: {
|
|
93
|
-
|
|
94
|
+
code: applied.refusal.rule,
|
|
95
|
+
retryable: false,
|
|
94
96
|
file: path,
|
|
95
97
|
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
96
98
|
},
|
|
@@ -120,7 +122,10 @@ function rewrites(changes, visible) {
|
|
|
120
122
|
for (const change of changes) {
|
|
121
123
|
if (change.kind === 'move')
|
|
122
124
|
moves.set(change.path, change.to);
|
|
123
|
-
if (change.kind === 'remove' ||
|
|
125
|
+
if (change.kind === 'remove' ||
|
|
126
|
+
change.kind === 'replace' ||
|
|
127
|
+
change.kind === 'create' ||
|
|
128
|
+
change.kind === 'binary') {
|
|
124
129
|
settled.add(change.path);
|
|
125
130
|
}
|
|
126
131
|
if (change.kind === 'field')
|
|
@@ -130,7 +135,7 @@ function rewrites(changes, visible) {
|
|
|
130
135
|
return { kind: 'changes', changes };
|
|
131
136
|
const merged = [...changes];
|
|
132
137
|
const added = [];
|
|
133
|
-
for (const [path, document] of
|
|
138
|
+
for (const [path, document] of documentsOf(visible.collection)) {
|
|
134
139
|
if (settled.has(path))
|
|
135
140
|
continue;
|
|
136
141
|
const moved = moves.get(path) ?? path;
|
|
@@ -172,7 +177,7 @@ export function providerRewritten(batch, visible) {
|
|
|
172
177
|
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
173
178
|
};
|
|
174
179
|
}
|
|
175
|
-
function incoming(collection, removed) {
|
|
180
|
+
function incoming(collection, removed, candidates) {
|
|
176
181
|
const found = new Map();
|
|
177
182
|
const note = (target, file) => {
|
|
178
183
|
if (!removed.has(target))
|
|
@@ -181,7 +186,13 @@ function incoming(collection, removed) {
|
|
|
181
186
|
referrers.add(file);
|
|
182
187
|
found.set(target, referrers);
|
|
183
188
|
};
|
|
184
|
-
|
|
189
|
+
const read = candidates === null
|
|
190
|
+
? documentsOf(collection)
|
|
191
|
+
: [...new Set(candidates)].sort().flatMap((path) => {
|
|
192
|
+
const document = collection.document(path);
|
|
193
|
+
return document === undefined ? [] : [[path, document]];
|
|
194
|
+
});
|
|
195
|
+
for (const [path, document] of read) {
|
|
185
196
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
186
197
|
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
187
198
|
if (to.includes(ATTACHMENT))
|
|
@@ -200,18 +211,26 @@ function incoming(collection, removed) {
|
|
|
200
211
|
every.add(file);
|
|
201
212
|
return {
|
|
202
213
|
kind: 'refused',
|
|
203
|
-
refusal: {
|
|
214
|
+
refusal: {
|
|
215
|
+
code: 'referenced-missing-file',
|
|
216
|
+
retryable: false,
|
|
217
|
+
file: first,
|
|
218
|
+
referrers: [...every].sort(),
|
|
219
|
+
},
|
|
204
220
|
};
|
|
205
221
|
}
|
|
206
222
|
function affected(change) {
|
|
207
223
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
208
224
|
}
|
|
209
225
|
function guard(changes, input) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
const overridden = new Map();
|
|
227
|
+
const existing = {
|
|
228
|
+
has: (path) => overridden.get(path) ??
|
|
229
|
+
(Object.hasOwn(input.visible.documents, path) || Object.hasOwn(input.visible.binaries, path)),
|
|
230
|
+
add: (path) => void overridden.set(path, true),
|
|
231
|
+
delete: (path) => void overridden.set(path, false),
|
|
232
|
+
};
|
|
233
|
+
for (const [index, change] of changes.entries()) {
|
|
215
234
|
for (const path of affected(change)) {
|
|
216
235
|
const normalized = normalize(path);
|
|
217
236
|
if (normalized === null || normalized === '')
|
|
@@ -220,7 +239,7 @@ function guard(changes, input) {
|
|
|
220
239
|
return refuse('provider-owned-path', normalized);
|
|
221
240
|
}
|
|
222
241
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
223
|
-
if (!covered(input.operations[operation], normalized))
|
|
242
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
224
243
|
return refuse(`${operation}-grant`, normalized);
|
|
225
244
|
}
|
|
226
245
|
if (change.kind === 'create') {
|
|
@@ -229,12 +248,26 @@ function guard(changes, input) {
|
|
|
229
248
|
existing.add(change.path);
|
|
230
249
|
continue;
|
|
231
250
|
}
|
|
251
|
+
if (change.kind === 'binary') {
|
|
252
|
+
if (input.visible.documents[change.path] !== undefined ||
|
|
253
|
+
(existing.has(change.path) &&
|
|
254
|
+
input.bases.some((base) => base.kind === 'absent' && base.path === change.path))) {
|
|
255
|
+
return refuse('path-collision', change.path);
|
|
256
|
+
}
|
|
257
|
+
if (!input.local &&
|
|
258
|
+
existing.has(change.path) &&
|
|
259
|
+
input.visible.revisions[departure(changes, index)] === undefined) {
|
|
260
|
+
return refuse('unknown-revision', change.path);
|
|
261
|
+
}
|
|
262
|
+
existing.add(change.path);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
232
265
|
if (change.kind === 'move') {
|
|
233
266
|
if (!existing.has(change.path))
|
|
234
267
|
return refuse('path-absent', change.path);
|
|
235
268
|
if (existing.has(change.to))
|
|
236
269
|
return refuse('path-collision', change.to);
|
|
237
|
-
if (
|
|
270
|
+
if (unrevised(input, change.path)) {
|
|
238
271
|
return refuse('unknown-revision', change.path);
|
|
239
272
|
}
|
|
240
273
|
existing.delete(change.path);
|
|
@@ -244,30 +277,53 @@ function guard(changes, input) {
|
|
|
244
277
|
if (!existing.has(change.path))
|
|
245
278
|
return refuse('path-absent', change.path);
|
|
246
279
|
if (change.kind === 'remove') {
|
|
247
|
-
if (
|
|
280
|
+
if (unrevised(input, change.path)) {
|
|
248
281
|
return refuse('unknown-revision', change.path);
|
|
249
282
|
}
|
|
250
283
|
existing.delete(change.path);
|
|
251
284
|
continue;
|
|
252
285
|
}
|
|
253
286
|
if (change.kind === 'replace') {
|
|
254
|
-
if (input
|
|
287
|
+
if (unrevised(input, change.path)) {
|
|
255
288
|
return refuse('unknown-revision', change.path);
|
|
256
289
|
}
|
|
257
290
|
continue;
|
|
258
291
|
}
|
|
259
|
-
if (input.visible.collection.
|
|
292
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
260
293
|
return refuse('path-absent', change.path);
|
|
261
294
|
}
|
|
262
295
|
}
|
|
263
296
|
return null;
|
|
264
297
|
}
|
|
265
|
-
function
|
|
266
|
-
return visible.revisions[path] === undefined
|
|
298
|
+
function unrevised(input, path) {
|
|
299
|
+
return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
|
|
300
|
+
}
|
|
301
|
+
export function queuedDocuments(queue) {
|
|
302
|
+
const paths = new Set();
|
|
303
|
+
for (const batch of queue) {
|
|
304
|
+
for (const change of batch.changes) {
|
|
305
|
+
if (change.kind === 'binary')
|
|
306
|
+
continue;
|
|
307
|
+
if (change.kind === 'remove')
|
|
308
|
+
paths.delete(change.path);
|
|
309
|
+
else if (change.kind !== 'move')
|
|
310
|
+
paths.add(change.path);
|
|
311
|
+
else if (paths.delete(change.path))
|
|
312
|
+
paths.add(change.to);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return paths;
|
|
316
|
+
}
|
|
317
|
+
function identity(precondition) {
|
|
318
|
+
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
319
|
+
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
267
320
|
}
|
|
268
321
|
function existence(visible, path) {
|
|
269
322
|
const revision = visible.revisions[path];
|
|
270
|
-
|
|
323
|
+
if (revision !== undefined)
|
|
324
|
+
return { kind: 'revision', path, revision };
|
|
325
|
+
const content = visible.documents[path];
|
|
326
|
+
return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
|
|
271
327
|
}
|
|
272
328
|
function sameTarget(precondition, change) {
|
|
273
329
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -279,11 +335,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
279
335
|
const preconditions = [];
|
|
280
336
|
const seen = new Set();
|
|
281
337
|
const add = (precondition) => {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
if (seen.has(identity))
|
|
338
|
+
const held = identity(precondition);
|
|
339
|
+
if (seen.has(held))
|
|
285
340
|
return;
|
|
286
|
-
seen.add(
|
|
341
|
+
seen.add(held);
|
|
287
342
|
preconditions.push(precondition);
|
|
288
343
|
};
|
|
289
344
|
for (const change of changes) {
|
|
@@ -293,7 +348,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
293
348
|
add(based);
|
|
294
349
|
continue;
|
|
295
350
|
}
|
|
296
|
-
const document = visible.collection.
|
|
351
|
+
const document = visible.collection.document(change.path);
|
|
297
352
|
if (change.kind === 'field') {
|
|
298
353
|
add({
|
|
299
354
|
kind: 'field',
|
|
@@ -318,6 +373,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
318
373
|
add({ kind: 'absent', path: change.path });
|
|
319
374
|
continue;
|
|
320
375
|
}
|
|
376
|
+
if (change.kind === 'binary') {
|
|
377
|
+
const revision = visible.revisions[change.path];
|
|
378
|
+
add(visible.binaries[change.path] === undefined || revision === undefined
|
|
379
|
+
? { kind: 'absent', path: change.path }
|
|
380
|
+
: { kind: 'revision', path: change.path, revision });
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
321
383
|
add(existence(visible, change.path));
|
|
322
384
|
if (change.kind === 'move')
|
|
323
385
|
add({ kind: 'absent', path: change.to });
|
|
@@ -327,10 +389,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
327
389
|
function depth(path) {
|
|
328
390
|
return path.split('/').length;
|
|
329
391
|
}
|
|
330
|
-
function exceeded(limits, changes, resulting) {
|
|
331
|
-
const written =
|
|
392
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
393
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
332
394
|
const created = changes
|
|
333
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
395
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
334
396
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
335
397
|
for (const limit of limits) {
|
|
336
398
|
const value = limit.value;
|
|
@@ -344,7 +406,7 @@ function exceeded(limits, changes, resulting) {
|
|
|
344
406
|
return refuse('provider-limit', undefined, limit.name);
|
|
345
407
|
}
|
|
346
408
|
if (limit.name === 'document-size') {
|
|
347
|
-
const large = written.find((path) => (resulting.documents[path] ?? '').
|
|
409
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
348
410
|
if (large !== undefined)
|
|
349
411
|
return refuse('provider-limit', large, limit.name);
|
|
350
412
|
}
|
|
@@ -389,7 +451,7 @@ function relativeChanges(input) {
|
|
|
389
451
|
return input.changes.map((change) => {
|
|
390
452
|
const definition = typed(change.path);
|
|
391
453
|
if (change.kind === 'field') {
|
|
392
|
-
const type = visible.collection.
|
|
454
|
+
const type = visible.collection.document(change.path)?.type;
|
|
393
455
|
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
394
456
|
return value === change.value ? change : { ...change, value };
|
|
395
457
|
}
|
|
@@ -412,13 +474,285 @@ function relativeChanges(input) {
|
|
|
412
474
|
return { ...change, fields, sections };
|
|
413
475
|
});
|
|
414
476
|
}
|
|
477
|
+
function unmodeledType(collection, composition) {
|
|
478
|
+
if (available(composition))
|
|
479
|
+
return false;
|
|
480
|
+
for (const [path, document] of documentsOf(collection)) {
|
|
481
|
+
if (document.type !== undefined &&
|
|
482
|
+
document.type !== 'any' &&
|
|
483
|
+
governing(composition, path).status === 'unavailable')
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
function unqueueable(file) {
|
|
489
|
+
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
490
|
+
}
|
|
491
|
+
function isPart(change) {
|
|
492
|
+
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
493
|
+
}
|
|
494
|
+
function touched(change) {
|
|
495
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
496
|
+
}
|
|
497
|
+
function partApplied(content, path, change) {
|
|
498
|
+
const parsed = parse(content, path);
|
|
499
|
+
if (!parsed.ok)
|
|
500
|
+
return null;
|
|
501
|
+
const document = parsed.document;
|
|
502
|
+
const applied = apply(document, change.kind === 'field'
|
|
503
|
+
? {
|
|
504
|
+
kind: 'field',
|
|
505
|
+
name: change.name,
|
|
506
|
+
value: change.value,
|
|
507
|
+
prior: document.fields[change.name],
|
|
508
|
+
}
|
|
509
|
+
: change.kind === 'section'
|
|
510
|
+
? {
|
|
511
|
+
kind: 'section',
|
|
512
|
+
name: change.name,
|
|
513
|
+
value: change.value,
|
|
514
|
+
prior: document.sections[change.name],
|
|
515
|
+
}
|
|
516
|
+
: { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
517
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
518
|
+
}
|
|
519
|
+
function folded(changes, change, appending) {
|
|
520
|
+
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
521
|
+
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
522
|
+
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
523
|
+
if (isPart(change)) {
|
|
524
|
+
const same = changes.findIndex((item) => item.kind === change.kind &&
|
|
525
|
+
item.path === change.path &&
|
|
526
|
+
(change.kind === 'preamble' || ('name' in item && item.name === change.name)));
|
|
527
|
+
if (same !== -1) {
|
|
528
|
+
changes[same] = change;
|
|
529
|
+
return 'folded';
|
|
530
|
+
}
|
|
531
|
+
if (held === undefined) {
|
|
532
|
+
if (!appending)
|
|
533
|
+
return 'skipped';
|
|
534
|
+
if (occupied)
|
|
535
|
+
return 'refused';
|
|
536
|
+
changes.push(change);
|
|
537
|
+
return 'appended';
|
|
538
|
+
}
|
|
539
|
+
if ('content' in held) {
|
|
540
|
+
const content = partApplied(held.content, held.path, change);
|
|
541
|
+
if (content === null)
|
|
542
|
+
return 'refused';
|
|
543
|
+
changes[whole] = { ...held, content };
|
|
544
|
+
return 'folded';
|
|
545
|
+
}
|
|
546
|
+
if (change.kind === 'preamble')
|
|
547
|
+
return 'refused';
|
|
548
|
+
changes[whole] =
|
|
549
|
+
change.kind === 'field'
|
|
550
|
+
? { ...held, fields: { ...held.fields, [change.name]: change.value } }
|
|
551
|
+
: { ...held, sections: { ...held.sections, [change.name]: change.value } };
|
|
552
|
+
return 'folded';
|
|
553
|
+
}
|
|
554
|
+
if (change.kind === 'replace') {
|
|
555
|
+
if (held !== undefined) {
|
|
556
|
+
if (!('content' in held))
|
|
557
|
+
return 'refused';
|
|
558
|
+
changes[whole] = { ...held, content: change.content };
|
|
559
|
+
return 'folded';
|
|
560
|
+
}
|
|
561
|
+
const parts = changes.filter((item) => isPart(item) && item.path === change.path);
|
|
562
|
+
if (parts.length === 0 && !appending)
|
|
563
|
+
return 'skipped';
|
|
564
|
+
if (occupied)
|
|
565
|
+
return 'refused';
|
|
566
|
+
changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
|
|
567
|
+
return 'appended';
|
|
568
|
+
}
|
|
569
|
+
if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
|
|
570
|
+
return 'refused';
|
|
571
|
+
changes.push(change);
|
|
572
|
+
return 'appended';
|
|
573
|
+
}
|
|
574
|
+
function relinked(batch, move, before, after, appending) {
|
|
575
|
+
const derived = rewrites([move], after);
|
|
576
|
+
if (derived.kind === 'refused')
|
|
577
|
+
return derived;
|
|
578
|
+
const changes = [...batch.changes];
|
|
579
|
+
const appended = [];
|
|
580
|
+
let changed = false;
|
|
581
|
+
for (const change of derived.changes) {
|
|
582
|
+
if (change === move)
|
|
583
|
+
continue;
|
|
584
|
+
const outcome = folded(changes, change, appending);
|
|
585
|
+
if (outcome === 'refused')
|
|
586
|
+
return unqueueable(change.path);
|
|
587
|
+
if (outcome === 'appended')
|
|
588
|
+
appended.push(change);
|
|
589
|
+
if (outcome !== 'skipped')
|
|
590
|
+
changed = true;
|
|
591
|
+
}
|
|
592
|
+
if (!changed)
|
|
593
|
+
return { kind: 'relinked', batch };
|
|
594
|
+
const held = new Set(batch.preconditions.map(identity));
|
|
595
|
+
return {
|
|
596
|
+
kind: 'relinked',
|
|
597
|
+
batch: immutable({
|
|
598
|
+
...batch,
|
|
599
|
+
id: changeBatchIdentity(),
|
|
600
|
+
changes,
|
|
601
|
+
preconditions: [
|
|
602
|
+
...batch.preconditions,
|
|
603
|
+
...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
|
|
604
|
+
],
|
|
605
|
+
}),
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function leading(changes, at) {
|
|
609
|
+
const from = departure(changes, at);
|
|
610
|
+
return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
|
|
611
|
+
}
|
|
612
|
+
function created(batch, at) {
|
|
613
|
+
const file = batch.changes[at];
|
|
614
|
+
return (leading(batch.changes, at).size === 0 &&
|
|
615
|
+
batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
|
|
616
|
+
}
|
|
617
|
+
function withdrawnFile(batch, change, at) {
|
|
618
|
+
const file = batch.changes[at];
|
|
619
|
+
const from = departure(batch.changes, at);
|
|
620
|
+
const moves = leading(batch.changes, at);
|
|
621
|
+
const fresh = created(batch, at);
|
|
622
|
+
const vacated = new Set([...moves].map((move) => move.to));
|
|
623
|
+
if (fresh)
|
|
624
|
+
vacated.add(file.path);
|
|
625
|
+
const replaced = fresh
|
|
626
|
+
? change.kind === 'remove'
|
|
627
|
+
? []
|
|
628
|
+
: [{ ...file, path: change.to }]
|
|
629
|
+
: change.kind === 'remove'
|
|
630
|
+
? [{ kind: 'remove', path: from }]
|
|
631
|
+
: change.to === from
|
|
632
|
+
? [{ ...file, path: from }]
|
|
633
|
+
: [
|
|
634
|
+
{ kind: 'move', path: from, to: change.to },
|
|
635
|
+
{ ...file, path: change.to },
|
|
636
|
+
];
|
|
637
|
+
const added = change.kind === 'move' && (fresh || change.to !== from)
|
|
638
|
+
? [{ kind: 'absent', path: change.to }]
|
|
639
|
+
: [];
|
|
640
|
+
const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
|
|
641
|
+
if (changes.length === 0)
|
|
642
|
+
return null;
|
|
643
|
+
const preconditions = [
|
|
644
|
+
...added,
|
|
645
|
+
...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
|
|
646
|
+
];
|
|
647
|
+
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
648
|
+
}
|
|
649
|
+
export function requeued(queue, changes, visible, changeable, state) {
|
|
650
|
+
const next = [...queue];
|
|
651
|
+
const origins = new Map();
|
|
652
|
+
const withdrawn = new Map();
|
|
653
|
+
const owners = new Set();
|
|
654
|
+
const queued = new Set();
|
|
655
|
+
const kept = [];
|
|
656
|
+
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
657
|
+
const open = (batch) => origins.has(batch) || changeable(batch);
|
|
658
|
+
for (const change of changes) {
|
|
659
|
+
const held = visible.binaries[change.path];
|
|
660
|
+
if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
|
|
661
|
+
kept.push(change);
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
let index = next.length - 1;
|
|
665
|
+
while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
|
|
666
|
+
index -= 1;
|
|
667
|
+
const owner = next[index];
|
|
668
|
+
if (owner === undefined || !open(owner))
|
|
669
|
+
return unqueueable(change.path);
|
|
670
|
+
const following = next.slice(index);
|
|
671
|
+
const rebuilt = [];
|
|
672
|
+
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
673
|
+
for (const [offset, batch] of following.entries()) {
|
|
674
|
+
let current = batch;
|
|
675
|
+
if (change.kind === 'move') {
|
|
676
|
+
const after = state(next.slice(0, index + offset + 1));
|
|
677
|
+
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
678
|
+
const relinking = relinked(batch, change, before, after, appending);
|
|
679
|
+
if (relinking.kind === 'refused')
|
|
680
|
+
return relinking;
|
|
681
|
+
current = relinking.batch;
|
|
682
|
+
before = after;
|
|
683
|
+
}
|
|
684
|
+
if (current !== batch && !open(batch))
|
|
685
|
+
return unqueueable(change.path);
|
|
686
|
+
rebuilt.push(offset === 0
|
|
687
|
+
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
688
|
+
: current);
|
|
689
|
+
}
|
|
690
|
+
for (const [offset, batch] of following.entries()) {
|
|
691
|
+
const replaced = rebuilt[offset];
|
|
692
|
+
if (replaced === batch)
|
|
693
|
+
continue;
|
|
694
|
+
const origin = originOf(batch);
|
|
695
|
+
withdrawn.set(origin, replaced);
|
|
696
|
+
if (replaced !== null)
|
|
697
|
+
origins.set(replaced, origin);
|
|
698
|
+
}
|
|
699
|
+
owners.add(originOf(owner));
|
|
700
|
+
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
701
|
+
queued.add(change.path);
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
kind: 'requeued',
|
|
705
|
+
queue: withdrawn.size === 0 ? queue : next,
|
|
706
|
+
changes: kept,
|
|
707
|
+
queued,
|
|
708
|
+
withdrawn,
|
|
709
|
+
owners,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function covering(precondition) {
|
|
713
|
+
return precondition.kind === 'field' ||
|
|
714
|
+
precondition.kind === 'section' ||
|
|
715
|
+
precondition.kind === 'preamble'
|
|
716
|
+
? identity(precondition)
|
|
717
|
+
: `path ${precondition.path}`;
|
|
718
|
+
}
|
|
719
|
+
export function joined(queue, owner, extra) {
|
|
720
|
+
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
721
|
+
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
722
|
+
if (clash !== undefined)
|
|
723
|
+
return unqueueable(clash);
|
|
724
|
+
const changes = [...owner.changes];
|
|
725
|
+
for (const change of extra.changes) {
|
|
726
|
+
if (folded(changes, change, true) === 'refused')
|
|
727
|
+
return unqueueable(change.path);
|
|
728
|
+
}
|
|
729
|
+
const held = new Set(owner.preconditions.map(covering));
|
|
730
|
+
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
731
|
+
const preconditions = [
|
|
732
|
+
...owner.preconditions,
|
|
733
|
+
...extra.preconditions.filter((precondition) => {
|
|
734
|
+
const key = covering(precondition);
|
|
735
|
+
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
736
|
+
}),
|
|
737
|
+
];
|
|
738
|
+
const batch = immutable({
|
|
739
|
+
...owner,
|
|
740
|
+
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
741
|
+
id: changeBatchIdentity(),
|
|
742
|
+
changes,
|
|
743
|
+
preconditions,
|
|
744
|
+
});
|
|
745
|
+
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
746
|
+
}
|
|
747
|
+
export function limitedIn(queue, batch, limits, state) {
|
|
748
|
+
const at = queue.indexOf(batch);
|
|
749
|
+
const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
|
|
750
|
+
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
751
|
+
}
|
|
415
752
|
export function buildBatch(input) {
|
|
416
753
|
const changes = relativeChanges(input);
|
|
417
754
|
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
418
|
-
if (structural !== undefined &&
|
|
419
|
-
Object.entries(input.visible.collection.documents).some(([path, document]) => document.type !== undefined &&
|
|
420
|
-
document.type !== 'any' &&
|
|
421
|
-
governing(input.composition, path).status === 'unavailable'))
|
|
755
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
422
756
|
return refuse('model-unavailable', structural.path);
|
|
423
757
|
if (changes.length === 0)
|
|
424
758
|
return refuse('no-change');
|
|
@@ -433,9 +767,16 @@ export function buildBatch(input) {
|
|
|
433
767
|
const resulting = overlay(input.visible, [
|
|
434
768
|
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
435
769
|
]);
|
|
770
|
+
const clean = input.clean !== undefined &&
|
|
771
|
+
input.clean.text === input.visible.documents &&
|
|
772
|
+
input.clean.binaries === input.visible.binaries &&
|
|
773
|
+
available(input.composition)
|
|
774
|
+
? input.clean
|
|
775
|
+
: null;
|
|
436
776
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
437
777
|
if (removed.size > 0) {
|
|
438
|
-
const
|
|
778
|
+
const referrers = clean === null ? null : referring(clean, input.composition, removed);
|
|
779
|
+
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, referrers === null ? null : [...referrers, ...predicted.changes.flatMap(affected)]);
|
|
439
780
|
if (referenced !== null)
|
|
440
781
|
return referenced;
|
|
441
782
|
}
|
|
@@ -445,27 +786,33 @@ export function buildBatch(input) {
|
|
|
445
786
|
const parsed = parse(content);
|
|
446
787
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
447
788
|
};
|
|
448
|
-
const
|
|
789
|
+
const touched = [...new Set(predicted.changes.flatMap(affected))];
|
|
790
|
+
const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
|
|
449
791
|
const needsModel = typedPaths.length > 0;
|
|
450
792
|
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
451
793
|
if (withoutModel !== undefined)
|
|
452
794
|
return refuse('model-unavailable', withoutModel);
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
795
|
+
const verdict = clean !== null &&
|
|
796
|
+
changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
|
|
797
|
+
? { verdict: 'accept' }
|
|
798
|
+
: validateCollection(needsModel
|
|
799
|
+
? resulting.documents
|
|
800
|
+
: Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
|
|
801
|
+
? []
|
|
802
|
+
: [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
|
|
457
803
|
if (verdict.verdict === 'refuse') {
|
|
458
804
|
return {
|
|
459
805
|
kind: 'refused',
|
|
460
806
|
refusal: {
|
|
461
|
-
|
|
807
|
+
code: verdict.rule,
|
|
808
|
+
retryable: false,
|
|
462
809
|
file: verdict.file,
|
|
463
810
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
464
811
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
465
812
|
},
|
|
466
813
|
};
|
|
467
814
|
}
|
|
468
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
815
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
469
816
|
if (limited !== null)
|
|
470
817
|
return limited;
|
|
471
818
|
const validated = typedPaths
|