@agentshouse/mdruntime 0.1.0-ce531b51ff088 → 0.1.0-ceb97106cc2fd
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 +273 -175
- package/README.md +21 -11
- 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 +129 -41
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +1 -9
- package/dist/attached-model.d.ts +11 -0
- package/dist/attached-model.d.ts.map +1 -0
- package/dist/attached-model.js +47 -0
- 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 +20 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +114 -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 +28 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +71 -26
- 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 +50 -23
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2074 -735
- package/dist/conversation.d.ts +41 -14
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +546 -214
- 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 +24 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +126 -45
- 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 +17 -7
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +159 -46
- package/dist/model.d.ts +17 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +16 -4
- package/dist/observation.d.ts +6 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +38 -17
- 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/paths.d.ts +0 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +0 -16
- package/dist/query.d.ts +46 -6
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +85 -37
- package/dist/reach.d.ts +45 -0
- package/dist/reach.d.ts.map +1 -0
- package/dist/reach.js +306 -0
- package/dist/react.d.ts +44 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +66 -0
- package/dist/read-side.d.ts +16 -5
- package/dist/read-side.d.ts.map +1 -1
- package/dist/read-side.js +408 -137
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -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 +12 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +51 -36
- package/dist/replica.d.ts +10 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +129 -73
- package/dist/room-draft.d.ts +41 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +139 -28
- package/dist/rows.d.ts +11 -14
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +87 -50
- package/dist/save.d.ts +32 -3
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +465 -92
- package/dist/search.d.ts +37 -8
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +116 -45
- 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 +76 -19
- 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 +162 -19
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +82 -61
- package/package.json +31 -7
- 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,35 +1,49 @@
|
|
|
1
1
|
import { definitionOf } from './model.js';
|
|
2
|
-
import { apply, links, parse, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
3
|
-
import { immutable, } from './change.js';
|
|
4
|
-
import { governing,
|
|
2
|
+
import { apply, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, serialize, target, } from '@agentshouse/mdmodel';
|
|
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
|
-
import { covered, normalize
|
|
8
|
-
function refuse(
|
|
7
|
+
import { covered, normalize } from './paths.js';
|
|
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
|
},
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
function repointed(document, definition, from, to, moves, settled) {
|
|
20
|
+
const parsed = parse(document.content, from);
|
|
21
|
+
if (!parsed.ok)
|
|
22
|
+
return [];
|
|
23
|
+
let next = parsed.document;
|
|
24
|
+
const names = new Set();
|
|
25
|
+
for (const { pointer, value } of definition === undefined
|
|
26
|
+
? []
|
|
27
|
+
: references(document, definition)) {
|
|
28
|
+
const [name = ''] = pointerSegments(pointer);
|
|
29
|
+
const named = target(from, value);
|
|
30
|
+
if (named === null || settled.has(`${from} ${name}`))
|
|
31
|
+
continue;
|
|
32
|
+
const destination = moves.get(named) ?? named;
|
|
33
|
+
if (target(to, value) === destination)
|
|
34
|
+
continue;
|
|
35
|
+
const applied = apply(next, {
|
|
36
|
+
kind: 'reference',
|
|
37
|
+
pointer,
|
|
38
|
+
value: retarget(value, relative(to, destination)),
|
|
39
|
+
prior: value,
|
|
40
|
+
});
|
|
41
|
+
if (applied.outcome !== 'applied')
|
|
42
|
+
continue;
|
|
43
|
+
next = applied.document;
|
|
44
|
+
names.add(name);
|
|
25
45
|
}
|
|
26
|
-
|
|
27
|
-
return undefined;
|
|
28
|
-
const named = target(from, value);
|
|
29
|
-
if (named === null)
|
|
30
|
-
return undefined;
|
|
31
|
-
const destination = moves.get(named) ?? named;
|
|
32
|
-
return target(to, value) === destination ? undefined : retarget(value, relative(to, destination));
|
|
46
|
+
return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
|
|
33
47
|
}
|
|
34
48
|
function edited(document, path, changes) {
|
|
35
49
|
let next = document;
|
|
@@ -77,7 +91,8 @@ function relinks(path, moved, before, moves) {
|
|
|
77
91
|
return {
|
|
78
92
|
kind: 'refused',
|
|
79
93
|
refusal: {
|
|
80
|
-
|
|
94
|
+
code: applied.refusal.rule,
|
|
95
|
+
retryable: false,
|
|
81
96
|
file: path,
|
|
82
97
|
...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
|
|
83
98
|
},
|
|
@@ -107,7 +122,10 @@ function rewrites(changes, visible) {
|
|
|
107
122
|
for (const change of changes) {
|
|
108
123
|
if (change.kind === 'move')
|
|
109
124
|
moves.set(change.path, change.to);
|
|
110
|
-
if (change.kind === 'remove' ||
|
|
125
|
+
if (change.kind === 'remove' ||
|
|
126
|
+
change.kind === 'replace' ||
|
|
127
|
+
change.kind === 'create' ||
|
|
128
|
+
change.kind === 'binary') {
|
|
111
129
|
settled.add(change.path);
|
|
112
130
|
}
|
|
113
131
|
if (change.kind === 'field')
|
|
@@ -117,19 +135,12 @@ function rewrites(changes, visible) {
|
|
|
117
135
|
return { kind: 'changes', changes };
|
|
118
136
|
const merged = [...changes];
|
|
119
137
|
const added = [];
|
|
120
|
-
for (const [path, document] of
|
|
138
|
+
for (const [path, document] of documentsOf(visible.collection)) {
|
|
121
139
|
if (settled.has(path))
|
|
122
140
|
continue;
|
|
123
141
|
const moved = moves.get(path) ?? path;
|
|
124
142
|
const definition = definitionOf(governing(visible.collection.composition, path), document.type);
|
|
125
|
-
|
|
126
|
-
if (!referenceField(spec) || settled.has(`${path} ${name}`))
|
|
127
|
-
continue;
|
|
128
|
-
const value = repointed(document.fields[name], path, moved, moves);
|
|
129
|
-
if (value === undefined)
|
|
130
|
-
continue;
|
|
131
|
-
added.push({ kind: 'field', path, name, value });
|
|
132
|
-
}
|
|
143
|
+
added.push(...repointed(document, definition, path, moved, moves, settled));
|
|
133
144
|
const relinked = relinks(path, moved, edited(document, path, merged), moves);
|
|
134
145
|
if (relinked.kind === 'refused')
|
|
135
146
|
return relinked;
|
|
@@ -166,19 +177,7 @@ export function providerRewritten(batch, visible) {
|
|
|
166
177
|
...anticipated(batch.changes, batch.preconditions, rewritten.changes, visible),
|
|
167
178
|
};
|
|
168
179
|
}
|
|
169
|
-
function
|
|
170
|
-
const values = Array.isArray(value) ? value : [value];
|
|
171
|
-
const resolved = [];
|
|
172
|
-
for (const item of values) {
|
|
173
|
-
if (typeof item !== 'string')
|
|
174
|
-
continue;
|
|
175
|
-
const named = target(from, item);
|
|
176
|
-
if (named !== null)
|
|
177
|
-
resolved.push(named);
|
|
178
|
-
}
|
|
179
|
-
return resolved;
|
|
180
|
-
}
|
|
181
|
-
function incoming(collection, removed) {
|
|
180
|
+
function incoming(collection, removed, candidates) {
|
|
182
181
|
const found = new Map();
|
|
183
182
|
const note = (target, file) => {
|
|
184
183
|
if (!removed.has(target))
|
|
@@ -187,16 +186,21 @@ function incoming(collection, removed) {
|
|
|
187
186
|
referrers.add(file);
|
|
188
187
|
found.set(target, referrers);
|
|
189
188
|
};
|
|
190
|
-
|
|
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) {
|
|
191
196
|
const definition = definitionOf(governing(collection.composition, path), document.type);
|
|
192
|
-
for (const
|
|
193
|
-
if (
|
|
197
|
+
for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
|
|
198
|
+
if (to.includes(ATTACHMENT))
|
|
194
199
|
continue;
|
|
195
|
-
|
|
200
|
+
const named = target(path, value);
|
|
201
|
+
if (named !== null)
|
|
196
202
|
note(named, path);
|
|
197
203
|
}
|
|
198
|
-
for (const link of links(document, path))
|
|
199
|
-
note(link.target, path);
|
|
200
204
|
}
|
|
201
205
|
const [first] = [...found.keys()].sort();
|
|
202
206
|
if (first === undefined)
|
|
@@ -207,18 +211,26 @@ function incoming(collection, removed) {
|
|
|
207
211
|
every.add(file);
|
|
208
212
|
return {
|
|
209
213
|
kind: 'refused',
|
|
210
|
-
refusal: {
|
|
214
|
+
refusal: {
|
|
215
|
+
code: 'referenced-missing-file',
|
|
216
|
+
retryable: false,
|
|
217
|
+
file: first,
|
|
218
|
+
referrers: [...every].sort(),
|
|
219
|
+
},
|
|
211
220
|
};
|
|
212
221
|
}
|
|
213
222
|
function affected(change) {
|
|
214
223
|
return change.kind === 'move' ? [change.path, change.to] : [change.path];
|
|
215
224
|
}
|
|
216
225
|
function guard(changes, input) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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()) {
|
|
222
234
|
for (const path of affected(change)) {
|
|
223
235
|
const normalized = normalize(path);
|
|
224
236
|
if (normalized === null || normalized === '')
|
|
@@ -227,7 +239,7 @@ function guard(changes, input) {
|
|
|
227
239
|
return refuse('provider-owned-path', normalized);
|
|
228
240
|
}
|
|
229
241
|
const operation = change.kind === 'remove' ? 'delete' : 'write';
|
|
230
|
-
if (!covered(input.operations[operation], normalized))
|
|
242
|
+
if (!input.local && !covered(input.operations[operation], normalized))
|
|
231
243
|
return refuse(`${operation}-grant`, normalized);
|
|
232
244
|
}
|
|
233
245
|
if (change.kind === 'create') {
|
|
@@ -236,12 +248,26 @@ function guard(changes, input) {
|
|
|
236
248
|
existing.add(change.path);
|
|
237
249
|
continue;
|
|
238
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
|
+
}
|
|
239
265
|
if (change.kind === 'move') {
|
|
240
266
|
if (!existing.has(change.path))
|
|
241
267
|
return refuse('path-absent', change.path);
|
|
242
268
|
if (existing.has(change.to))
|
|
243
269
|
return refuse('path-collision', change.to);
|
|
244
|
-
if (
|
|
270
|
+
if (unrevised(input, change.path)) {
|
|
245
271
|
return refuse('unknown-revision', change.path);
|
|
246
272
|
}
|
|
247
273
|
existing.delete(change.path);
|
|
@@ -251,30 +277,37 @@ function guard(changes, input) {
|
|
|
251
277
|
if (!existing.has(change.path))
|
|
252
278
|
return refuse('path-absent', change.path);
|
|
253
279
|
if (change.kind === 'remove') {
|
|
254
|
-
if (
|
|
280
|
+
if (unrevised(input, change.path)) {
|
|
255
281
|
return refuse('unknown-revision', change.path);
|
|
256
282
|
}
|
|
257
283
|
existing.delete(change.path);
|
|
258
284
|
continue;
|
|
259
285
|
}
|
|
260
286
|
if (change.kind === 'replace') {
|
|
261
|
-
if (input.visible.revisions[change.path] === undefined) {
|
|
287
|
+
if (!input.local && input.visible.revisions[change.path] === undefined) {
|
|
262
288
|
return refuse('unknown-revision', change.path);
|
|
263
289
|
}
|
|
264
290
|
continue;
|
|
265
291
|
}
|
|
266
|
-
if (input.visible.collection.
|
|
292
|
+
if (input.visible.collection.document(change.path) === undefined) {
|
|
267
293
|
return refuse('path-absent', change.path);
|
|
268
294
|
}
|
|
269
295
|
}
|
|
270
296
|
return null;
|
|
271
297
|
}
|
|
272
|
-
function
|
|
273
|
-
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}`;
|
|
274
304
|
}
|
|
275
305
|
function existence(visible, path) {
|
|
276
306
|
const revision = visible.revisions[path];
|
|
277
|
-
|
|
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 };
|
|
278
311
|
}
|
|
279
312
|
function sameTarget(precondition, change) {
|
|
280
313
|
if (precondition.kind !== change.kind || precondition.path !== change.path)
|
|
@@ -286,11 +319,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
286
319
|
const preconditions = [];
|
|
287
320
|
const seen = new Set();
|
|
288
321
|
const add = (precondition) => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
if (seen.has(identity))
|
|
322
|
+
const held = identity(precondition);
|
|
323
|
+
if (seen.has(held))
|
|
292
324
|
return;
|
|
293
|
-
seen.add(
|
|
325
|
+
seen.add(held);
|
|
294
326
|
preconditions.push(precondition);
|
|
295
327
|
};
|
|
296
328
|
for (const change of changes) {
|
|
@@ -300,7 +332,7 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
300
332
|
add(based);
|
|
301
333
|
continue;
|
|
302
334
|
}
|
|
303
|
-
const document = visible.collection.
|
|
335
|
+
const document = visible.collection.document(change.path);
|
|
304
336
|
if (change.kind === 'field') {
|
|
305
337
|
add({
|
|
306
338
|
kind: 'field',
|
|
@@ -325,6 +357,13 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
325
357
|
add({ kind: 'absent', path: change.path });
|
|
326
358
|
continue;
|
|
327
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
|
+
}
|
|
328
367
|
add(existence(visible, change.path));
|
|
329
368
|
if (change.kind === 'move')
|
|
330
369
|
add({ kind: 'absent', path: change.to });
|
|
@@ -334,10 +373,10 @@ function preconditionsOf(changes, visible, bases) {
|
|
|
334
373
|
function depth(path) {
|
|
335
374
|
return path.split('/').length;
|
|
336
375
|
}
|
|
337
|
-
function exceeded(limits, changes, resulting) {
|
|
338
|
-
const written =
|
|
376
|
+
function exceeded(limits, changes, effective, resulting) {
|
|
377
|
+
const written = effective.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
339
378
|
const created = changes
|
|
340
|
-
.filter((change) => change.kind === 'create' || change.kind === 'move')
|
|
379
|
+
.filter((change) => change.kind === 'create' || change.kind === 'move' || change.kind === 'binary')
|
|
341
380
|
.map((change) => (change.kind === 'move' ? change.to : change.path));
|
|
342
381
|
for (const limit of limits) {
|
|
343
382
|
const value = limit.value;
|
|
@@ -351,7 +390,7 @@ function exceeded(limits, changes, resulting) {
|
|
|
351
390
|
return refuse('provider-limit', undefined, limit.name);
|
|
352
391
|
}
|
|
353
392
|
if (limit.name === 'document-size') {
|
|
354
|
-
const large = written.find((path) => (resulting.documents[path] ?? '').
|
|
393
|
+
const large = written.find((path) => new TextEncoder().encode(resulting.documents[path] ?? '').byteLength > value);
|
|
355
394
|
if (large !== undefined)
|
|
356
395
|
return refuse('provider-limit', large, limit.name);
|
|
357
396
|
}
|
|
@@ -368,32 +407,360 @@ function exceeded(limits, changes, resulting) {
|
|
|
368
407
|
}
|
|
369
408
|
return null;
|
|
370
409
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
410
|
+
function shellRelative(text, path, handle, definition) {
|
|
411
|
+
if (!text.includes(`/rooms/${handle}/`))
|
|
412
|
+
return null;
|
|
413
|
+
const parsed = parse(text, path);
|
|
414
|
+
if (!parsed.ok)
|
|
415
|
+
return null;
|
|
416
|
+
const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
|
|
417
|
+
return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
|
|
418
|
+
}
|
|
419
|
+
function relativeField(path, name, value, handle, definition) {
|
|
420
|
+
const opened = parse('---\n---\n');
|
|
421
|
+
if (definition === undefined ||
|
|
422
|
+
!opened.ok ||
|
|
423
|
+
!JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
|
|
424
|
+
return value;
|
|
425
|
+
const held = apply(opened.document, { kind: 'field', name, value, prior: undefined });
|
|
426
|
+
if (held.outcome !== 'applied')
|
|
427
|
+
return value;
|
|
428
|
+
const result = relativize(held.document, path, handle, definition);
|
|
429
|
+
return result.outcome === 'applied' ? result.document.fields[name] : value;
|
|
430
|
+
}
|
|
431
|
+
function relativeChanges(input) {
|
|
432
|
+
const { handle, composition, visible } = input;
|
|
433
|
+
const typed = (path) => (type) => definitionOf(governing(composition, path), type);
|
|
434
|
+
const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
|
|
435
|
+
return input.changes.map((change) => {
|
|
436
|
+
const definition = typed(change.path);
|
|
437
|
+
if (change.kind === 'field') {
|
|
438
|
+
const type = visible.collection.document(change.path)?.type;
|
|
439
|
+
const value = relativeField(change.path, change.name, change.value, handle, definition(type));
|
|
440
|
+
return value === change.value ? change : { ...change, value };
|
|
441
|
+
}
|
|
442
|
+
if (change.kind === 'section' || change.kind === 'preamble') {
|
|
443
|
+
const value = body(change.path, change.value);
|
|
444
|
+
return value === change.value ? change : { ...change, value };
|
|
445
|
+
}
|
|
446
|
+
if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
|
|
447
|
+
const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
|
|
448
|
+
return content === change.content ? change : { ...change, content };
|
|
449
|
+
}
|
|
450
|
+
if (change.kind !== 'create')
|
|
451
|
+
return change;
|
|
452
|
+
const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
|
|
453
|
+
const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
|
|
454
|
+
name,
|
|
455
|
+
relativeField(change.path, name, value, handle, definition(type)),
|
|
456
|
+
]));
|
|
457
|
+
const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
|
|
458
|
+
return { ...change, fields, sections };
|
|
459
|
+
});
|
|
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 &&
|
|
375
466
|
document.type !== 'any' &&
|
|
376
|
-
governing(
|
|
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
|
+
}
|
|
736
|
+
export function buildBatch(input) {
|
|
737
|
+
const changes = relativeChanges(input);
|
|
738
|
+
const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
|
|
739
|
+
if (structural !== undefined && unmodeledType(input.visible.collection, input.composition))
|
|
377
740
|
return refuse('model-unavailable', structural.path);
|
|
378
|
-
if (
|
|
741
|
+
if (changes.length === 0)
|
|
379
742
|
return refuse('no-change');
|
|
380
|
-
const rewritten = rewrites(
|
|
743
|
+
const rewritten = rewrites(changes, input.visible);
|
|
381
744
|
if (rewritten.kind === 'refused')
|
|
382
745
|
return rewritten;
|
|
383
|
-
const changes = input.rewriting === 'provider' ? input.changes : rewritten.changes;
|
|
384
746
|
const refusal = guard(changes, input);
|
|
385
747
|
if (refusal !== null)
|
|
386
748
|
return refusal;
|
|
387
749
|
const preconditions = preconditionsOf(changes, input.visible, input.bases);
|
|
388
|
-
const predicted = input.
|
|
389
|
-
? anticipated(changes, preconditions, rewritten.changes, input.visible)
|
|
390
|
-
: { changes, preconditions };
|
|
750
|
+
const predicted = anticipated(changes, preconditions, rewritten.changes, input.visible);
|
|
391
751
|
const resulting = overlay(input.visible, [
|
|
392
752
|
{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted },
|
|
393
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;
|
|
394
760
|
const removed = new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path));
|
|
395
761
|
if (removed.size > 0) {
|
|
396
|
-
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)]);
|
|
397
764
|
if (referenced !== null)
|
|
398
765
|
return referenced;
|
|
399
766
|
}
|
|
@@ -403,27 +770,33 @@ export function buildBatch(input) {
|
|
|
403
770
|
const parsed = parse(content);
|
|
404
771
|
return parsed.ok && parsed.document.type !== undefined && parsed.document.type !== 'any';
|
|
405
772
|
};
|
|
406
|
-
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]));
|
|
407
775
|
const needsModel = typedPaths.length > 0;
|
|
408
776
|
const withoutModel = typedPaths.find((path) => governing(input.composition, path).status === 'unavailable');
|
|
409
777
|
if (withoutModel !== undefined)
|
|
410
778
|
return refuse('model-unavailable', withoutModel);
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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);
|
|
415
787
|
if (verdict.verdict === 'refuse') {
|
|
416
788
|
return {
|
|
417
789
|
kind: 'refused',
|
|
418
790
|
refusal: {
|
|
419
|
-
|
|
791
|
+
code: verdict.rule,
|
|
792
|
+
retryable: false,
|
|
420
793
|
file: verdict.file,
|
|
421
794
|
...(verdict.location === undefined ? {} : { location: verdict.location }),
|
|
422
795
|
...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
|
|
423
796
|
},
|
|
424
797
|
};
|
|
425
798
|
}
|
|
426
|
-
const limited = exceeded(input.providerLimits, changes, resulting);
|
|
799
|
+
const limited = exceeded(input.providerLimits, changes, predicted.changes, resulting);
|
|
427
800
|
if (limited !== null)
|
|
428
801
|
return limited;
|
|
429
802
|
const validated = typedPaths
|