@agentshouse/mdruntime 0.1.0-c0136de3edfbe → 0.1.0-c01d4615bfe46

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 (245) hide show
  1. package/README.md +16 -15
  2. package/dist/acceptance.d.ts +0 -3
  3. package/dist/acceptance.d.ts.map +1 -1
  4. package/dist/acceptance.js +74 -164
  5. package/dist/act-lanes.d.ts +30 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +225 -0
  8. package/dist/actions.d.ts +8 -2
  9. package/dist/actions.d.ts.map +1 -1
  10. package/dist/actions.js +122 -68
  11. package/dist/adapter.d.ts +49 -55
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +0 -1
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +0 -5
  16. package/dist/bootstrap.d.ts +1 -1
  17. package/dist/bootstrap.d.ts.map +1 -1
  18. package/dist/bootstrap.js +6 -18
  19. package/dist/change-set.d.ts +4 -0
  20. package/dist/change-set.d.ts.map +1 -1
  21. package/dist/change-set.js +6 -0
  22. package/dist/change.d.ts.map +1 -1
  23. package/dist/change.js +4 -2
  24. package/dist/chat-types.d.ts +2 -2
  25. package/dist/chat-types.d.ts.map +1 -1
  26. package/dist/chat-types.js +11 -2
  27. package/dist/client.d.ts +13 -19
  28. package/dist/client.d.ts.map +1 -1
  29. package/dist/client.js +35 -19
  30. package/dist/collection.d.ts +22 -4
  31. package/dist/collection.d.ts.map +1 -1
  32. package/dist/collection.js +12 -2
  33. package/dist/compile.d.ts +2 -0
  34. package/dist/compile.d.ts.map +1 -1
  35. package/dist/compile.js +31 -24
  36. package/dist/connection.d.ts.map +1 -1
  37. package/dist/connection.js +32 -24
  38. package/dist/conversation-live.d.ts +147 -0
  39. package/dist/conversation-live.d.ts.map +1 -0
  40. package/dist/conversation-live.js +258 -0
  41. package/dist/conversation-row.d.ts.map +1 -1
  42. package/dist/conversation-row.js +7 -1
  43. package/dist/conversation.d.ts +52 -44
  44. package/dist/conversation.d.ts.map +1 -1
  45. package/dist/conversation.js +956 -1029
  46. package/dist/declaration.d.ts +10 -0
  47. package/dist/declaration.d.ts.map +1 -0
  48. package/dist/declaration.js +24 -0
  49. package/dist/discovery.d.ts.map +1 -1
  50. package/dist/discovery.js +19 -44
  51. package/dist/durability.d.ts +13 -0
  52. package/dist/durability.d.ts.map +1 -1
  53. package/dist/durability.js +4 -0
  54. package/dist/entry.js +1 -1
  55. package/dist/expectation.d.ts +26 -13
  56. package/dist/expectation.d.ts.map +1 -1
  57. package/dist/expectation.js +72 -19
  58. package/dist/failure.d.ts +3 -0
  59. package/dist/failure.d.ts.map +1 -1
  60. package/dist/failure.js +6 -0
  61. package/dist/figures.d.ts +1 -0
  62. package/dist/figures.d.ts.map +1 -1
  63. package/dist/figures.js +2 -2
  64. package/dist/file.d.ts.map +1 -1
  65. package/dist/file.js +2 -1
  66. package/dist/holdings.d.ts +29 -0
  67. package/dist/holdings.d.ts.map +1 -0
  68. package/dist/holdings.js +95 -0
  69. package/dist/host.d.ts +18 -2
  70. package/dist/host.d.ts.map +1 -1
  71. package/dist/host.js +46 -11
  72. package/dist/index.d.ts +17 -9
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +13 -6
  75. package/dist/install.d.ts +2 -4
  76. package/dist/install.d.ts.map +1 -1
  77. package/dist/install.js +53 -83
  78. package/dist/lifecycle.d.ts +1 -1
  79. package/dist/lifecycle.d.ts.map +1 -1
  80. package/dist/lifecycle.js +45 -35
  81. package/dist/listing.d.ts +6 -11
  82. package/dist/listing.d.ts.map +1 -1
  83. package/dist/listing.js +25 -61
  84. package/dist/live.d.ts.map +1 -1
  85. package/dist/live.js +3 -3
  86. package/dist/locks.d.ts +2 -4
  87. package/dist/locks.d.ts.map +1 -1
  88. package/dist/locks.js +10 -39
  89. package/dist/lost.d.ts.map +1 -1
  90. package/dist/lost.js +10 -22
  91. package/dist/media.d.ts +1 -1
  92. package/dist/media.d.ts.map +1 -1
  93. package/dist/media.js +10 -13
  94. package/dist/memory.d.ts +7 -3
  95. package/dist/memory.d.ts.map +1 -1
  96. package/dist/memory.js +58 -27
  97. package/dist/messages.d.ts +10 -6
  98. package/dist/messages.d.ts.map +1 -1
  99. package/dist/messages.js +26 -36
  100. package/dist/model.d.ts +0 -1
  101. package/dist/model.d.ts.map +1 -1
  102. package/dist/model.js +0 -5
  103. package/dist/models.d.ts +4 -8
  104. package/dist/models.d.ts.map +1 -1
  105. package/dist/models.js +95 -135
  106. package/dist/observation.d.ts +3 -2
  107. package/dist/observation.d.ts.map +1 -1
  108. package/dist/observation.js +22 -21
  109. package/dist/opened.d.ts +1 -1
  110. package/dist/opened.d.ts.map +1 -1
  111. package/dist/opened.js +5 -5
  112. package/dist/opening.d.ts.map +1 -1
  113. package/dist/opening.js +7 -10
  114. package/dist/outcome.d.ts +10 -1
  115. package/dist/outcome.d.ts.map +1 -1
  116. package/dist/outcome.js +23 -2
  117. package/dist/overlay.d.ts +5 -1
  118. package/dist/overlay.d.ts.map +1 -1
  119. package/dist/overlay.js +36 -21
  120. package/dist/page-metadata.d.ts +8 -0
  121. package/dist/page-metadata.d.ts.map +1 -0
  122. package/dist/page-metadata.js +19 -0
  123. package/dist/parts.d.ts +3 -58
  124. package/dist/parts.d.ts.map +1 -1
  125. package/dist/parts.js +10 -115
  126. package/dist/paths.d.ts +1 -0
  127. package/dist/paths.d.ts.map +1 -1
  128. package/dist/paths.js +22 -0
  129. package/dist/preparation.d.ts.map +1 -1
  130. package/dist/preparation.js +25 -24
  131. package/dist/presence.d.ts +52 -0
  132. package/dist/presence.d.ts.map +1 -0
  133. package/dist/presence.js +220 -0
  134. package/dist/queries.d.ts +1 -1
  135. package/dist/queries.d.ts.map +1 -1
  136. package/dist/queries.js +24 -14
  137. package/dist/query.d.ts +0 -1
  138. package/dist/query.d.ts.map +1 -1
  139. package/dist/questions.d.ts +2 -5
  140. package/dist/questions.d.ts.map +1 -1
  141. package/dist/questions.js +3 -6
  142. package/dist/queue.d.ts +40 -0
  143. package/dist/queue.d.ts.map +1 -0
  144. package/dist/queue.js +450 -0
  145. package/dist/reach.d.ts.map +1 -1
  146. package/dist/reach.js +68 -23
  147. package/dist/react.d.ts +11 -1
  148. package/dist/react.d.ts.map +1 -1
  149. package/dist/react.js +33 -1
  150. package/dist/readiness.d.ts +1 -2
  151. package/dist/readiness.d.ts.map +1 -1
  152. package/dist/reads.d.ts +4 -1
  153. package/dist/reads.d.ts.map +1 -1
  154. package/dist/reads.js +47 -5
  155. package/dist/recovery.d.ts +3 -1
  156. package/dist/recovery.d.ts.map +1 -1
  157. package/dist/recovery.js +29 -7
  158. package/dist/refresh.js +2 -2
  159. package/dist/refusal.d.ts.map +1 -1
  160. package/dist/refusal.js +5 -6
  161. package/dist/replica.d.ts +5 -10
  162. package/dist/replica.d.ts.map +1 -1
  163. package/dist/replica.js +15 -16
  164. package/dist/retried.d.ts +30 -0
  165. package/dist/retried.d.ts.map +1 -0
  166. package/dist/retried.js +72 -0
  167. package/dist/room-draft.d.ts +4 -6
  168. package/dist/room-draft.d.ts.map +1 -1
  169. package/dist/room-draft.js +48 -59
  170. package/dist/room-drafts.d.ts.map +1 -1
  171. package/dist/room-drafts.js +17 -16
  172. package/dist/rows.d.ts.map +1 -1
  173. package/dist/rows.js +3 -4
  174. package/dist/runtime.d.ts +11 -19
  175. package/dist/runtime.d.ts.map +1 -1
  176. package/dist/save.d.ts +42 -86
  177. package/dist/save.d.ts.map +1 -1
  178. package/dist/save.js +333 -163
  179. package/dist/saves.d.ts +2 -40
  180. package/dist/saves.d.ts.map +1 -1
  181. package/dist/saves.js +109 -703
  182. package/dist/seed.d.ts.map +1 -1
  183. package/dist/seed.js +2 -3
  184. package/dist/shared.d.ts +1 -6
  185. package/dist/shared.d.ts.map +1 -1
  186. package/dist/shared.js +13 -95
  187. package/dist/shown.d.ts +6 -4
  188. package/dist/shown.d.ts.map +1 -1
  189. package/dist/shown.js +43 -35
  190. package/dist/socket/lanes.d.ts +12 -4
  191. package/dist/socket/lanes.d.ts.map +1 -1
  192. package/dist/socket/lanes.js +34 -8
  193. package/dist/socket/session.d.ts +0 -1
  194. package/dist/socket/session.d.ts.map +1 -1
  195. package/dist/socket.d.ts +3 -6
  196. package/dist/socket.d.ts.map +1 -1
  197. package/dist/socket.js +5 -16
  198. package/dist/source.d.ts +0 -7
  199. package/dist/source.d.ts.map +1 -1
  200. package/dist/source.js +5 -4
  201. package/dist/store/records.d.ts +11 -8
  202. package/dist/store/records.d.ts.map +1 -1
  203. package/dist/store/records.js +43 -12
  204. package/dist/store/space.d.ts +1 -1
  205. package/dist/store/space.d.ts.map +1 -1
  206. package/dist/store/space.js +0 -2
  207. package/dist/store/watchers.js +1 -2
  208. package/dist/store.d.ts +12 -7
  209. package/dist/store.d.ts.map +1 -1
  210. package/dist/store.js +38 -13
  211. package/dist/stored.d.ts +2 -1
  212. package/dist/stored.d.ts.map +1 -1
  213. package/dist/stored.js +27 -15
  214. package/dist/surface.d.ts +7 -4
  215. package/dist/surface.d.ts.map +1 -1
  216. package/dist/surface.js +30 -25
  217. package/dist/sync.d.ts +0 -1
  218. package/dist/sync.d.ts.map +1 -1
  219. package/dist/sync.js +93 -89
  220. package/dist/tables.d.ts +19 -11
  221. package/dist/tables.d.ts.map +1 -1
  222. package/dist/tables.js +58 -21
  223. package/dist/tallies.d.ts.map +1 -1
  224. package/dist/tallies.js +6 -7
  225. package/dist/validation.d.ts.map +1 -1
  226. package/dist/validation.js +2 -2
  227. package/dist/wire.d.ts +53 -7
  228. package/dist/wire.d.ts.map +1 -1
  229. package/dist/wire.js +17 -4
  230. package/package.json +3 -2
  231. package/dist/attempt.d.ts +0 -16
  232. package/dist/attempt.d.ts.map +0 -1
  233. package/dist/attempt.js +0 -70
  234. package/dist/job.d.ts +0 -33
  235. package/dist/job.d.ts.map +0 -1
  236. package/dist/job.js +0 -54
  237. package/dist/log.d.ts +0 -6
  238. package/dist/log.d.ts.map +0 -1
  239. package/dist/log.js +0 -14
  240. package/dist/socket/rooms.d.ts +0 -43
  241. package/dist/socket/rooms.d.ts.map +0 -1
  242. package/dist/socket/rooms.js +0 -90
  243. package/dist/store/fences.d.ts +0 -16
  244. package/dist/store/fences.d.ts.map +0 -1
  245. package/dist/store/fences.js +0 -26
package/dist/save.js CHANGED
@@ -1,14 +1,14 @@
1
- import { definitionOf } from './model.js';
2
- import { apply, ARRAY_INDEX, ATTACHMENT, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
1
+ import { apply, ARRAY_INDEX, ATTACHMENT, create, equalValue, links, parse, pointerSegments, references, relative, relativize, retarget, roomReferences, serialize, target, } from '@agentshouse/mdmodel';
3
2
  import { immutable, leaving, queuedFile, departure, } from './change.js';
4
- import { collectionOf, documentsOf, governing, } from './collection.js';
5
- import { changeBatchIdentity } from './identity.js';
6
- import { overlaying } from './overlay.js';
7
- import { ancestors, covered, normalize } from './paths.js';
8
- import { sorting } from './shared.js';
3
+ import { collectionOf, definitionsAt, documentsOf, governing, } from './collection.js';
4
+ import { changeBatchIdentity, roomDraftRevision } from './identity.js';
5
+ import { composed, overlaying } from './overlay.js';
6
+ import { ancestors } from './paths.js';
7
+ import { entriesIn, sorting } from './shared.js';
8
+ import { changedParts, changesOf, moveTarget, withdrawnDrafts, } from './room-draft.js';
9
9
  import { paced, ROW_WEIGHT, stepped } from './turn.js';
10
10
  import { validatedCollection } from './validation.js';
11
- function refuse(code, file, location) {
11
+ function refuse(code, file, location, line) {
12
12
  return {
13
13
  kind: 'refused',
14
14
  refusal: {
@@ -16,13 +16,14 @@ function refuse(code, file, location) {
16
16
  retryable: false,
17
17
  ...(file === undefined ? {} : { file }),
18
18
  ...(location === undefined ? {} : { location }),
19
+ ...(line === undefined ? {} : { line }),
19
20
  },
20
21
  };
21
22
  }
23
+ function refusedBy(refusal, file) {
24
+ return refuse(refusal.rule, file, refusal.location, refusal.line);
25
+ }
22
26
  function repointed(document, definition, from, to, moves, settled) {
23
- const parsed = parse(document.content, from);
24
- if (!parsed.ok)
25
- return [];
26
27
  const changes = [];
27
28
  const names = new Set();
28
29
  for (const { pointer, value } of definition === undefined
@@ -45,15 +46,22 @@ function repointed(document, definition, from, to, moves, settled) {
45
46
  }
46
47
  if (changes.length === 0)
47
48
  return [];
48
- const applied = apply(parsed.document, changes);
49
+ const applied = apply(document, changes, definition);
49
50
  if (applied.outcome !== 'applied')
50
51
  return [];
51
52
  const next = applied.document;
52
53
  return [...names].map((name) => ({ kind: 'field', path: from, name, value: next.fields[name] }));
53
54
  }
54
- function edited(document, changes) {
55
+ function edited(document, changes, definition) {
55
56
  const parts = new Map();
56
57
  for (const change of changes) {
58
+ if (change.kind === 'field')
59
+ parts.set(`field ${change.name}`, {
60
+ kind: 'field',
61
+ name: change.name,
62
+ value: change.value,
63
+ prior: document.fields[change.name],
64
+ });
57
65
  if (change.kind === 'preamble')
58
66
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
59
67
  if (change.kind === 'section')
@@ -66,11 +74,16 @@ function edited(document, changes) {
66
74
  }
67
75
  if (parts.size === 0)
68
76
  return document;
69
- const applied = apply(document, [...parts.values()]);
77
+ const applied = apply(document, [...parts.values()], definition);
70
78
  return applied.outcome === 'applied' ? applied.document : document;
71
79
  }
72
80
  function bodies(path, before, after) {
73
81
  const changes = [];
82
+ for (const [name, value] of Object.entries(after.fields)) {
83
+ if (equalValue(before.fields[name], value))
84
+ continue;
85
+ changes.push({ kind: 'field', path, name, value });
86
+ }
74
87
  if (after.preamble !== before.preamble) {
75
88
  changes.push({ kind: 'preamble', path, value: after.preamble });
76
89
  }
@@ -81,7 +94,7 @@ function bodies(path, before, after) {
81
94
  }
82
95
  return changes;
83
96
  }
84
- function relinks(path, moved, before, moves) {
97
+ function relinks(path, moved, before, moves, definition) {
85
98
  const changes = [];
86
99
  for (const link of links(before, path)) {
87
100
  const destination = moves.get(link.target) ?? link.target;
@@ -94,18 +107,9 @@ function relinks(path, moved, before, moves) {
94
107
  }
95
108
  if (changes.length === 0)
96
109
  return { kind: 'changes', changes: [] };
97
- const applied = apply(before, changes);
98
- if (applied.outcome === 'refuse') {
99
- return {
100
- kind: 'refused',
101
- refusal: {
102
- code: applied.refusal.rule,
103
- retryable: false,
104
- file: path,
105
- ...(applied.refusal.location === undefined ? {} : { location: applied.refusal.location }),
106
- },
107
- };
108
- }
110
+ const applied = apply(before, changes, definition);
111
+ if (applied.outcome === 'refuse')
112
+ return refusedBy(applied.refusal, path);
109
113
  const after = applied.document;
110
114
  if (after === before)
111
115
  return { kind: 'changes', changes: [] };
@@ -115,7 +119,18 @@ function relinks(path, moved, before, moves) {
115
119
  return { kind: 'changes', changes: bodies(path, before, after) };
116
120
  }
117
121
  function partKey(change) {
118
- return change.kind === 'section' ? `section ${change.name}` : change.kind;
122
+ return change.kind === 'section' || change.kind === 'field'
123
+ ? `${change.kind} ${change.name}`
124
+ : change.kind;
125
+ }
126
+ const NAMES_A_SKIP_TESTS = 64;
127
+ const ENCODED = /[%&\\]/u;
128
+ function movedNames(moves) {
129
+ const names = new Set([...moves.keys()].map((path) => path.slice(path.lastIndexOf('/') + 1)));
130
+ return names.size > NAMES_A_SKIP_TESTS ? null : [...names];
131
+ }
132
+ function cannotLink(text, names) {
133
+ return names !== null && !ENCODED.test(text) && !names.some((name) => text.includes(name));
119
134
  }
120
135
  function* rewrites(changes, visible) {
121
136
  const moves = new Map();
@@ -142,17 +157,20 @@ function* rewrites(changes, visible) {
142
157
  }
143
158
  if (moves.size === 0)
144
159
  return { kind: 'changes', changes };
160
+ const names = movedNames(moves);
145
161
  const merged = [...changes];
146
162
  const added = [];
147
163
  for (const [path, document] of yield* documentsOf(visible.collection)) {
148
164
  if (settled.has(path))
149
165
  continue;
150
166
  const moved = moves.get(path) ?? path;
151
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
167
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
152
168
  const repointing = repointed(document, definition, path, moved, moves, settled);
153
169
  added.push(...repointing);
154
170
  const own = parts.get(path) ?? [];
155
- const relinked = relinks(path, moved, edited(document, own.map((index) => merged[index])), moves);
171
+ const relinked = moved === path && own.length === 0 && cannotLink(document.content, names)
172
+ ? { kind: 'changes', changes: [] }
173
+ : relinks(path, moved, edited(document, own.map((index) => merged[index]), definition), moves, definition);
156
174
  if (relinked.kind === 'refused')
157
175
  return relinked;
158
176
  const keyed = new Map();
@@ -165,7 +183,7 @@ function* rewrites(changes, visible) {
165
183
  if (change.kind === 'replace') {
166
184
  for (const index of own) {
167
185
  const kind = merged[index]?.kind;
168
- if (kind === 'section' || kind === 'preamble')
186
+ if (kind === 'field' || kind === 'section' || kind === 'preamble')
169
187
  merged[index] = null;
170
188
  }
171
189
  added.push(change);
@@ -221,25 +239,21 @@ function crossed(document, definition, path, to) {
221
239
  }
222
240
  return changes;
223
241
  }
224
- function rewritten(path, document, changes) {
242
+ function rewritten(path, document, changes, definition) {
225
243
  if (changes.length === 0)
226
244
  return [];
227
- const applied = apply(document, changes);
245
+ const applied = apply(document, changes, definition);
228
246
  if (applied.outcome !== 'applied')
229
247
  return [];
230
248
  const after = applied.document;
231
249
  if (document.type === undefined)
232
250
  return [{ kind: 'replace', path, content: serialize(after) }];
233
- const names = new Set(changes.flatMap((change) => change.kind === 'reference' ? [pointerSegments(change.pointer)[0] ?? ''] : []));
234
- return [
235
- ...[...names].map((name) => ({ kind: 'field', path, name, value: after.fields[name] })),
236
- ...bodies(path, document, after),
237
- ];
251
+ return bodies(path, document, after);
238
252
  }
239
253
  function roomPathOf(handle, path) {
240
254
  return `/rooms/${handle}/${path}`;
241
255
  }
242
- export function* leavingRewritten(batch, visible) {
256
+ function* leavingRewritten(batch, visible) {
243
257
  const between = batch.between;
244
258
  if (between === undefined)
245
259
  return batch;
@@ -248,8 +262,8 @@ export function* leavingRewritten(batch, visible) {
248
262
  for (const [path, document] of yield* documentsOf(visible.collection)) {
249
263
  if (moved.has(path))
250
264
  continue;
251
- const definition = definitionOf(governing(visible.collection.composition, path), document.type);
252
- derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named))));
265
+ const definition = definitionsAt(visible.collection.composition, path)(document.type);
266
+ derived.push(...rewritten(path, document, crossed(document, definition, path, (named) => moved.get(named)), definition));
253
267
  yield ROW_WEIGHT + visible.collection.text[path].length;
254
268
  }
255
269
  if (derived.length === 0)
@@ -291,7 +305,7 @@ function* incoming(collection, removed, candidates) {
291
305
  for (const [path, document] of candidates === null
292
306
  ? yield* documentsOf(collection)
293
307
  : read(candidates)) {
294
- const definition = definitionOf(governing(collection.composition, path), document.type);
308
+ const definition = definitionsAt(collection.composition, path)(document.type);
295
309
  for (const { value, to } of definition === undefined ? [] : references(document, definition)) {
296
310
  if (to.includes(ATTACHMENT))
297
311
  continue;
@@ -334,15 +348,9 @@ function guard(changes, input, removing) {
334
348
  const occupied = (path) => input.folders.has(path) ? path : (ancestors(path).find((one) => existing.has(one)) ?? null);
335
349
  for (const [index, change] of changes.entries()) {
336
350
  for (const path of affected(change)) {
337
- const normalized = normalize(path);
338
- if (normalized === null || normalized === '')
339
- return refuse('room-root', path);
340
- if (covered(input.providerOwnedPaths, normalized)) {
341
- return refuse('provider-owned-path', normalized);
342
- }
343
- for (const operation of change.kind === 'remove' ? removing : WRITE)
344
- if (!input.local && !covered(input.operations[operation], normalized))
345
- return refuse(`${operation}-grant`, normalized);
351
+ const refused = input.rule(path, change.kind === 'remove' ? removing : WRITE);
352
+ if (refused !== null)
353
+ return refuse(refused.code, refused.path);
346
354
  }
347
355
  if (change.kind === 'create' || change.kind === 'arrival') {
348
356
  const taken = existing.has(change.path) ? change.path : occupied(change.path);
@@ -403,9 +411,9 @@ function guard(changes, input, removing) {
403
411
  return null;
404
412
  }
405
413
  function unrevised(input, path) {
406
- return (!input.local && input.queued?.has(path) !== true && input.visible.revisions[path] === undefined);
414
+ return !input.local && !input.queued.has(path) && input.visible.revisions[path] === undefined;
407
415
  }
408
- export function queuedDocuments(queue) {
416
+ function queuedDocuments(queue) {
409
417
  const paths = new Set();
410
418
  for (const batch of queue) {
411
419
  for (const change of batch.changes) {
@@ -541,56 +549,69 @@ function* exceeding(limits, changes, effective, resulting) {
541
549
  function shellRelative(text, path, handle, definition) {
542
550
  if (!text.includes(`/rooms/${handle}/`))
543
551
  return null;
544
- const parsed = parse(text, path);
552
+ const parsed = parse(text, path, definition);
545
553
  if (!parsed.ok)
546
554
  return null;
547
555
  const result = relativize(parsed.document, path, handle, definition(parsed.document.type));
548
556
  return result.outcome === 'applied' && result.document.content !== text ? result.document : null;
549
557
  }
550
558
  function relativeField(path, name, value, handle, definition) {
551
- const opened = parse('---\n---\n');
552
- if (definition === undefined ||
553
- !opened.ok ||
554
- !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
559
+ if (definition === undefined || !JSON.stringify(value ?? null).includes(`/rooms/${handle}/`))
555
560
  return value;
556
- const held = apply(opened.document, [{ kind: 'field', name, value, prior: undefined }]);
561
+ const held = apply(create(definition.name, definition), [{ kind: 'field', name, value, prior: undefined }], definition);
557
562
  if (held.outcome !== 'applied')
558
563
  return value;
559
564
  const result = relativize(held.document, path, handle, definition);
560
565
  return result.outcome === 'applied' ? result.document.fields[name] : value;
561
566
  }
562
567
  function relativeChanges(input) {
568
+ const changes = [];
569
+ for (const change of input.changes) {
570
+ const next = relativeChange(change, input);
571
+ if ('refusal' in next)
572
+ return next;
573
+ changes.push(next.change);
574
+ }
575
+ return { kind: 'changes', changes };
576
+ }
577
+ function relativeChange(change, input) {
563
578
  const { handle, composition, visible } = input;
564
- const typed = (path) => (type) => definitionOf(governing(composition, path), type);
565
579
  const body = (path, text) => shellRelative(text, path, handle, () => undefined)?.content ?? text;
566
- return input.changes.map((change) => {
567
- const definition = typed(change.path);
568
- if (change.kind === 'field') {
569
- const type = visible.collection.document(change.path)?.type;
570
- const value = relativeField(change.path, change.name, change.value, handle, definition(type));
571
- return value === change.value ? change : { ...change, value };
572
- }
573
- if (change.kind === 'section' || change.kind === 'preamble') {
574
- const value = body(change.path, change.value);
575
- return value === change.value ? change : { ...change, value };
576
- }
577
- if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
578
- const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
579
- return content === change.content ? change : { ...change, content };
580
- }
581
- if (change.kind !== 'create')
582
- return change;
583
- const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
584
- const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
585
- name,
586
- relativeField(change.path, name, value, handle, definition(type)),
587
- ]));
588
- const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
589
- return { ...change, fields, sections };
590
- });
580
+ const definition = definitionsAt(composition, change.path);
581
+ if (change.kind === 'field') {
582
+ const type = visible.collection.document(change.path)?.type;
583
+ const value = relativeField(change.path, change.name, change.value, handle, definition(type));
584
+ return { change: value === change.value ? change : { ...change, value } };
585
+ }
586
+ if (change.kind === 'section' || change.kind === 'preamble') {
587
+ const value = body(change.path, change.value);
588
+ return { change: value === change.value ? change : { ...change, value } };
589
+ }
590
+ if (change.kind === 'replace' || (change.kind === 'create' && 'content' in change)) {
591
+ const content = shellRelative(change.content, change.path, handle, definition)?.content ?? change.content;
592
+ return { change: content === change.content ? change : { ...change, content } };
593
+ }
594
+ if (change.kind !== 'create')
595
+ return { change };
596
+ const type = typeof change.fields.type === 'string' ? change.fields.type : undefined;
597
+ const fields = Object.fromEntries(Object.entries(change.fields).map(([name, value]) => [
598
+ name,
599
+ relativeField(change.path, name, value, handle, definition(type)),
600
+ ]));
601
+ const sections = Object.fromEntries(Object.entries(change.sections).map(([name, value]) => [name, body(change.path, value)]));
602
+ const written = composed(change.path, fields, sections, composition);
603
+ if (written.outcome === 'refuse')
604
+ return refusedBy(written.refusal, change.path);
605
+ if (written.outcome === 'stale')
606
+ throw new Error(`the creation of ${change.path} is stale`);
607
+ if (Object.keys(sections).length === 0)
608
+ return { change: { ...change, fields, sections } };
609
+ return {
610
+ change: { kind: 'create', path: change.path, content: serialize(written.document) },
611
+ };
591
612
  }
592
613
  function unqueueable(file) {
593
- return { kind: 'refused', refusal: { code: 'unknown-revision', retryable: false, file } };
614
+ return refuse('unknown-revision', file);
594
615
  }
595
616
  function isPart(change) {
596
617
  return change.kind === 'field' || change.kind === 'section' || change.kind === 'preamble';
@@ -598,8 +619,29 @@ function isPart(change) {
598
619
  function touched(change) {
599
620
  return change.kind === 'move' ? [change.path, change.to] : [change.path];
600
621
  }
601
- function partsApplied(content, path, changes) {
602
- const parsed = parse(content, path);
622
+ function partsApplied(content, path, changes, composition) {
623
+ const applied = partsResult(content, path, changes, composition);
624
+ return applied?.outcome === 'applied' ? serialize(applied.document) : null;
625
+ }
626
+ function unapplied(changes, visible, composition) {
627
+ const parted = new Map();
628
+ for (const change of changes) {
629
+ if (isPart(change))
630
+ parted.set(change.path, [...(parted.get(change.path) ?? []), change]);
631
+ }
632
+ for (const [path, parts] of parted) {
633
+ const content = visible.documents[path];
634
+ if (content === undefined)
635
+ continue;
636
+ const applied = partsResult(content, path, parts, composition);
637
+ if (applied?.outcome === 'refuse')
638
+ return refusedBy(applied.refusal, path);
639
+ }
640
+ return null;
641
+ }
642
+ function partsResult(content, path, changes, composition) {
643
+ const definitions = definitionsAt(composition, path);
644
+ const parsed = parse(content, path, definitions);
603
645
  if (!parsed.ok)
604
646
  return null;
605
647
  const document = parsed.document;
@@ -622,12 +664,11 @@ function partsApplied(content, path, changes) {
622
664
  else
623
665
  parts.set('preamble', { kind: 'preamble', value: change.value, prior: document.preamble });
624
666
  }
625
- const applied = apply(document, [...parts.values()]);
626
- return applied.outcome === 'applied' ? serialize(applied.document) : null;
667
+ return apply(document, [...parts.values()], definitions(document.type));
627
668
  }
628
- function settledParts(changes, deferred) {
669
+ function settledParts(changes, deferred, composition) {
629
670
  for (const [held, parts] of deferred) {
630
- const content = partsApplied(held.content, held.path, parts);
671
+ const content = partsApplied(held.content, held.path, parts, composition);
631
672
  if (content === null)
632
673
  return held.path;
633
674
  changes[changes.indexOf(held)] = { ...held, content };
@@ -702,7 +743,9 @@ function* relinked(batch, move, before, after, appending) {
702
743
  for (const change of derived.changes) {
703
744
  if (change === move)
704
745
  continue;
705
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
746
+ const unsettled = isPart(change)
747
+ ? null
748
+ : settledParts(changes, deferred, after.collection.composition);
706
749
  if (unsettled !== null)
707
750
  return unqueueable(unsettled);
708
751
  const outcome = folded(changes, change, appending, deferred);
@@ -713,7 +756,7 @@ function* relinked(batch, move, before, after, appending) {
713
756
  if (outcome !== 'skipped')
714
757
  changed = true;
715
758
  }
716
- const unsettled = settledParts(changes, deferred);
759
+ const unsettled = settledParts(changes, deferred, after.collection.composition);
717
760
  if (unsettled !== null)
718
761
  return unqueueable(unsettled);
719
762
  if (!changed)
@@ -774,11 +817,10 @@ function withdrawnFile(batch, change, at) {
774
817
  ];
775
818
  return immutable({ ...batch, id: changeBatchIdentity(), changes, preconditions });
776
819
  }
777
- export function* requeuing(queue, changes, visible, changeable, state) {
820
+ function* requeuing(queue, changes, visible, changeable, state) {
778
821
  const next = [...queue];
779
822
  const origins = new Map();
780
823
  const withdrawn = new Map();
781
- const owners = new Set();
782
824
  const queued = new Set();
783
825
  const kept = [];
784
826
  const originOf = (batch) => origins.get(batch) ?? batch.id;
@@ -824,7 +866,6 @@ export function* requeuing(queue, changes, visible, changeable, state) {
824
866
  if (replaced !== null)
825
867
  origins.set(replaced, origin);
826
868
  }
827
- owners.add(originOf(owner));
828
869
  next.splice(index, following.length, ...rebuilt.filter((batch) => batch !== null));
829
870
  queued.add(change.path);
830
871
  }
@@ -834,56 +875,13 @@ export function* requeuing(queue, changes, visible, changeable, state) {
834
875
  changes: kept,
835
876
  queued,
836
877
  withdrawn,
837
- owners,
838
878
  };
839
879
  }
840
- function covering(precondition) {
841
- return precondition.kind === 'field' ||
842
- precondition.kind === 'section' ||
843
- precondition.kind === 'preamble'
844
- ? identity(precondition)
845
- : `path ${precondition.path}`;
846
- }
847
- export function joined(queue, owner, extra) {
848
- const later = new Set(queue.slice(queue.indexOf(owner) + 1).flatMap((batch) => batch.changes.flatMap(touched)));
849
- const clash = extra.changes.flatMap(touched).find((path) => later.has(path));
850
- if (clash !== undefined)
851
- return unqueueable(clash);
852
- const changes = [...owner.changes];
853
- const deferred = new Map();
854
- for (const change of extra.changes) {
855
- const unsettled = isPart(change) ? null : settledParts(changes, deferred);
856
- if (unsettled !== null)
857
- return unqueueable(unsettled);
858
- if (folded(changes, change, true, deferred) === 'refused')
859
- return unqueueable(change.path);
860
- }
861
- const unsettled = settledParts(changes, deferred);
862
- if (unsettled !== null)
863
- return unqueueable(unsettled);
864
- const held = new Set(owner.preconditions.map(covering));
865
- const written = new Set(owner.changes.flatMap((change) => change.kind === 'create' || change.kind === 'replace' ? [change.path] : []));
866
- const preconditions = [
867
- ...owner.preconditions,
868
- ...extra.preconditions.filter((precondition) => {
869
- const key = covering(precondition);
870
- return (!held.has(key) && (key === `path ${precondition.path}` || !written.has(precondition.path)));
871
- }),
872
- ];
873
- const batch = immutable({
874
- ...owner,
875
- ...(extra.validation === undefined ? {} : { validation: extra.validation }),
876
- id: changeBatchIdentity(),
877
- changes,
878
- preconditions,
879
- });
880
- return { kind: 'joined', batch, queue: queue.map((item) => (item === owner ? batch : item)) };
881
- }
882
- export function* limiting(queue, batch, limits, state) {
880
+ function* limiting(queue, batch, limits, state) {
883
881
  const at = queue.indexOf(batch);
884
882
  const rewritten = yield* providerRewritten(batch, yield* state(queue.slice(0, at)));
885
883
  const limited = yield* exceeding(limits, batch.changes, rewritten.changes, yield* state(queue.slice(0, at + 1)));
886
- return limited?.kind === 'refused' ? limited.refusal : null;
884
+ return limited?.refusal ?? null;
887
885
  }
888
886
  function typedText(content) {
889
887
  if (content === undefined)
@@ -900,7 +898,7 @@ function touchedLibrary(resulting, touched, composition) {
900
898
  })
901
899
  .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
902
900
  const contents = new Map(entries.map(({ path, content }) => [path, content]));
903
- const binaries = new Set(Object.keys(resulting.binaries).filter((path) => resulting.binaries[path] !== undefined));
901
+ const binaries = new Set(Array.from(entriesIn(resulting.binaries), ([path]) => path));
904
902
  return validatedCollection(() => Promise.resolve({ entries, next: null }), async (paths) => {
905
903
  const known = new Map();
906
904
  const pace = paced();
@@ -909,7 +907,7 @@ function touchedLibrary(resulting, touched, composition) {
909
907
  if (binaries.has(path))
910
908
  known.set(path, { type: undefined, binary: true });
911
909
  else if (content !== undefined && content !== null) {
912
- const read = parse(content, path);
910
+ const read = parse(content, path, definitionsAt(composition, path));
913
911
  if (read.ok)
914
912
  known.set(path, { type: read.document.type ?? 'any', binary: false });
915
913
  await pace(content.length + ROW_WEIGHT);
@@ -947,6 +945,7 @@ async function judging(visible, resulting, composition, validate, removed, touch
947
945
  retryable: false,
948
946
  file: verdict.file,
949
947
  ...(verdict.location === undefined ? {} : { location: verdict.location }),
948
+ ...(verdict.line === undefined ? {} : { line: verdict.line }),
950
949
  ...(verdict.referrers === undefined ? {} : { referrers: verdict.referrers }),
951
950
  },
952
951
  };
@@ -962,8 +961,6 @@ function placed(reference) {
962
961
  const key = roomPathOf(reference.handle, reference.path);
963
962
  if (reference.kind === 'link')
964
963
  return `link ${key}`;
965
- if (reference.kind === 'item')
966
- return `section ${reference.section} ${key}`;
967
964
  const pointer = reference.pointer
968
965
  .split('/')
969
966
  .map((segment) => (ARRAY_INDEX.test(segment) ? '-' : segment))
@@ -973,10 +970,11 @@ function placed(reference) {
973
970
  function roomReferencesIn(path, content, composition) {
974
971
  if (content === undefined)
975
972
  return [];
976
- const parsed = parse(content, path);
973
+ const definitions = definitionsAt(composition, path);
974
+ const parsed = parse(content, path, definitions);
977
975
  if (!parsed.ok)
978
976
  return [];
979
- return roomReferences(parsed.document, definitionOf(governing(composition, path), parsed.document.type));
977
+ return roomReferences(parsed.document, definitions(parsed.document.type));
980
978
  }
981
979
  function* admittedRooms(changes, visible, resulting, composition) {
982
980
  const from = new Map();
@@ -994,7 +992,7 @@ function* admittedRooms(changes, visible, resulting, composition) {
994
992
  continue;
995
993
  if (held.has(placed(reference)))
996
994
  continue;
997
- return refuse('unresolved-reference', path, reference.kind === 'field' ? reference.pointer : reference.section);
995
+ return refuse('unresolved-reference', path, reference.pointer);
998
996
  }
999
997
  yield ROW_WEIGHT + now.length;
1000
998
  }
@@ -1012,7 +1010,10 @@ function validationOf(validated) {
1012
1010
  };
1013
1011
  }
1014
1012
  async function batching(input) {
1015
- const changes = relativeChanges(input);
1013
+ const relative = relativeChanges(input);
1014
+ if (relative.kind === 'refused')
1015
+ return relative;
1016
+ const { changes } = relative;
1016
1017
  const structural = changes.find((change) => change.kind === 'move' || change.kind === 'remove');
1017
1018
  if (structural !== undefined && input.unmodeled) {
1018
1019
  return refuse('model-unavailable', structural.path);
@@ -1022,12 +1023,12 @@ async function batching(input) {
1022
1023
  const rewritten = await stepped(rewrites(changes, input.visible));
1023
1024
  if (rewritten.kind === 'refused')
1024
1025
  return rewritten;
1025
- const refusal = guard(changes, input, WRITE);
1026
+ const refusal = guard(changes, input, WRITE) ?? unapplied(changes, input.visible, input.composition);
1026
1027
  if (refusal !== null)
1027
1028
  return refusal;
1028
1029
  const preconditions = await stepped(preconditionsOf(changes, input.visible, input.bases));
1029
1030
  const predicted = await stepped(anticipated(changes, preconditions, rewritten.changes, input.visible));
1030
- const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }]));
1031
+ const resulting = await stepped(overlaying(input.visible, [{ id: '', baseRoomVersion: input.baseRoomVersion, ...predicted }], input.composition));
1031
1032
  const judged = await judging(input.visible, resulting, input.composition, input.validate, new Set(changes.filter((change) => change.kind === 'remove').map((change) => change.path)), [...new Set(predicted.changes.flatMap(affected))]);
1032
1033
  if (judged.kind === 'refused')
1033
1034
  return judged;
@@ -1049,7 +1050,7 @@ async function batching(input) {
1049
1050
  }),
1050
1051
  };
1051
1052
  }
1052
- export const MOVE_DOCUMENTS = 'move-documents';
1053
+ const MOVE_DOCUMENTS = 'move-documents';
1053
1054
  function heldAt(visible, path) {
1054
1055
  return visible.documents[path] !== undefined || visible.binaries[path] !== undefined;
1055
1056
  }
@@ -1058,7 +1059,7 @@ function arrivingText(path, to, source, moved) {
1058
1059
  const document = source.visible.collection.document(path);
1059
1060
  if (document === undefined)
1060
1061
  return content;
1061
- const definition = definitionOf(governing(source.composition, path), document.type);
1062
+ const definition = definitionsAt(source.composition, path)(document.type);
1062
1063
  const changes = crossed(document, definition, path, (named, href) => {
1063
1064
  const at = moved.get(named);
1064
1065
  if (at !== undefined)
@@ -1067,7 +1068,7 @@ function arrivingText(path, to, source, moved) {
1067
1068
  });
1068
1069
  if (changes.length === 0)
1069
1070
  return content;
1070
- const applied = apply(document, changes);
1071
+ const applied = apply(document, changes, definition);
1071
1072
  return applied.outcome === 'applied' ? serialize(applied.document) : content;
1072
1073
  }
1073
1074
  function arrivalOf({ path, to }, source, moved) {
@@ -1101,8 +1102,6 @@ function arrivalOf({ path, to }, source, moved) {
1101
1102
  }
1102
1103
  async function moving(input) {
1103
1104
  const { source, destination, moves } = input;
1104
- if (moves.length === 0)
1105
- return refuse('no-change');
1106
1105
  const removals = moves.map(({ path }) => ({ kind: 'remove', path }));
1107
1106
  const leaves = guard(removals, { ...source, local: input.local, bases: [] }, MOVE_OUT);
1108
1107
  if (leaves !== null)
@@ -1141,7 +1140,7 @@ async function moving(input) {
1141
1140
  between,
1142
1141
  };
1143
1142
  const effective = await stepped(leavingRewritten(leavingBatch, source.visible));
1144
- const left = await stepped(overlaying(source.visible, [effective]));
1143
+ const left = await stepped(overlaying(source.visible, [effective], source.composition));
1145
1144
  const sourceJudged = await judging(source.visible, left, source.composition, source.validate, new Set(moves.map(({ path }) => path)), [...new Set(effective.changes.map((change) => change.path))]);
1146
1145
  if (sourceJudged.kind === 'refused')
1147
1146
  return sourceJudged;
@@ -1153,7 +1152,7 @@ async function moving(input) {
1153
1152
  ...written,
1154
1153
  between,
1155
1154
  };
1156
- const arrived = await stepped(overlaying(destination.visible, [arrivingBatch]));
1155
+ const arrived = await stepped(overlaying(destination.visible, [arrivingBatch], destination.composition));
1157
1156
  const destinationJudged = await judging(destination.visible, arrived, destination.composition, destination.validate, new Set(), moves.map(({ to }) => to));
1158
1157
  if (destinationJudged.kind === 'refused')
1159
1158
  return destinationJudged;
@@ -1163,9 +1162,180 @@ async function moving(input) {
1163
1162
  return limited;
1164
1163
  return { kind: 'moved', leaving: immutable(leavingBatch), arriving: immutable(arrivingBatch) };
1165
1164
  }
1166
- export function buildMove(input) {
1167
- return moving(input);
1165
+ function unreadable(saved, visible, composition) {
1166
+ const path = saved.find(({ path: at, parts }) => (parts.fields !== undefined ||
1167
+ parts.sections !== undefined ||
1168
+ parts.preamble !== undefined) &&
1169
+ typedText(visible.documents[at]) &&
1170
+ governing(composition, at).status === 'unavailable')?.path;
1171
+ return path === undefined ? null : refuse('model-unavailable', path);
1172
+ }
1173
+ function changedUnderneath(saved, visible) {
1174
+ const parts = saved.flatMap(({ path, parts: held }) => changedParts(path, held, visible));
1175
+ return parts.length === 0
1176
+ ? null
1177
+ : { kind: 'refused', refusal: { code: 'changed-underneath', retryable: false, parts } };
1178
+ }
1179
+ function sideOf(room, visible) {
1180
+ const { record } = room;
1181
+ return {
1182
+ room: record.locator,
1183
+ handle: record.declaration.handle,
1184
+ baseRoomVersion: record.roomVersion,
1185
+ composition: room.composition,
1186
+ rule: room.rule,
1187
+ visible,
1188
+ folders: room.folders,
1189
+ queued: queuedDocuments(room.queue),
1190
+ unmodeled: !room.modelled,
1191
+ validate: room.validate,
1192
+ };
1193
+ }
1194
+ function carriedDrafts(ask, from, into) {
1195
+ return new Map(ask.saved.flatMap(({ path, parts }) => {
1196
+ const record = from.drafts.get(path);
1197
+ if (parts.move === undefined || record === undefined || ask.consumed.has(path))
1198
+ return [];
1199
+ const to = moveTarget(parts.move);
1200
+ if (into.drafts.has(to))
1201
+ return [];
1202
+ const locator = into.record.locator;
1203
+ return [[path, { ...record, locator, path: to, revision: roomDraftRevision() }]];
1204
+ }));
1205
+ }
1206
+ async function plannedMove(ask, source, into) {
1207
+ const destination = into.record.locator;
1208
+ const mixed = ask.saved.find(({ parts }) => Object.keys(parts).length !== 1 || parts.move?.room !== destination);
1209
+ if (mixed !== undefined)
1210
+ return refuse('mixed-room-move', mixed.path);
1211
+ const leavingVisible = await stepped(source.replay(source.queue));
1212
+ const changed = changedUnderneath(ask.saved, leavingVisible);
1213
+ if (changed !== null)
1214
+ return changed;
1215
+ const moves = ask.saved.map(({ path, parts }) => ({ path, to: moveTarget(parts.move) }));
1216
+ const built = await moving({
1217
+ source: sideOf(source, leavingVisible),
1218
+ destination: sideOf(into, await stepped(into.replay(into.queue))),
1219
+ moves,
1220
+ providerLimits: ask.providerLimits,
1221
+ local: ask.local,
1222
+ ...(ask.writtenBy === undefined ? {} : { writtenBy: ask.writtenBy }),
1223
+ });
1224
+ if (built.kind === 'refused')
1225
+ return built;
1226
+ const { id } = built.leaving;
1227
+ const carried = carriedDrafts(ask, source, into);
1228
+ return {
1229
+ kind: 'planned',
1230
+ identifier: id,
1231
+ rooms: [
1232
+ {
1233
+ locator: source.record.locator,
1234
+ queue: [...source.queue, built.leaving],
1235
+ saved: new Map([[id, ask.saved]]),
1236
+ consumed: [...ask.consumed, ...carried.keys()],
1237
+ carried: [],
1238
+ },
1239
+ {
1240
+ locator: destination,
1241
+ queue: [...into.queue, built.arriving],
1242
+ saved: new Map([
1243
+ [id, [...carried.values()].map(({ path }) => ({ path, parts: {} }))],
1244
+ ]),
1245
+ consumed: [],
1246
+ carried: [...carried.values()],
1247
+ },
1248
+ ],
1249
+ };
1250
+ }
1251
+ async function plannedWithin(ask, room) {
1252
+ const { record } = room;
1253
+ const visible = await stepped(room.replay(room.queue));
1254
+ const changed = unreadable(ask.saved, visible, room.composition) ?? changedUnderneath(ask.saved, visible);
1255
+ if (changed !== null)
1256
+ return changed;
1257
+ const parts = ask.saved.map(({ path, parts: held }) => changesOf(path, held));
1258
+ const changes = parts.flatMap(({ changes: made }) => made);
1259
+ const requeue = await stepped(requeuing(room.queue, changes, visible, (batch) => room.changeable.has(batch.id), room.replay));
1260
+ if (requeue.kind === 'refused')
1261
+ return requeue;
1262
+ const foreign = [...requeue.withdrawn.keys()].some((origin) => {
1263
+ const writer = room.queue.find((batch) => batch.id === origin)?.writtenBy;
1264
+ return writer?.app !== ask.writtenBy?.app || writer?.release !== ask.writtenBy?.release;
1265
+ });
1266
+ if (foreign)
1267
+ return unqueueable([...requeue.queued].at(-1));
1268
+ const input = {
1269
+ local: ask.local,
1270
+ baseRoomVersion: record.roomVersion,
1271
+ composition: room.composition,
1272
+ rule: room.rule,
1273
+ providerLimits: ask.providerLimits,
1274
+ handle: record.declaration.handle,
1275
+ visible,
1276
+ folders: room.folders,
1277
+ changes,
1278
+ bases: parts.flatMap(({ bases }) => bases),
1279
+ queued: queuedDocuments(room.queue),
1280
+ writtenBy: ask.writtenBy,
1281
+ unmodeled: !room.modelled,
1282
+ validate: room.validate,
1283
+ };
1284
+ const rewritten = requeue.withdrawn.size > 0;
1285
+ if (rewritten) {
1286
+ const whole = await batching({
1287
+ ...input,
1288
+ providerLimits: [],
1289
+ queued: new Set([...input.queued, ...requeue.queued]),
1290
+ });
1291
+ if (whole.kind === 'refused')
1292
+ return whole;
1293
+ }
1294
+ const own = !rewritten
1295
+ ? await batching(input)
1296
+ : requeue.changes.length === 0
1297
+ ? null
1298
+ : await batching({
1299
+ ...input,
1300
+ visible: await stepped(room.replay(requeue.queue)),
1301
+ changes: requeue.changes,
1302
+ queued: queuedDocuments(requeue.queue),
1303
+ });
1304
+ if (own?.kind === 'refused')
1305
+ return own;
1306
+ const queue = own === null ? requeue.queue : [...requeue.queue, own.batch];
1307
+ const saved = new Map();
1308
+ for (const [origin, rebuilt] of requeue.withdrawn) {
1309
+ if (rebuilt === null)
1310
+ continue;
1311
+ const limited = await stepped(limiting(queue, rebuilt, ask.providerLimits, room.replay));
1312
+ if (limited !== null)
1313
+ return { kind: 'refused', refusal: limited };
1314
+ saved.set(rebuilt.id, withdrawnDrafts(room.saved.get(origin) ?? [], changes));
1315
+ }
1316
+ if (own !== null) {
1317
+ saved.set(own.batch.id, ask.saved.filter(({ path }) => !requeue.queued.has(path)));
1318
+ }
1319
+ const carried = carriedDrafts(ask, room, room);
1320
+ return {
1321
+ kind: 'planned',
1322
+ identifier: own?.batch.id ??
1323
+ [...requeue.withdrawn].map(([origin, rebuilt]) => rebuilt?.id ?? origin).at(-1),
1324
+ rooms: [
1325
+ {
1326
+ locator: record.locator,
1327
+ queue,
1328
+ saved,
1329
+ consumed: [...ask.consumed, ...carried.keys()],
1330
+ carried: [...carried.values()],
1331
+ },
1332
+ ],
1333
+ };
1334
+ }
1335
+ export function destination(locator, saved) {
1336
+ const into = saved.find(({ parts }) => parts.move?.room !== undefined && parts.move.room !== locator);
1337
+ return into?.parts.move?.room ?? null;
1168
1338
  }
1169
- export function buildBatch(input) {
1170
- return batching(input);
1339
+ export function planSave(ask, room, into) {
1340
+ return into === undefined ? plannedWithin(ask, room) : plannedMove(ask, room, into);
1171
1341
  }