@agentshouse/mdruntime 0.1.0-ca1eff92bbe73 → 0.1.0-ca68e8b4bbebc
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 +269 -214
- package/README.md +13 -3
- 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 +81 -20
- package/dist/adapter.d.ts.map +1 -1
- 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/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 +63 -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 +629 -0
- package/dist/chat/question.d.ts +9 -0
- package/dist/chat/question.d.ts.map +1 -0
- package/dist/chat/question.js +80 -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 +25 -12
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +75 -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 +44 -15
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1832 -395
- package/dist/conversation.d.ts +26 -10
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +414 -148
- 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 +97 -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 +11 -4
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +83 -27
- package/dist/model.d.ts +7 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +10 -1
- package/dist/observation.d.ts +5 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +22 -11
- 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 +32 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +42 -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 +39 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +13 -33
- package/dist/reach.d.ts +8 -3
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +60 -24
- package/dist/react.d.ts +30 -18
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +14 -10
- package/dist/read-side.d.ts +13 -6
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +294 -85
- 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 +8 -5
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +124 -67
- 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 +9 -13
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +14 -14
- package/dist/save.d.ts +31 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +376 -45
- package/dist/search.d.ts +39 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +132 -46
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +61 -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 +83 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +555 -200
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +45 -0
- package/dist/wire.d.ts +141 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +61 -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,37 @@ 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.visible.revisions[change.path] === undefined) {
|
|
287
|
+
if (!input.local && input.visible.revisions[change.path] === undefined) {
|
|
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
|
+
function identity(precondition) {
|
|
302
|
+
const part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
|
|
303
|
+
return `${precondition.kind} ${precondition.path} ${part}`;
|
|
267
304
|
}
|
|
268
305
|
function existence(visible, path) {
|
|
269
306
|
const revision = visible.revisions[path];
|
|
270
|
-
|
|
307
|
+
if (revision !== undefined)
|
|
308
|
+
return { kind: 'revision', path, revision };
|
|
309
|
+
const content = visible.documents[path];
|
|
310
|
+
return content === undefined ? { kind: 'present', path } : { kind: 'content', path, content };
|
|
271
311
|
}
|
|
272
312
|
function sameTarget(precondition, change) {
|
|
273
313
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -279,11 +319,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
279
319
|
const preconditions = [];
|
|
280
320
|
const seen = new Set();
|
|
281
321
|
const add = (precondition) => {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
if (seen.has(identity))
|
|
322
|
+
const held = identity(precondition);
|
|
323
|
+
if (seen.has(held))
|
|
285
324
|
return;
|
|
286
|
-
seen.add(
|
|
325
|
+
seen.add(held);
|
|
287
326
|
preconditions.push(precondition);
|
|
288
327
|
};
|
|
289
328
|
for (const change of changes) {
|
|
@@ -293,7 +332,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
293
332
|
add(based);
|
|
294
333
|
continue;
|
|
295
334
|
}
|
|
296
|
-
const document = visible.collection.
|
|
335
|
+
const document = visible.collection.document(change.path);
|
|
297
336
|
if (change.kind === 'field') {
|
|
298
337
|
add({
|
|
299
338
|
kind: 'field',
|
|
@@ -318,6 +357,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
318
357
|
add({ kind: 'absent', path: change.path });
|
|
319
358
|
continue;
|
|
320
359
|
}
|
|
360
|
+
if (change.kind === 'binary') {
|
|
361
|
+
const revision = visible.revisions[change.path];
|
|
362
|
+
add(visible.binaries[change.path] === undefined || revision === undefined
|
|
363
|
+
? { kind: 'absent', path: change.path }
|
|
364
|
+
: { kind: 'revision', path: change.path, revision });
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
321
367
|
add(existence(visible, change.path));
|
|
322
368
|
if (change.kind === 'move')
|
|
323
369
|
add({ kind: 'absent', path: change.to });
|
|
@@ -327,10 +373,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
327
373
|
function depth(path) {
|
|
328
374
|
return path.split('/').length;
|
|
329
375
|
}
|
|
330
|
-
function exceeded(limits, changes, resulting) {
|
|
331
|
-
const written =
|
|
376
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
377
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
332
378
|
const created = changes
|
|
333
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
379
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
334
380
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
335
381
|
for (const limit of limits) {
|
|
336
382
|
const value = limit.value;
|
|
@@ -344,7 +390,7 @@ function exceeded(limits, changes, resulting) {
|
|
|
344
390
|
return refuse('provider-limit', undefined, limit.name);
|
|
345
391
|
}
|
|
346
392
|
if (limit.name === 'document-size') {
|
|
347
|
-
const large = written.find((path) => (resulting.documents[path] ?? '').
|
|
393
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
348
394
|
if (large !== undefined)
|
|
349
395
|
return refuse('provider-limit', large, limit.name);
|
|
350
396
|
}
|
|
@@ -389,7 +435,7 @@ function relativeChanges(input) {
|
|
|
389
435
|
return input.changes.map((change) => {
|
|
390
436
|
const definition = typed(change.path);
|
|
391
437
|
if (change.kind === 'field') {
|
|
392
|
-
const type = visible.collection.
|
|
438
|
+
const type = visible.collection.document(change.path)?.type;
|
|
393
439
|
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
394
440
|
return value === change.value ? change : { ...change, value };
|
|
395
441
|
}
|
|
@@ -412,13 +458,285 @@ function relativeChanges(input) {
|
|
|
412
458
|
return { ...change, fields, sections };
|
|
413
459
|
});
|
|
414
460
|
}
|
|
461
|
+
function unmodeledType(collection, composition) {
|
|
462
|
+
if (available(composition))
|
|
463
|
+
return false;
|
|
464
|
+
for (const [path, document] of documentsOf(collection)) {
|
|
465
|
+
if (document.type !== undefined &&
|
|
466
|
+
document.type !== 'any' &&
|
|
467
|
+
governing(composition, path).status === 'unavailable')
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
function unqueueable(file) {
|
|
473
|
+
return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
|
|
474
|
+
}
|
|
475
|
+
function isPart(change) {
|
|
476
|
+
return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
|
|
477
|
+
}
|
|
478
|
+
function touched(change) {
|
|
479
|
+
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
480
|
+
}
|
|
481
|
+
function partApplied(content, path, change) {
|
|
482
|
+
const parsed = parse(content, path);
|
|
483
|
+
if (!parsed.ok)
|
|
484
|
+
return null;
|
|
485
|
+
const document = parsed.document;
|
|
486
|
+
const applied = apply(document, change.kind === 'field'
|
|
487
|
+
? {
|
|
488
|
+
kind: 'field',
|
|
489
|
+
name: change.name,
|
|
490
|
+
value: change.value,
|
|
491
|
+
prior: document.fields[change.name],
|
|
492
|
+
}
|
|
493
|
+
: change.kind === 'section'
|
|
494
|
+
? {
|
|
495
|
+
kind: 'section',
|
|
496
|
+
name: change.name,
|
|
497
|
+
value: change.value,
|
|
498
|
+
prior: document.sections[change.name],
|
|
499
|
+
}
|
|
500
|
+
: { kind: 'preamble', value: change.value, prior: document.preamble });
|
|
501
|
+
return applied.outcome === 'applied' ? serialize(applied.document) : null;
|
|
502
|
+
}
|
|
503
|
+
function folded(changes, change, appending) {
|
|
504
|
+
const held = changes.find((item) => item.path === change.path && (item.kind === 'create' || item.kind === 'replace'));
|
|
505
|
+
const whole = held === undefined ? -1 : changes.indexOf(held);
|
|
506
|
+
const occupied = changes.some((item) => !isPart(item) && item !== held && touched(item).includes(change.path));
|
|
507
|
+
if (isPart(change)) {
|
|
508
|
+
const same = changes.findIndex((item) => item.kind === change.kind &&
|
|
509
|
+
item.path === change.path &&
|
|
510
|
+
(change.kind === 'preamble' || ('name' in item && item.name === change.name)));
|
|
511
|
+
if (same !== -1) {
|
|
512
|
+
changes[same] = change;
|
|
513
|
+
return 'folded';
|
|
514
|
+
}
|
|
515
|
+
if (held === undefined) {
|
|
516
|
+
if (!appending)
|
|
517
|
+
return 'skipped';
|
|
518
|
+
if (occupied)
|
|
519
|
+
return 'refused';
|
|
520
|
+
changes.push(change);
|
|
521
|
+
return 'appended';
|
|
522
|
+
}
|
|
523
|
+
if ('content' in held) {
|
|
524
|
+
const content = partApplied(held.content, held.path, change);
|
|
525
|
+
if (content === null)
|
|
526
|
+
return 'refused';
|
|
527
|
+
changes[whole] = { ...held, content };
|
|
528
|
+
return 'folded';
|
|
529
|
+
}
|
|
530
|
+
if (change.kind === 'preamble')
|
|
531
|
+
return 'refused';
|
|
532
|
+
changes[whole] =
|
|
533
|
+
change.kind === 'field'
|
|
534
|
+
? { ...held, fields: { ...held.fields, [change.name]: change.value } }
|
|
535
|
+
: { ...held, sections: { ...held.sections, [change.name]: change.value } };
|
|
536
|
+
return 'folded';
|
|
537
|
+
}
|
|
538
|
+
if (change.kind === 'replace') {
|
|
539
|
+
if (held !== undefined) {
|
|
540
|
+
if (!('content' in held))
|
|
541
|
+
return 'refused';
|
|
542
|
+
changes[whole] = { ...held, content: change.content };
|
|
543
|
+
return 'folded';
|
|
544
|
+
}
|
|
545
|
+
const parts = changes.filter((item) => isPart(item) && item.path === change.path);
|
|
546
|
+
if (parts.length === 0 && !appending)
|
|
547
|
+
return 'skipped';
|
|
548
|
+
if (occupied)
|
|
549
|
+
return 'refused';
|
|
550
|
+
changes.splice(0, changes.length, ...changes.filter((item) => !parts.includes(item)), change);
|
|
551
|
+
return 'appended';
|
|
552
|
+
}
|
|
553
|
+
if (changes.some((item) => touched(change).some((path) => touched(item).includes(path))))
|
|
554
|
+
return 'refused';
|
|
555
|
+
changes.push(change);
|
|
556
|
+
return 'appended';
|
|
557
|
+
}
|
|
558
|
+
function relinked(batch, move, before, after, appending) {
|
|
559
|
+
const derived = rewrites([move], after);
|
|
560
|
+
if (derived.kind === 'refused')
|
|
561
|
+
return derived;
|
|
562
|
+
const changes = [...batch.changes];
|
|
563
|
+
const appended = [];
|
|
564
|
+
let changed = false;
|
|
565
|
+
for (const change of derived.changes) {
|
|
566
|
+
if (change === move)
|
|
567
|
+
continue;
|
|
568
|
+
const outcome = folded(changes, change, appending);
|
|
569
|
+
if (outcome === 'refused')
|
|
570
|
+
return unqueueable(change.path);
|
|
571
|
+
if (outcome === 'appended')
|
|
572
|
+
appended.push(change);
|
|
573
|
+
if (outcome !== 'skipped')
|
|
574
|
+
changed = true;
|
|
575
|
+
}
|
|
576
|
+
if (!changed)
|
|
577
|
+
return { kind: 'relinked', batch };
|
|
578
|
+
const held = new Set(batch.preconditions.map(identity));
|
|
579
|
+
return {
|
|
580
|
+
kind: 'relinked',
|
|
581
|
+
batch: immutable({
|
|
582
|
+
...batch,
|
|
583
|
+
id: changeBatchIdentity(),
|
|
584
|
+
changes,
|
|
585
|
+
preconditions: [
|
|
586
|
+
...batch.preconditions,
|
|
587
|
+
...preconditionsOf(appended, before, []).filter((precondition) => !held.has(identity(precondition))),
|
|
588
|
+
],
|
|
589
|
+
}),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function leading(changes, at) {
|
|
593
|
+
const from = departure(changes, at);
|
|
594
|
+
return new Set(changes.filter((change, index) => index < at && change.kind === 'move' && departure(changes, index) === from));
|
|
595
|
+
}
|
|
596
|
+
function created(batch, at) {
|
|
597
|
+
const file = batch.changes[at];
|
|
598
|
+
return (leading(batch.changes, at).size === 0 &&
|
|
599
|
+
batch.preconditions.some((precondition) => precondition.kind === 'absent' && precondition.path === file.path));
|
|
600
|
+
}
|
|
601
|
+
function withdrawnFile(batch, change, at) {
|
|
602
|
+
const file = batch.changes[at];
|
|
603
|
+
const from = departure(batch.changes, at);
|
|
604
|
+
const moves = leading(batch.changes, at);
|
|
605
|
+
const fresh = created(batch, at);
|
|
606
|
+
const vacated = new Set([...moves].map((move) => move.to));
|
|
607
|
+
if (fresh)
|
|
608
|
+
vacated.add(file.path);
|
|
609
|
+
const replaced = fresh
|
|
610
|
+
? change.kind === 'remove'
|
|
611
|
+
? []
|
|
612
|
+
: [{ ...file, path: change.to }]
|
|
613
|
+
: change.kind === 'remove'
|
|
614
|
+
? [{ kind: 'remove', path: from }]
|
|
615
|
+
: change.to === from
|
|
616
|
+
? [{ ...file, path: from }]
|
|
617
|
+
: [
|
|
618
|
+
{ kind: 'move', path: from, to: change.to },
|
|
619
|
+
{ ...file, path: change.to },
|
|
620
|
+
];
|
|
621
|
+
const added = change.kind === 'move' && (fresh || change.to !== from)
|
|
622
|
+
? [{ kind: 'absent', path: change.to }]
|
|
623
|
+
: [];
|
|
624
|
+
const changes = batch.changes.flatMap((item) => item === file ? replaced : item.kind === 'move' && moves.has(item) ? [] : [item]);
|
|
625
|
+
if (changes.length === 0)
|
|
626
|
+
return null;
|
|
627
|
+
const preconditions = [
|
|
628
|
+
...added,
|
|
629
|
+
...batch.preconditions.filter((precondition) => precondition.kind !== 'absent' || !vacated.has(precondition.path)),
|
|
630
|
+
];
|
|
631
|
+
return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
|
|
632
|
+
}
|
|
633
|
+
export function requeued(queue, changes, visible, changeable, state) {
|
|
634
|
+
const next = [...queue];
|
|
635
|
+
const origins = new Map();
|
|
636
|
+
const withdrawn = new Map();
|
|
637
|
+
const owners = new Set();
|
|
638
|
+
const queued = new Set();
|
|
639
|
+
const kept = [];
|
|
640
|
+
const originOf = (batch) => origins.get(batch) ?? batch.id;
|
|
641
|
+
const open = (batch) => origins.has(batch) || changeable(batch);
|
|
642
|
+
for (const change of changes) {
|
|
643
|
+
const held = visible.binaries[change.path];
|
|
644
|
+
if ((change.kind !== 'move' && change.kind !== 'remove') || held?.state !== 'pending') {
|
|
645
|
+
kept.push(change);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
let index = next.length - 1;
|
|
649
|
+
while (index >= 0 && queuedFile(next[index], change.path, held.localId) === -1)
|
|
650
|
+
index -= 1;
|
|
651
|
+
const owner = next[index];
|
|
652
|
+
if (owner === undefined || !open(owner))
|
|
653
|
+
return unqueueable(change.path);
|
|
654
|
+
const following = next.slice(index);
|
|
655
|
+
const rebuilt = [];
|
|
656
|
+
let before = change.kind === 'move' ? state(next.slice(0, index)) : visible;
|
|
657
|
+
for (const [offset, batch] of following.entries()) {
|
|
658
|
+
let current = batch;
|
|
659
|
+
if (change.kind === 'move') {
|
|
660
|
+
const after = state(next.slice(0, index + offset + 1));
|
|
661
|
+
const appending = offset === 0 && created(batch, queuedFile(batch, change.path, held.localId));
|
|
662
|
+
const relinking = relinked(batch, change, before, after, appending);
|
|
663
|
+
if (relinking.kind === 'refused')
|
|
664
|
+
return relinking;
|
|
665
|
+
current = relinking.batch;
|
|
666
|
+
before = after;
|
|
667
|
+
}
|
|
668
|
+
if (current !== batch && !open(batch))
|
|
669
|
+
return unqueueable(change.path);
|
|
670
|
+
rebuilt.push(offset === 0
|
|
671
|
+
? withdrawnFile(current, change, queuedFile(current, change.path, held.localId))
|
|
672
|
+
: current);
|
|
673
|
+
}
|
|
674
|
+
for (const [offset, batch] of following.entries()) {
|
|
675
|
+
const replaced = rebuilt[offset];
|
|
676
|
+
if (replaced === batch)
|
|
677
|
+
continue;
|
|
678
|
+
const origin = originOf(batch);
|
|
679
|
+
withdrawn.set(origin, replaced);
|
|
680
|
+
if (replaced !== null)
|
|
681
|
+
origins.set(replaced, origin);
|
|
682
|
+
}
|
|
683
|
+
owners.add(originOf(owner));
|
|
684
|
+
next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
|
|
685
|
+
queued.add(change.path);
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
kind: 'requeued',
|
|
689
|
+
queue: withdrawn.size === 0 ? queue : next,
|
|
690
|
+
changes: kept,
|
|
691
|
+
queued,
|
|
692
|
+
withdrawn,
|
|
693
|
+
owners,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function covering(precondition) {
|
|
697
|
+
return precondition.kind === 'field' ||
|
|
698
|
+
precondition.kind === 'section' ||
|
|
699
|
+
precondition.kind === 'preamble'
|
|
700
|
+
? identity(precondition)
|
|
701
|
+
: `path ${precondition.path}`;
|
|
702
|
+
}
|
|
703
|
+
export function joined(queue, owner, extra) {
|
|
704
|
+
const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
|
|
705
|
+
const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
|
|
706
|
+
if (clash !== undefined)
|
|
707
|
+
return unqueueable(clash);
|
|
708
|
+
const changes = [...owner.changes];
|
|
709
|
+
for (const change of extra.changes) {
|
|
710
|
+
if (folded(changes, change, true) === 'refused')
|
|
711
|
+
return unqueueable(change.path);
|
|
712
|
+
}
|
|
713
|
+
const held = new Set(owner.preconditions.map(covering));
|
|
714
|
+
const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
|
|
715
|
+
const preconditions = [
|
|
716
|
+
...owner.preconditions,
|
|
717
|
+
...extra.preconditions.filter((precondition) => {
|
|
718
|
+
const key = covering(precondition);
|
|
719
|
+
return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
|
|
720
|
+
}),
|
|
721
|
+
];
|
|
722
|
+
const batch = immutable({
|
|
723
|
+
...owner,
|
|
724
|
+
...(extra.validation === undefined ? {} : { validation: extra.validation }),
|
|
725
|
+
id: changeBatchIdentity(),
|
|
726
|
+
changes,
|
|
727
|
+
preconditions,
|
|
728
|
+
});
|
|
729
|
+
return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
|
|
730
|
+
}
|
|
731
|
+
export function limitedIn(queue, batch, limits, state) {
|
|
732
|
+
const at = queue.indexOf(batch);
|
|
733
|
+
const limited = exceeded(limits, batch.changes, providerRewritten(batch, state(queue.slice(0, at))).changes, state(queue.slice(0, at + 1)));
|
|
734
|
+
return limited?.kind === 'refused' ? limited.refusal : null;
|
|
735
|
+
}
|
|
415
736
|
export function buildBatch(input) {
|
|
416
737
|
const changes = relativeChanges(input);
|
|
417
738
|
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'))
|
|
739
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
422
740
|
return refuse('model-unavailable', structural.path);
|
|
423
741
|
if (changes.length === 0)
|
|
424
742
|
return refuse('no-change');
|
|
@@ -433,9 +751,16 @@ export function buildBatch(input) {
|
|
|
433
751
|
const resulting = overlay(input.visible, [
|
|
434
752
|
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
435
753
|
]);
|
|
754
|
+
const clean = input.clean !== undefined &&
|
|
755
|
+
input.clean.text === input.visible.documents &&
|
|
756
|
+
input.clean.binaries === input.visible.binaries &&
|
|
757
|
+
available(input.composition)
|
|
758
|
+
? input.clean
|
|
759
|
+
: null;
|
|
436
760
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
437
761
|
if (removed.size > 0) {
|
|
438
|
-
const
|
|
762
|
+
const referrers = clean === null ? null : referring(clean, input.composition, removed);
|
|
763
|
+
const referenced = incoming(collectionOf(resulting.documents, input.composition), removed, referrers === null ? null : [...referrers, ...predicted.changes.flatMap(affected)]);
|
|
439
764
|
if (referenced !== null)
|
|
440
765
|
return referenced;
|
|
441
766
|
}
|
|
@@ -445,27 +770,33 @@ export function buildBatch(input) {
|
|
|
445
770
|
const parsed = parse(content);
|
|
446
771
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
447
772
|
};
|
|
448
|
-
const
|
|
773
|
+
const touched = [...new Set(predicted.changes.flatMap(affected))];
|
|
774
|
+
const typedPaths = touched.filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
|
|
449
775
|
const needsModel = typedPaths.length > 0;
|
|
450
776
|
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
451
777
|
if (withoutModel !== undefined)
|
|
452
778
|
return refuse('model-unavailable', withoutModel);
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
779
|
+
const verdict = clean !== null &&
|
|
780
|
+
changedCleanly({ text: resulting.documents, binaries: resulting.binaries }, clean, input.composition)
|
|
781
|
+
? { verdict: 'accept' }
|
|
782
|
+
: validateCollection(needsModel
|
|
783
|
+
? resulting.documents
|
|
784
|
+
: Object.fromEntries(touched.flatMap((path) => resulting.documents[path] === undefined
|
|
785
|
+
? []
|
|
786
|
+
: [[path, resulting.documents[path]]])), Object.keys(resulting.binaries), input.composition);
|
|
457
787
|
if (verdict.verdict === 'refuse') {
|
|
458
788
|
return {
|
|
459
789
|
kind: 'refused',
|
|
460
790
|
refusal: {
|
|
461
|
-
|
|
791
|
+
code: verdict.rule,
|
|
792
|
+
retryable: false,
|
|
462
793
|
file: verdict.file,
|
|
463
794
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
464
795
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
465
796
|
},
|
|
466
797
|
};
|
|
467
798
|
}
|
|
468
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
799
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
469
800
|
if (limited !== null)
|
|
470
801
|
return limited;
|
|
471
802
|
const validated = typedPaths
|