@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-caee7332d2235

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