@agentshouse/mdruntime 0.1.0-cfbf80d8d4644 → 0.1.0-cfcbb238a5fa0
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 -203
- 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 +83 -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/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 +1788 -400
- 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 +87 -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 +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 +40 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +60 -21
- 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 +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 +394 -129
- 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 +9 -6
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +125 -68
- 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 +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +89 -49
- package/dist/save.d.ts +31 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +376 -47
- 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 +75 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +539 -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))
|
|
@@ -190,8 +201,6 @@ function incoming(collection, removed) {
|
|
|
190
201
|
if (named !== null)
|
|
191
202
|
note(named, path);
|
|
192
203
|
}
|
|
193
|
-
for (const link of links(document, path))
|
|
194
|
-
note(link.target, path);
|
|
195
204
|
}
|
|
196
205
|
const [first] = [...found.keys()].sort();
|
|
197
206
|
if (first === undefined)
|
|
@@ -202,18 +211,26 @@ function incoming(collection, removed) {
|
|
|
202
211
|
every.add(file);
|
|
203
212
|
return {
|
|
204
213
|
kind: 'refused',
|
|
205
|
-
refusal: {
|
|
214
|
+
refusal: {
|
|
215
|
+
code: 'referenced-missing-file',
|
|
216
|
+
retryable: false,
|
|
217
|
+
file: first,
|
|
218
|
+
referrers: [...every].sort(),
|
|
219
|
+
},
|
|
206
220
|
};
|
|
207
221
|
}
|
|
208
222
|
function affected(change) {
|
|
209
223
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
210
224
|
}
|
|
211
225
|
function guard(changes, input) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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()) {
|
|
217
234
|
for (const path of affected(change)) {
|
|
218
235
|
const normalized = normalize(path);
|
|
219
236
|
if (normalized === null || normalized === '')
|
|
@@ -222,7 +239,7 @@ function guard(changes, input) {
|
|
|
222
239
|
return refuse('provider-owned-path', normalized);
|
|
223
240
|
}
|
|
224
241
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
225
|
-
if (!covered(input.operations[operation], normalized))
|
|
242
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
226
243
|
return refuse(`${operation}-grant`, normalized);
|
|
227
244
|
}
|
|
228
245
|
if (change.kind === 'create') {
|
|
@@ -231,12 +248,26 @@ function guard(changes, input) {
|
|
|
231
248
|
existing.add(change.path);
|
|
232
249
|
continue;
|
|
233
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
|
+
}
|
|
234
265
|
if (change.kind === 'move') {
|
|
235
266
|
if (!existing.has(change.path))
|
|
236
267
|
return refuse('path-absent', change.path);
|
|
237
268
|
if (existing.has(change.to))
|
|
238
269
|
return refuse('path-collision', change.to);
|
|
239
|
-
if (
|
|
270
|
+
if (unrevised(input, change.path)) {
|
|
240
271
|
return refuse('unknown-revision', change.path);
|
|
241
272
|
}
|
|
242
273
|
existing.delete(change.path);
|
|
@@ -246,30 +277,37 @@ function guard(changes, input) {
|
|
|
246
277
|
if (!existing.has(change.path))
|
|
247
278
|
return refuse('path-absent', change.path);
|
|
248
279
|
if (change.kind === 'remove') {
|
|
249
|
-
if (
|
|
280
|
+
if (unrevised(input, change.path)) {
|
|
250
281
|
return refuse('unknown-revision', change.path);
|
|
251
282
|
}
|
|
252
283
|
existing.delete(change.path);
|
|
253
284
|
continue;
|
|
254
285
|
}
|
|
255
286
|
if (change.kind === 'replace') {
|
|
256
|
-
if (input.visible.revisions[change.path] === undefined) {
|
|
287
|
+
if (!input.local && input.visible.revisions[change.path] === undefined) {
|
|
257
288
|
return refuse('unknown-revision', change.path);
|
|
258
289
|
}
|
|
259
290
|
continue;
|
|
260
291
|
}
|
|
261
|
-
if (input.visible.collection.
|
|
292
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
262
293
|
return refuse('path-absent', change.path);
|
|
263
294
|
}
|
|
264
295
|
}
|
|
265
296
|
return null;
|
|
266
297
|
}
|
|
267
|
-
function
|
|
268
|
-
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}`;
|
|
269
304
|
}
|
|
270
305
|
function existence(visible, path) {
|
|
271
306
|
const revision = visible.revisions[path];
|
|
272
|
-
|
|
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 };
|
|
273
311
|
}
|
|
274
312
|
function sameTarget(precondition, change) {
|
|
275
313
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -281,11 +319,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
281
319
|
const preconditions = [];
|
|
282
320
|
const seen = new Set();
|
|
283
321
|
const add = (precondition) => {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
if (seen.has(identity))
|
|
322
|
+
const held = identity(precondition);
|
|
323
|
+
if (seen.has(held))
|
|
287
324
|
return;
|
|
288
|
-
seen.add(
|
|
325
|
+
seen.add(held);
|
|
289
326
|
preconditions.push(precondition);
|
|
290
327
|
};
|
|
291
328
|
for (const change of changes) {
|
|
@@ -295,7 +332,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
295
332
|
add(based);
|
|
296
333
|
continue;
|
|
297
334
|
}
|
|
298
|
-
const document = visible.collection.
|
|
335
|
+
const document = visible.collection.document(change.path);
|
|
299
336
|
if (change.kind === 'field') {
|
|
300
337
|
add({
|
|
301
338
|
kind: 'field',
|
|
@@ -320,6 +357,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
320
357
|
add({ kind: 'absent', path: change.path });
|
|
321
358
|
continue;
|
|
322
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
|
+
}
|
|
323
367
|
add(existence(visible, change.path));
|
|
324
368
|
if (change.kind === 'move')
|
|
325
369
|
add({ kind: 'absent', path: change.to });
|
|
@@ -329,10 +373,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
329
373
|
function depth(path) {
|
|
330
374
|
return path.split('/').length;
|
|
331
375
|
}
|
|
332
|
-
function exceeded(limits, changes, resulting) {
|
|
333
|
-
const written =
|
|
376
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
377
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
334
378
|
const created = changes
|
|
335
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
379
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
336
380
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
337
381
|
for (const limit of limits) {
|
|
338
382
|
const value = limit.value;
|
|
@@ -346,7 +390,7 @@ function exceeded(limits, changes, resulting) {
|
|
|
346
390
|
return refuse('provider-limit', undefined, limit.name);
|
|
347
391
|
}
|
|
348
392
|
if (limit.name === 'document-size') {
|
|
349
|
-
const large = written.find((path) => (resulting.documents[path] ?? '').
|
|
393
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
350
394
|
if (large !== undefined)
|
|
351
395
|
return refuse('provider-limit', large, limit.name);
|
|
352
396
|
}
|
|
@@ -391,7 +435,7 @@ function relativeChanges(input) {
|
|
|
391
435
|
return input.changes.map((change) => {
|
|
392
436
|
const definition = typed(change.path);
|
|
393
437
|
if (change.kind === 'field') {
|
|
394
|
-
const type = visible.collection.
|
|
438
|
+
const type = visible.collection.document(change.path)?.type;
|
|
395
439
|
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
396
440
|
return value === change.value ? change : { ...change, value };
|
|
397
441
|
}
|
|
@@ -414,13 +458,285 @@ function relativeChanges(input) {
|
|
|
414
458
|
return { ...change, fields, sections };
|
|
415
459
|
});
|
|
416
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
|
+
}
|
|
417
736
|
export function buildBatch(input) {
|
|
418
737
|
const changes = relativeChanges(input);
|
|
419
738
|
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
420
|
-
if (structural !== undefined &&
|
|
421
|
-
Object.entries(input.visible.collection.documents).some(([path, document]) => document.type !== undefined &&
|
|
422
|
-
document.type !== 'any' &&
|
|
423
|
-
governing(input.composition, path).status === 'unavailable'))
|
|
739
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
424
740
|
return refuse('model-unavailable', structural.path);
|
|
425
741
|
if (changes.length === 0)
|
|
426
742
|
return refuse('no-change');
|
|
@@ -435,9 +751,16 @@ export function buildBatch(input) {
|
|
|
435
751
|
const resulting = overlay(input.visible, [
|
|
436
752
|
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
437
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;
|
|
438
760
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
439
761
|
if (removed.size > 0) {
|
|
440
|
-
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)]);
|
|
441
764
|
if (referenced !== null)
|
|
442
765
|
return referenced;
|
|
443
766
|
}
|
|
@@ -447,27 +770,33 @@ export function buildBatch(input) {
|
|
|
447
770
|
const parsed = parse(content);
|
|
448
771
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
449
772
|
};
|
|
450
|
-
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]));
|
|
451
775
|
const needsModel = typedPaths.length > 0;
|
|
452
776
|
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
453
777
|
if (withoutModel !== undefined)
|
|
454
778
|
return refuse('model-unavailable', withoutModel);
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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);
|
|
459
787
|
if (verdict.verdict === 'refuse') {
|
|
460
788
|
return {
|
|
461
789
|
kind: 'refused',
|
|
462
790
|
refusal: {
|
|
463
|
-
|
|
791
|
+
code: verdict.rule,
|
|
792
|
+
retryable: false,
|
|
464
793
|
file: verdict.file,
|
|
465
794
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
466
795
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
467
796
|
},
|
|
468
797
|
};
|
|
469
798
|
}
|
|
470
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
799
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
471
800
|
if (limited !== null)
|
|
472
801
|
return limited;
|
|
473
802
|
const validated = typedPaths
|