@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-cb2e390ee7b89

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