@agentshouse/mdruntime 0.1.0-cf6041a06849c → 0.1.0-cf96c797c5fa8

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