@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.
Files changed (201) hide show
  1. package/AGENTS.md +270 -203
  2. package/README.md +20 -10
  3. package/dist/acquisition.d.ts +14 -0
  4. package/dist/acquisition.d.ts.map +1 -0
  5. package/dist/acquisition.js +24 -0
  6. package/dist/adapter.d.ts +83 -20
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/attached-model.d.ts.map +1 -1
  9. package/dist/attached-model.js +15 -2
  10. package/dist/attempt.d.ts +16 -0
  11. package/dist/attempt.d.ts.map +1 -0
  12. package/dist/attempt.js +70 -0
  13. package/dist/binary.d.ts +23 -0
  14. package/dist/binary.d.ts.map +1 -0
  15. package/dist/binary.js +8 -0
  16. package/dist/bootstrap.d.ts +18 -5
  17. package/dist/bootstrap.d.ts.map +1 -1
  18. package/dist/bootstrap.js +113 -37
  19. package/dist/change-set.d.ts +2 -0
  20. package/dist/change-set.d.ts.map +1 -1
  21. package/dist/change-set.js +49 -16
  22. package/dist/change.d.ts +15 -0
  23. package/dist/change.d.ts.map +1 -1
  24. package/dist/change.js +26 -1
  25. package/dist/chat/acts.d.ts +33 -0
  26. package/dist/chat/acts.d.ts.map +1 -0
  27. package/dist/chat/acts.js +36 -0
  28. package/dist/chat/context.d.ts +3 -0
  29. package/dist/chat/context.d.ts.map +1 -0
  30. package/dist/chat/context.js +10 -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 +63 -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 +629 -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 +80 -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 +25 -12
  89. package/dist/client.d.ts.map +1 -1
  90. package/dist/client.js +75 -16
  91. package/dist/collection.d.ts +21 -12
  92. package/dist/collection.d.ts.map +1 -1
  93. package/dist/collection.js +271 -23
  94. package/dist/connection.d.ts +44 -15
  95. package/dist/connection.d.ts.map +1 -1
  96. package/dist/connection.js +1788 -400
  97. package/dist/conversation.d.ts +26 -10
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +414 -148
  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 +156 -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 +18 -2
  115. package/dist/host.d.ts.map +1 -1
  116. package/dist/host.js +97 -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 +87 -25
  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 +5 -2
  130. package/dist/observation.d.ts.map +1 -1
  131. package/dist/observation.js +22 -11
  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 +32 -3
  136. package/dist/outcome.d.ts.map +1 -1
  137. package/dist/outcome.js +42 -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 +40 -4
  142. package/dist/query.d.ts.map +1 -1
  143. package/dist/query.js +60 -21
  144. package/dist/reach.d.ts +8 -3
  145. package/dist/reach.d.ts.map +1 -1
  146. package/dist/reach.js +60 -24
  147. package/dist/react.d.ts +31 -18
  148. package/dist/react.d.ts.map +1 -1
  149. package/dist/react.js +15 -11
  150. package/dist/read-side.d.ts +13 -6
  151. package/dist/read-side.d.ts.map +1 -1
  152. package/dist/read-side.js +394 -129
  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 +9 -6
  166. package/dist/replica.d.ts.map +1 -1
  167. package/dist/replica.js +125 -68
  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 +11 -14
  172. package/dist/rows.d.ts.map +1 -1
  173. package/dist/rows.js +89 -49
  174. package/dist/save.d.ts +31 -1
  175. package/dist/save.d.ts.map +1 -1
  176. package/dist/save.js +376 -47
  177. package/dist/search.d.ts +39 -8
  178. package/dist/search.d.ts.map +1 -1
  179. package/dist/search.js +132 -46
  180. package/dist/seed.d.ts +16 -0
  181. package/dist/seed.d.ts.map +1 -0
  182. package/dist/seed.js +61 -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 +75 -18
  190. package/dist/store.d.ts.map +1 -1
  191. package/dist/store.js +539 -200
  192. package/dist/turn.d.ts +7 -0
  193. package/dist/turn.d.ts.map +1 -0
  194. package/dist/turn.js +45 -0
  195. package/dist/wire.d.ts +141 -33
  196. package/dist/wire.d.ts.map +1 -1
  197. package/dist/wire.js +61 -31
  198. package/package.json +26 -14
  199. package/dist/attachment.d.ts +0 -3
  200. package/dist/attachment.d.ts.map +0 -1
  201. 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, 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
+ function refuse(code, file, location) {
9
9
  return {
10
10
  kind: 'refused',
11
11
  refusal: {
12
- rule,
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
- rule: applied.refusal.rule,
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' || change.kind === 'replace' || change.kind === 'create') {
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 Object.entries(visible.collection.documents)) {
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
- for (const [path, document] of Object.entries(collection.documents)) {
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: { rule: 'referenced-missing-file', file: first, referrers: [...every].sort() },
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 existing = new Set([
213
- ...Object.keys(input.visible.documents),
214
- ...input.visible.binaryTargets,
215
- ]);
216
- for (const change of changes) {
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 (unrevisionedDocument(input.visible, change.path)) {
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 (unrevisionedDocument(input.visible, change.path)) {
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.documents[change.path] === undefined) {
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 unrevisionedDocument(visible, path) {
268
- return visible.revisions[path] === undefined && !visible.binaryTargets.includes(path);
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
- return revision === undefined ? { kind: 'present', path } : { kind: 'revision', path, revision };
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 part = precondition.kind === 'field' || precondition.kind === 'section' ? precondition.name : '';
285
- const identity = `${precondition.kind} ${precondition.path} ${part}`;
286
- if (seen.has(identity))
322
+ const held = identity(precondition);
323
+ if (seen.has(held))
287
324
  return;
288
- seen.add(identity);
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.documents[change.path];
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 = changes.map((change) => (change.kind === 'move' ? change.to : change.path));
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] ?? '').length > value);
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.documents[change.path]?.type;
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 referenced = incoming(parseCollection(resulting.documents, input.composition), removed);
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 typedPaths = [...new Set(predicted.changes.flatMap(affected))].filter((path) => typed(input.visible.documents[path]) || typed(resulting.documents[path]));
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 validationText = needsModel
456
- ? resulting.documents
457
- : Object.fromEntries(Object.entries(resulting.documents).filter(([path]) => predicted.changes.some((change) => affected(change).includes(path))));
458
- const verdict = validateCollection(validationText, resulting.binaryTargets, input.composition);
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
- rule: verdict.rule,
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