@agentshouse/mdruntime 0.1.0-cebcd51f70ed1 → 0.1.0-cedb03eaa48b6

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