@plurnk/plurnk-service 1.6.0 → 1.6.1

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 (124) hide show
  1. package/.env.defaults +7 -21
  2. package/INSTALL.md +18 -17
  3. package/README.md +1 -1
  4. package/SPEC.md +125 -115
  5. package/dist/build-info.json +1 -1
  6. package/dist/core/ChannelWrite.sql +3 -3
  7. package/dist/core/CoreSchemeServices.d.ts +1 -1
  8. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  9. package/dist/core/CoreSchemeServices.js +1 -1
  10. package/dist/core/CoreSchemeServices.js.map +1 -1
  11. package/dist/core/Dispatcher.d.ts +2 -2
  12. package/dist/core/Dispatcher.d.ts.map +1 -1
  13. package/dist/core/Dispatcher.js +79 -1115
  14. package/dist/core/Dispatcher.js.map +1 -1
  15. package/dist/core/Engine.d.ts +9 -33
  16. package/dist/core/Engine.d.ts.map +1 -1
  17. package/dist/core/Engine.js +58 -1656
  18. package/dist/core/Engine.js.map +1 -1
  19. package/dist/core/Engine.sql +46 -32
  20. package/dist/core/LogBody.d.ts +1 -0
  21. package/dist/core/LogBody.d.ts.map +1 -1
  22. package/dist/core/LogBody.js +4 -0
  23. package/dist/core/LogBody.js.map +1 -1
  24. package/dist/core/ModelCall.d.ts +2 -2
  25. package/dist/core/ModelCall.d.ts.map +1 -1
  26. package/dist/core/ModelCall.js +4 -2
  27. package/dist/core/ModelCall.js.map +1 -1
  28. package/dist/core/PacketBuilder.d.ts +11 -22
  29. package/dist/core/PacketBuilder.d.ts.map +1 -1
  30. package/dist/core/PacketBuilder.js +66 -168
  31. package/dist/core/PacketBuilder.js.map +1 -1
  32. package/dist/core/ProblemLog.d.ts +1 -1
  33. package/dist/core/ProblemLog.d.ts.map +1 -1
  34. package/dist/core/ProblemLog.js +14 -3
  35. package/dist/core/ProblemLog.js.map +1 -1
  36. package/dist/core/ProposalLifecycle.d.ts +2 -2
  37. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  38. package/dist/core/ProposalLifecycle.js +14 -5
  39. package/dist/core/ProposalLifecycle.js.map +1 -1
  40. package/dist/core/ProviderInstantiate.js +2 -2
  41. package/dist/core/ProviderInstantiate.js.map +1 -1
  42. package/dist/core/ResourceMutations.d.ts +73 -0
  43. package/dist/core/ResourceMutations.d.ts.map +1 -0
  44. package/dist/core/ResourceMutations.js +1142 -0
  45. package/dist/core/ResourceMutations.js.map +1 -0
  46. package/dist/core/SchemeRegistry.js +3 -3
  47. package/dist/core/SchemeRegistry.js.map +1 -1
  48. package/dist/core/StoredPacket.d.ts +2 -2
  49. package/dist/core/StoredPacket.js +7 -7
  50. package/dist/core/TurnRunner.d.ts +72 -0
  51. package/dist/core/TurnRunner.d.ts.map +1 -0
  52. package/dist/core/TurnRunner.js +1730 -0
  53. package/dist/core/TurnRunner.js.map +1 -0
  54. package/dist/core/caps/DbChannelCaps.js +4 -4
  55. package/dist/core/caps/DbChannelCaps.js.map +1 -1
  56. package/dist/core/content-hash.js +2 -2
  57. package/dist/core/content-hash.js.map +1 -1
  58. package/dist/core/content-weight.d.ts +2 -0
  59. package/dist/core/content-weight.d.ts.map +1 -0
  60. package/dist/core/content-weight.js +4 -0
  61. package/dist/core/content-weight.js.map +1 -0
  62. package/dist/core/content_weight.d.ts +4 -0
  63. package/dist/core/content_weight.d.ts.map +1 -0
  64. package/dist/core/content_weight.js +6 -0
  65. package/dist/core/content_weight.js.map +1 -0
  66. package/dist/core/fork.sql +5 -5
  67. package/dist/core/packet-wire.d.ts +4 -4
  68. package/dist/core/packet-wire.d.ts.map +1 -1
  69. package/dist/core/packet-wire.js +21 -21
  70. package/dist/core/packet-wire.js.map +1 -1
  71. package/dist/core/scheme-types.d.ts +1 -1
  72. package/dist/core/scheme-types.d.ts.map +1 -1
  73. package/dist/digest/Digest.js +1 -1
  74. package/dist/digest/Digest.js.map +1 -1
  75. package/dist/schemes/Exec.d.ts.map +1 -1
  76. package/dist/schemes/Exec.js +26 -10
  77. package/dist/schemes/Exec.js.map +1 -1
  78. package/dist/schemes/ExecOutputScheme.js +1 -1
  79. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  80. package/dist/schemes/File.d.ts.map +1 -1
  81. package/dist/schemes/File.js +1 -1
  82. package/dist/schemes/File.js.map +1 -1
  83. package/dist/schemes/Log.d.ts.map +1 -1
  84. package/dist/schemes/Log.js +2 -2
  85. package/dist/schemes/Log.sql +2 -2
  86. package/dist/schemes/Worker.js +2 -2
  87. package/dist/schemes/Worker.js.map +1 -1
  88. package/dist/schemes/_entry-crud.js +4 -4
  89. package/dist/schemes/_entry-crud.js.map +1 -1
  90. package/dist/schemes/_entry-crud.sql +2 -2
  91. package/dist/schemes/_entry-find.d.ts +5 -5
  92. package/dist/schemes/_entry-find.d.ts.map +1 -1
  93. package/dist/schemes/_entry-find.js +29 -28
  94. package/dist/schemes/_entry-find.js.map +1 -1
  95. package/dist/schemes/_entry-manifest.js +4 -4
  96. package/dist/schemes/_entry-manifest.js.map +1 -1
  97. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  98. package/dist/schemes/_entry-ops.js +3 -3
  99. package/dist/schemes/_entry-ops.js.map +1 -1
  100. package/dist/schemes/_entry-ops.sql +3 -3
  101. package/dist/server/Daemon.d.ts +7 -42
  102. package/dist/server/Daemon.d.ts.map +1 -1
  103. package/dist/server/Daemon.js +124 -887
  104. package/dist/server/Daemon.js.map +1 -1
  105. package/dist/server/DrainSupervisor.d.ts +117 -0
  106. package/dist/server/DrainSupervisor.d.ts.map +1 -0
  107. package/dist/server/DrainSupervisor.js +802 -0
  108. package/dist/server/DrainSupervisor.js.map +1 -0
  109. package/dist/server/lifecycle-recovery.sql +3 -0
  110. package/dist/server/logEntry.d.ts +1 -1
  111. package/dist/server/logEntry.js +1 -1
  112. package/dist/server/seam-entry-read.sql +2 -2
  113. package/dist/service.js +1 -1
  114. package/dist/service.js.map +1 -1
  115. package/migrations/001_schema.sql +20 -10
  116. package/package.json +33 -33
  117. package/dist/core/ruler_count.d.ts +0 -4
  118. package/dist/core/ruler_count.d.ts.map +0 -1
  119. package/dist/core/ruler_count.js +0 -7
  120. package/dist/core/ruler_count.js.map +0 -1
  121. package/dist/core/token-ruler.d.ts +0 -2
  122. package/dist/core/token-ruler.d.ts.map +0 -1
  123. package/dist/core/token-ruler.js +0 -5
  124. package/dist/core/token-ruler.js.map +0 -1
@@ -0,0 +1,1142 @@
1
+ var _a;
2
+ import { InvalidOperationResultError, } from "@plurnk/plurnk-schemes";
3
+ import { entryPathnameOf, renderAddress, renderTarget, schemeNameOf } from "./plurnk-uri.js";
4
+ import { assertEditBatchReceipt, assertEditReceipt, assertResourceEffects, EditCollision, editReceipt, LineAnchors, LineMarkerOps, MimetypeBinary, PathMimetype, projectEditReceipt, } from "../content/index.js";
5
+ import DbProjectionCaps from "./caps/DbProjectionCaps.js";
6
+ import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
7
+ import EntryCrud from "../schemes/_entry-crud.js";
8
+ import Results from "./results.js";
9
+ // Owns EDIT batch state and COPY/MOVE resource mutation composition.
10
+ // Dispatcher retains admission, generic scheme routing, proposal lifecycle, and journaling.
11
+ class ResourceMutations {
12
+ static #failure(code, status, detail, fields = {}, extensions = {}) {
13
+ return Results.failure("engine:dispatcher", code, status, detail, fields, extensions);
14
+ }
15
+ #schemes;
16
+ #liveSubscriptions;
17
+ #run;
18
+ #checkWritable;
19
+ #checkFlagsGate;
20
+ #editTargetIdentity;
21
+ #canonicalFilePath;
22
+ #prepareDataRepresentation;
23
+ #readEntry;
24
+ #writeEntry;
25
+ #deleteChannel;
26
+ #applyProposal;
27
+ #preparedEdits = new WeakMap();
28
+ constructor({ schemes, liveSubscriptions, run, checkWritable, checkFlagsGate, editTargetIdentity, canonicalFilePath, prepareDataRepresentation, readEntry, writeEntry, deleteChannel, applyProposal, }) {
29
+ this.#schemes = schemes;
30
+ this.#liveSubscriptions = liveSubscriptions;
31
+ this.#run = run;
32
+ this.#checkWritable = checkWritable;
33
+ this.#checkFlagsGate = checkFlagsGate;
34
+ this.#editTargetIdentity = editTargetIdentity;
35
+ this.#canonicalFilePath = canonicalFilePath;
36
+ this.#prepareDataRepresentation = prepareDataRepresentation;
37
+ this.#readEntry = readEntry;
38
+ this.#writeEntry = writeEntry;
39
+ this.#deleteChannel = deleteChannel;
40
+ this.#applyProposal = applyProposal;
41
+ }
42
+ async #resolveEditAnchors(statements, identity, schemeName, manifest, ctx) {
43
+ const anchored = statements.filter(({ lineMarker }) => LineAnchors.hasAnchor(lineMarker));
44
+ if (anchored.length === 0) {
45
+ return { statements: statements, precondition: null };
46
+ }
47
+ if (manifest.textEditScopes !== true || !manifest.writableBy.includes("model")) {
48
+ return {
49
+ result: _a.#failure("line-anchor-unsupported", 400, `Scheme '${schemeName}' does not support textual EDIT scopes.`, {}, {
50
+ scheme: schemeName,
51
+ operation: "EDIT",
52
+ recovery: "Remove the scope and submit the scheme's complete editable unit.",
53
+ retryable: false,
54
+ }),
55
+ };
56
+ }
57
+ if (identity === null) {
58
+ return {
59
+ result: _a.#failure("edit-target-required", 400, "A line-anchored EDIT requires a target resource.", {}, { recovery: "Provide the target that rendered the line anchor.", retryable: false }),
60
+ };
61
+ }
62
+ const first = statements[0];
63
+ if (first === undefined)
64
+ return { statements: [], precondition: null };
65
+ const current = await this.#run(schemeName, {
66
+ op: "READ",
67
+ suffix: first.suffix,
68
+ signal: null,
69
+ target: first.target,
70
+ lineMarker: { marks: [1, -1] },
71
+ body: null,
72
+ position: first.position,
73
+ }, ctx);
74
+ if (current.status === 204 || current.status === 404) {
75
+ return { result: EditCollision.result(identity) };
76
+ }
77
+ if (current.status >= 300) {
78
+ return {
79
+ result: _a.#failure("line-anchor-validation-failed", current.status, `EDIT could not validate its line anchor at ${identity}: ${current.problem?.detail ?? `READ returned ${current.status}`}`, {}, {
80
+ target: identity,
81
+ upstreamStatus: current.status,
82
+ stage: "mutation-precondition",
83
+ retryable: false,
84
+ }),
85
+ };
86
+ }
87
+ if (current.status !== 200) {
88
+ return { result: EditCollision.result(identity) };
89
+ }
90
+ const content = current.content;
91
+ if (typeof content !== "string") {
92
+ throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ ${current.status} without textual content while validating an EDIT anchor.`);
93
+ }
94
+ const lineAnchors = current.lineAnchors;
95
+ const lineAnchorIdentity = current.lineAnchorIdentity;
96
+ try {
97
+ LineAnchors.assertProjection(content, lineAnchors);
98
+ if (typeof lineAnchorIdentity !== "string" || lineAnchorIdentity.length === 0) {
99
+ throw new TypeError("READ line anchors require their canonical derivation identity.");
100
+ }
101
+ }
102
+ catch (cause) {
103
+ throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ 200 without its core-owned line-anchor projection.`, { cause });
104
+ }
105
+ const resolved = [];
106
+ const checks = [];
107
+ for (const statement of statements) {
108
+ if (statement.lineMarker === null || !LineAnchors.hasAnchor(statement.lineMarker)) {
109
+ resolved.push(statement);
110
+ continue;
111
+ }
112
+ const resolution = LineAnchors.resolve(lineAnchors, statement.lineMarker);
113
+ if (!resolution.ok) {
114
+ const { anchor, kind } = resolution.failure;
115
+ const invalid = kind === "invalid";
116
+ if (!invalid)
117
+ return { result: EditCollision.result(lineAnchorIdentity) };
118
+ return {
119
+ result: _a.#failure("line-anchor-invalid", 400, `${anchor} is not valid in that EDIT line-coordinate position.`, {}, {
120
+ anchor,
121
+ target: identity,
122
+ recovery: "Use anchors only where the EDIT scope denotes a line.",
123
+ retryable: false,
124
+ }),
125
+ };
126
+ }
127
+ for (const [index, anchor] of statement.lineMarker.marks.entries()) {
128
+ if (typeof anchor !== "string")
129
+ continue;
130
+ const line = resolution.marker.marks[index];
131
+ if (typeof line !== "number") {
132
+ throw new InvalidOperationResultError("An EDIT line anchor did not lower to a numeric line.");
133
+ }
134
+ checks.push({ anchor, line });
135
+ }
136
+ resolved.push({ ...statement, lineMarker: resolution.marker });
137
+ }
138
+ const uniqueChecks = [...new Map(checks.map((check) => [`${check.anchor}:${check.line}`, check])).values()];
139
+ return {
140
+ statements: resolved,
141
+ precondition: { identity: lineAnchorIdentity, checks: uniqueChecks },
142
+ };
143
+ }
144
+ async prepareEditBatches(statements, context, schemeCtx) {
145
+ const { workspaceId, loopId, origin } = context;
146
+ const groups = new Map();
147
+ for (const statement of statements) {
148
+ const { key, identity } = await this.#editTargetIdentity(statement, workspaceId);
149
+ const group = groups.get(key);
150
+ if (group === undefined)
151
+ groups.set(key, {
152
+ identity,
153
+ statements: [statement],
154
+ });
155
+ else
156
+ group.statements.push(statement);
157
+ }
158
+ for (const preparedGroup of groups.values()) {
159
+ const group = preparedGroup.statements;
160
+ const first = group[0];
161
+ const schemeName = schemeNameOf(first.target);
162
+ let initial;
163
+ let denial = group.map((statement) => this.#checkWritable(statement, origin)).find((result) => result !== null) ?? null;
164
+ if (denial === null) {
165
+ for (const statement of group) {
166
+ denial = await this.#checkFlagsGate(statement, loopId);
167
+ if (denial !== null)
168
+ break;
169
+ }
170
+ }
171
+ if (denial !== null) {
172
+ initial = denial;
173
+ }
174
+ else if (schemeName === null) {
175
+ initial = _a.#failure("target-required", 400, "EDIT requires a target scheme.", {}, { retryable: false });
176
+ }
177
+ else {
178
+ const handler = this.#schemes.get(schemeName);
179
+ const method = handler?.editBatch;
180
+ const manifest = this.#schemes.manifestFor(schemeName);
181
+ if (typeof method !== "function" || manifest === undefined) {
182
+ initial = _a.#failure("operation-not-implemented", 501, `Scheme '${schemeName}' does not implement EDIT batches.`, {}, {
183
+ scheme: schemeName,
184
+ operation: "EDIT",
185
+ retryable: false,
186
+ });
187
+ }
188
+ else {
189
+ try {
190
+ const resolved = await this.#resolveEditAnchors(group, preparedGroup.identity, schemeName, manifest, schemeCtx);
191
+ if ("result" in resolved) {
192
+ initial = resolved.result;
193
+ }
194
+ else {
195
+ const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
196
+ const publishedChannel = first.target?.kind === "url"
197
+ ? first.target.fragment ?? manifest.defaultChannel
198
+ : manifest.defaultChannel;
199
+ initial = Results.assert(await method.call(handler, resolved.statements, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, {
200
+ publishedChannel,
201
+ editPrecondition: resolved.precondition,
202
+ })));
203
+ }
204
+ }
205
+ catch (err) {
206
+ if (err instanceof InvalidOperationResultError)
207
+ throw err;
208
+ console.error(`Scheme '${schemeName}' EDIT batch threw outside its operation result contract:`, err);
209
+ initial = _a.#failure("scheme-handler-threw", 500, `The '${schemeName}' scheme did not produce an EDIT result.`, {}, {
210
+ stage: "scheme-dispatch",
211
+ scheme: schemeName,
212
+ operation: "EDIT",
213
+ });
214
+ }
215
+ }
216
+ }
217
+ let resolveSettled;
218
+ const settled = new Promise((resolve) => { resolveSettled = resolve; });
219
+ const candidate = initial.editReceipt;
220
+ const expectedNormalizations = group.filter(({ lineMarker }) => lineMarker?.marks.length === 3).length;
221
+ if (initial.status < 400
222
+ && (initial.scopeNormalizations?.length ?? 0) !== expectedNormalizations) {
223
+ throw new InvalidOperationResultError(`EDIT batch normalized ${initial.scopeNormalizations?.length ?? 0} scope(s), expected ${expectedNormalizations}.`);
224
+ }
225
+ const batch = {
226
+ initial,
227
+ settled,
228
+ aggregate: candidate === undefined || candidate === null
229
+ ? undefined
230
+ : assertEditBatchReceipt(candidate),
231
+ settle: resolveSettled,
232
+ };
233
+ let normalizationIndex = 0;
234
+ for (const [index, statement] of group.entries()) {
235
+ const ownsNormalization = statement.lineMarker?.marks.length === 3 && initial.status < 400;
236
+ this.#preparedEdits.set(statement, {
237
+ first: index === 0,
238
+ index,
239
+ normalizationIndex: ownsNormalization ? normalizationIndex++ : null,
240
+ batch,
241
+ });
242
+ }
243
+ }
244
+ }
245
+ preparedEditResult(statement) {
246
+ const prepared = this.#preparedEdits.get(statement);
247
+ if (prepared === undefined) {
248
+ throw new InvalidOperationResultError("EDIT reached dispatch without a prepared resource batch.");
249
+ }
250
+ return this.#projectPreparedEdit(prepared);
251
+ }
252
+ async #projectPreparedEdit(prepared) {
253
+ const settled = prepared.first
254
+ ? prepared.batch.initial
255
+ : await prepared.batch.settled;
256
+ const normalization = prepared.normalizationIndex === null
257
+ ? undefined
258
+ : settled.scopeNormalizations?.[prepared.normalizationIndex];
259
+ const { scopeNormalizations: _scopeNormalizations, ...projectedSettlement } = settled;
260
+ const statementResult = normalization === undefined
261
+ ? projectedSettlement
262
+ : { ...projectedSettlement, scopeNormalizations: [normalization] };
263
+ const aggregate = prepared.batch.aggregate;
264
+ if (aggregate === undefined)
265
+ return statementResult;
266
+ const receipt = assertEditBatchReceipt(aggregate);
267
+ const { editReceipt: _editReceipt, ...withoutAggregate } = statementResult;
268
+ return {
269
+ ...withoutAggregate,
270
+ receipt: projectEditReceipt(receipt, prepared.index),
271
+ };
272
+ }
273
+ settleEdit(statement, result) {
274
+ const prepared = this.#preparedEdits.get(statement);
275
+ if (prepared?.first !== true)
276
+ return;
277
+ const normalizations = prepared.batch.initial.scopeNormalizations;
278
+ prepared.batch.settle(normalizations === undefined
279
+ ? result
280
+ : Results.assert({ ...result, scopeNormalizations: normalizations }));
281
+ }
282
+ async handleCopy(statement, ctx) {
283
+ if (statement.target === null) {
284
+ return _a.#failure("copy-source-required", 400, "COPY requires a source path.", {}, { retryable: false });
285
+ }
286
+ if (statement.body === null) {
287
+ return _a.#failure("copy-destination-required", 400, "COPY requires a destination.", {}, { retryable: false });
288
+ }
289
+ return this.#copyOrchestration({
290
+ statement,
291
+ source: {
292
+ target: statement.target,
293
+ lineMarker: statement.lineMarker,
294
+ },
295
+ destination: statement.body,
296
+ ctx,
297
+ });
298
+ }
299
+ async handleMove(statement, ctx) {
300
+ if (statement.target === null) {
301
+ return _a.#failure("move-source-required", 400, "MOVE requires a source path.", {}, { retryable: false });
302
+ }
303
+ // MOVE is relocation only - deletion is KILL's job ({§move}, {§move-dev-null-not-special}). The /dev/null
304
+ // and null-body delete-by-MOVE has no alternate meaning.
305
+ if (statement.body === null) {
306
+ return _a.#failure("move-destination-required", 400, "MOVE requires a destination.", {}, {
307
+ recovery: "Use KILL when the intended operation is deletion.",
308
+ retryable: false,
309
+ });
310
+ }
311
+ const sourceMarks = statement.lineMarker?.marks;
312
+ const sourceLineMarker = sourceMarks?.length === 2
313
+ && sourceMarks[0] === 1
314
+ && sourceMarks[1] === -1
315
+ ? null
316
+ : statement.lineMarker;
317
+ return this.#moveOrchestration({
318
+ statement,
319
+ source: {
320
+ target: statement.target,
321
+ // Canonicalize only the execution selection. #writeLog retains
322
+ // the authored marker as operation evidence. {§move-canonical-whole-source}
323
+ lineMarker: sourceLineMarker,
324
+ },
325
+ destination: statement.body,
326
+ ctx,
327
+ });
328
+ }
329
+ static #isDispatchResult(value) {
330
+ return "status" in value;
331
+ }
332
+ async #resolveResourceSelection(selection, ctx) {
333
+ const { target, lineMarker } = selection;
334
+ const scheme = schemeNameOf(target);
335
+ if (scheme === null) {
336
+ return _a.#failure("resource-scheme-required", 400, "COPY and MOVE resources require a scheme.", {}, { retryable: false });
337
+ }
338
+ if (target.kind === "url" && target.scheme === "worker" && (target.hostname ?? "") !== "") {
339
+ return _a.#failure("worker-copy-address-invalid", 400, "COPY and MOVE do not address named worker spaces.", {}, {
340
+ recovery: "Move worker-space content with READ and EDIT.",
341
+ retryable: false,
342
+ });
343
+ }
344
+ const handler = this.#schemes.get(scheme);
345
+ const manifest = this.#schemes.manifestFor(scheme);
346
+ if (handler === undefined || manifest === undefined) {
347
+ return _a.#failure("scheme-not-found", 501, `COPY or MOVE addressed the unregistered scheme '${scheme}'.`, {}, {
348
+ scheme,
349
+ retryable: false,
350
+ });
351
+ }
352
+ if (manifest.category !== "data") {
353
+ return _a.#failure("entry-operation-unsupported", 400, `COPY and MOVE require entry-bearing resources; '${scheme}' is a ${manifest.category} scheme.`, {}, {
354
+ scheme,
355
+ category: manifest.category,
356
+ retryable: false,
357
+ });
358
+ }
359
+ const fragment = target.kind === "url" ? target.fragment : null;
360
+ const channel = fragment ?? manifest.defaultChannel;
361
+ if (channel.length === 0) {
362
+ return _a.#failure("channel-required", 400, `The '${scheme}' scheme has no default channel.`, {}, {
363
+ scheme,
364
+ recovery: "Address a named channel with a URI fragment.",
365
+ retryable: false,
366
+ });
367
+ }
368
+ if (fragment !== null
369
+ && fragment !== manifest.defaultChannel
370
+ && !Object.hasOwn(manifest.channels, fragment)) {
371
+ const availableChannels = [
372
+ ...new Set([manifest.defaultChannel, ...Object.keys(manifest.channels)]),
373
+ ].filter((candidate) => candidate.length > 0);
374
+ return _a.#failure("channel-not-found", 400, `Channel #${fragment} is not declared by the '${scheme}' scheme.`, {}, {
375
+ requestedChannel: fragment,
376
+ availableChannels,
377
+ retryable: false,
378
+ });
379
+ }
380
+ const pathname = entryPathnameOf(target);
381
+ const canonicalFilePath = scheme === "file"
382
+ ? await this.#canonicalFilePath(pathname, ctx.workspaceId)
383
+ : pathname;
384
+ return {
385
+ target,
386
+ lineMarker,
387
+ scheme,
388
+ pathname,
389
+ identityPathname: canonicalFilePath ?? pathname,
390
+ channel,
391
+ manifest,
392
+ };
393
+ }
394
+ #resolveResourceLineMarker(selection, content, operation) {
395
+ if (!LineAnchors.hasAnchor(selection.lineMarker)) {
396
+ return {
397
+ selection: {
398
+ ...selection,
399
+ lineMarker: selection.lineMarker,
400
+ },
401
+ precondition: null,
402
+ };
403
+ }
404
+ const target = this.#resourceAddress(selection);
405
+ if (selection.manifest.textEditScopes !== true || !selection.manifest.writableBy.includes("model")) {
406
+ return {
407
+ result: _a.#failure("line-anchor-unsupported", 400, `The representation at ${target} does not publish line anchors.`, {}, {
408
+ operation,
409
+ target,
410
+ recovery: "Use numeric text coordinates.",
411
+ retryable: false,
412
+ }),
413
+ };
414
+ }
415
+ const resolution = LineAnchors.resolve(LineAnchors.tokens(target, content), selection.lineMarker);
416
+ if (!resolution.ok) {
417
+ if (resolution.failure.kind === "invalid") {
418
+ return {
419
+ result: _a.#failure("line-anchor-invalid", 400, "A line anchor appeared outside a line-coordinate position.", {}, {
420
+ operation,
421
+ target,
422
+ recovery: "Use anchors only for L, SL, or EL; columns remain numeric.",
423
+ retryable: false,
424
+ }),
425
+ };
426
+ }
427
+ return {
428
+ result: _a.#failure("line-anchor-collision", 409, `${operation} coordinates collided with current content at ${target}.`, {}, {
429
+ operation,
430
+ target,
431
+ recovery: `READ ${target} again and retry against its current coordinates.`,
432
+ retryable: true,
433
+ }),
434
+ };
435
+ }
436
+ return {
437
+ selection: { ...selection, lineMarker: resolution.marker },
438
+ precondition: {
439
+ identity: target,
440
+ checks: LineAnchors.checks(selection.lineMarker, resolution.marker),
441
+ },
442
+ };
443
+ }
444
+ async #selectSource(selection, ctx, operation) {
445
+ const handler = this.#schemes.get(selection.scheme);
446
+ if (handler === undefined) {
447
+ throw new InvalidOperationResultError(`Resolved COPY/MOVE source scheme '${selection.scheme}' is no longer registered.`);
448
+ }
449
+ const addressedScheme = selection.target.kind === "url"
450
+ ? selection.target.scheme
451
+ : selection.scheme;
452
+ const schemeCtx = new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, { publishedChannel: selection.channel });
453
+ const prepared = await this.#prepareDataRepresentation({
454
+ target: selection.target,
455
+ routedScheme: selection.scheme,
456
+ handler,
457
+ manifest: selection.manifest,
458
+ ctx,
459
+ schemeCtx,
460
+ publishedChannel: selection.channel,
461
+ });
462
+ if (prepared.result !== null)
463
+ return prepared.result;
464
+ if (prepared.address === null) {
465
+ return _a.#failure("entry-not-found", 404, `No entry exists at ${this.#resourceAddress(selection)}.`, {}, { target: this.#resourceAddress(selection) });
466
+ }
467
+ const storageAddress = prepared.address;
468
+ const read = await EntryCrud.readEntry(storageAddress.pathname, ctx, storageAddress.scheme, storageAddress.ownerId);
469
+ if (read.status >= 400)
470
+ return read;
471
+ if (read.status !== 200 || read.entry === null) {
472
+ throw new InvalidOperationResultError(`The '${selection.scheme}' scheme returned status ${read.status} without a COPY/MOVE source entry.`);
473
+ }
474
+ const selected = read.entry.channels[selection.channel];
475
+ if (selected === undefined) {
476
+ return _a.#failure("channel-not-found", 404, `No channel named #${selection.channel} exists at ${renderAddress(storageAddress.scheme, storageAddress.pathname)}.`, {}, {
477
+ target: renderAddress(storageAddress.scheme, storageAddress.pathname),
478
+ requestedChannel: selection.channel,
479
+ availableChannels: Object.keys(read.entry.channels),
480
+ retryable: false,
481
+ });
482
+ }
483
+ const resolvedMarker = this.#resolveResourceLineMarker(selection, selected.content, operation);
484
+ if ("result" in resolvedMarker)
485
+ return resolvedMarker.result;
486
+ let content = selected.content;
487
+ let scopeNormalizations;
488
+ if (await MimetypeBinary.isBinaryMimetype(selected.mimetype, ctx.mimetypes)) {
489
+ return _a.#failure("binary-source-unsupported", 415, `Channel #${selection.channel} is a binary marker and cannot be copied or moved.`, {}, {
490
+ channel: selection.channel,
491
+ mimetype: selected.mimetype,
492
+ recovery: "Use a source with a readable text projection.",
493
+ retryable: false,
494
+ });
495
+ }
496
+ if (resolvedMarker.selection.lineMarker !== null) {
497
+ const sliced = LineMarkerOps.sliceLinesRaw(content, resolvedMarker.selection.lineMarker);
498
+ if (sliced.status !== 200)
499
+ return Results.assert(sliced);
500
+ content = sliced.text ?? "";
501
+ scopeNormalizations = sliced.scopeNormalizations;
502
+ }
503
+ if (selected.producerResult !== undefined && selected.producerResult.status >= 400) {
504
+ return Results.assert(selected.producerResult);
505
+ }
506
+ return {
507
+ ...resolvedMarker.selection,
508
+ storageAddress,
509
+ content,
510
+ completeContent: selected.content,
511
+ mimetype: selected.mimetype,
512
+ lineAnchorPrecondition: resolvedMarker.precondition,
513
+ ...(scopeNormalizations === undefined ? {} : { scopeNormalizations }),
514
+ };
515
+ }
516
+ static #prependScopeNormalizations(result, scopeNormalizations) {
517
+ if (scopeNormalizations === undefined)
518
+ return result;
519
+ return Results.assert({
520
+ ...result,
521
+ scopeNormalizations: [
522
+ ...scopeNormalizations,
523
+ ...(result.scopeNormalizations ?? []),
524
+ ],
525
+ });
526
+ }
527
+ static #mergeLineAnchorPreconditions(...values) {
528
+ const present = values.filter((value) => value !== null);
529
+ if (present.length === 0)
530
+ return null;
531
+ const identity = present[0].identity;
532
+ if (present.some((value) => value.identity !== identity)) {
533
+ throw new TypeError("Line-anchor preconditions for one edit batch must share a resource identity.");
534
+ }
535
+ const checks = [...new Map(present.flatMap(({ checks: valueChecks }) => valueChecks)
536
+ .map((check) => [`${check.anchor}:${check.line}`, check])).values()];
537
+ return { identity, checks };
538
+ }
539
+ #resourceAddress(selection) {
540
+ const address = renderTarget({
541
+ scheme: selection.scheme === "file" ? null : selection.scheme,
542
+ pathname: selection.scheme === "file"
543
+ ? selection.identityPathname.replace(/^\//, "")
544
+ : selection.identityPathname,
545
+ fragment: selection.channel === selection.manifest.defaultChannel
546
+ ? null
547
+ : selection.channel,
548
+ });
549
+ if (address === null)
550
+ throw new Error("resolved resource selection has no renderable address");
551
+ return address;
552
+ }
553
+ #pendingEffect(selection, action) {
554
+ return {
555
+ target: this.#resourceAddress(selection),
556
+ action,
557
+ };
558
+ }
559
+ static #appliedEffects(result, pending) {
560
+ // {§edit-result-copy-move-effects} — only an applied mutation earns
561
+ // engine-composed effects; native scheme receipts remain internal input.
562
+ const exact = Results.assert(result);
563
+ if (exact.status === 304 || exact.status === 202 || exact.status >= 300)
564
+ return exact;
565
+ if (exact.effects !== undefined) {
566
+ throw new InvalidOperationResultError("A COPY/MOVE mutation result supplied effects before engine composition.");
567
+ }
568
+ const batch = exact.editReceipt === undefined
569
+ ? undefined
570
+ : assertEditBatchReceipt(exact.editReceipt);
571
+ const single = batch === undefined && exact.receipt !== undefined
572
+ ? assertEditReceipt(exact.receipt)
573
+ : undefined;
574
+ const batchSize = batch === undefined
575
+ ? undefined
576
+ : "disposition" in batch
577
+ ? batch.superseded.length
578
+ : batch.effects.length;
579
+ if (batchSize !== undefined && batchSize !== pending.length) {
580
+ throw new InvalidOperationResultError(`COPY/MOVE expected ${pending.length} receipt projections, got ${batchSize}.`);
581
+ }
582
+ if (single !== undefined && pending.length !== 1) {
583
+ throw new InvalidOperationResultError(`COPY/MOVE received one receipt for ${pending.length} resource effects.`);
584
+ }
585
+ let effects;
586
+ if (batch !== undefined && "disposition" in batch) {
587
+ const replacement = pending[0];
588
+ if (replacement === undefined) {
589
+ throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch has no resource effect.");
590
+ }
591
+ if (pending.some(({ target, action }) => target !== replacement.target || action !== replacement.action)) {
592
+ throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch spans incompatible resource effects.");
593
+ }
594
+ effects = [{
595
+ ...replacement,
596
+ receipt: projectEditReceipt(batch, 0),
597
+ }];
598
+ }
599
+ else {
600
+ effects = pending.map((effect, index) => ({
601
+ ...effect,
602
+ ...(batch !== undefined
603
+ ? { receipt: projectEditReceipt(batch, index) }
604
+ : single !== undefined
605
+ ? { receipt: single }
606
+ : {}),
607
+ }));
608
+ }
609
+ assertResourceEffects(effects);
610
+ const { editReceipt: _editReceipt, receipt: _receipt, ...withoutInternalReceipts } = exact;
611
+ return {
612
+ ...withoutInternalReceipts,
613
+ effects,
614
+ };
615
+ }
616
+ // {§copy-move-observation} {§edit-result-copy-move-effects} — scoped
617
+ // COPY/MOVE into an unscoped channel is a text materialization even when
618
+ // CRUD creates the channel wholesale. Carry the receipt through synchronous
619
+ // writes and proposals; the destination scheme owns reviewed output.
620
+ static #withEditMaterialization(result, receipt) {
621
+ const exact = Results.assert(result);
622
+ if (exact.status !== 200 && exact.status !== 201 && exact.status !== 202)
623
+ return exact;
624
+ const materialized = exact.editReceipt === undefined
625
+ ? assertEditBatchReceipt(receipt)
626
+ : assertEditBatchReceipt(exact.editReceipt);
627
+ return {
628
+ ...exact,
629
+ editReceipt: materialized,
630
+ ...(exact.status === 202
631
+ ? {
632
+ attrs: {
633
+ ...exact.attrs,
634
+ editReceipt: materialized,
635
+ },
636
+ }
637
+ : {}),
638
+ };
639
+ }
640
+ #finalizeEffects(result, selection, pending) {
641
+ const routed = this.#withProposalRoute(result, selection);
642
+ if (routed.status !== 202)
643
+ return _a.#appliedEffects(routed, pending);
644
+ return {
645
+ ...routed,
646
+ attrs: {
647
+ ...routed.attrs,
648
+ proposalEffects: pending,
649
+ },
650
+ };
651
+ }
652
+ static #effectsOf(result) {
653
+ return result.effects === undefined
654
+ ? []
655
+ : assertResourceEffects(result.effects);
656
+ }
657
+ static #withCombinedEffects(result, ...additional) {
658
+ const existing = _a.#effectsOf(result);
659
+ const effects = [...existing, ...additional.flat()];
660
+ const { effects: _effects, ...withoutEffects } = result;
661
+ return effects.length === 0
662
+ ? withoutEffects
663
+ : { ...withoutEffects, effects: assertResourceEffects(effects) };
664
+ }
665
+ static #settleProposalEffects(original, settlement) {
666
+ const pending = original.attrs
667
+ ?.proposalEffects;
668
+ if (pending === undefined
669
+ || settlement.resolution.decision !== "accept"
670
+ || settlement.applied === undefined
671
+ || settlement.applied.status >= 300) {
672
+ return settlement;
673
+ }
674
+ const projected = settlement.resolution.result ?? {};
675
+ const aggregate = settlement.applied.editReceipt;
676
+ const applied = _a.#appliedEffects({
677
+ ...projected,
678
+ status: settlement.applied.status,
679
+ ...(aggregate === undefined ? {} : { editReceipt: aggregate }),
680
+ }, pending);
681
+ const { status: _status, body: _body, ...result } = applied;
682
+ const { body: _resolutionBody, ...resolution } = settlement.resolution;
683
+ return {
684
+ ...settlement,
685
+ resolution: {
686
+ ...resolution,
687
+ result,
688
+ },
689
+ };
690
+ }
691
+ #recordEditSettlement(statement, settlement) {
692
+ if (statement.op !== "EDIT")
693
+ return;
694
+ const prepared = this.#preparedEdits.get(statement);
695
+ if (prepared === undefined || !prepared.first) {
696
+ throw new InvalidOperationResultError("An EDIT proposal settled without its prepared batch owner.");
697
+ }
698
+ const { resolution, applied } = settlement;
699
+ if (resolution.decision !== "accept"
700
+ || applied === undefined
701
+ || applied.status >= 300) {
702
+ prepared.batch.aggregate = undefined;
703
+ return;
704
+ }
705
+ if (applied.editReceipt === null) {
706
+ prepared.batch.aggregate = undefined;
707
+ return;
708
+ }
709
+ if (applied.editReceipt !== undefined) {
710
+ prepared.batch.aggregate = assertEditBatchReceipt(applied.editReceipt);
711
+ return;
712
+ }
713
+ if (resolution.body !== undefined)
714
+ prepared.batch.aggregate = undefined;
715
+ }
716
+ static #settlementEffects(settlement) {
717
+ const effects = settlement.resolution.result
718
+ ?.effects;
719
+ return effects === undefined ? [] : assertResourceEffects(effects);
720
+ }
721
+ static #withSettlementEffects(settlement, effects) {
722
+ const projected = (settlement.resolution.result ?? {});
723
+ const { effects: _effects, ...withoutEffects } = projected;
724
+ const result = effects.length === 0
725
+ ? withoutEffects
726
+ : {
727
+ ...withoutEffects,
728
+ effects: assertResourceEffects(effects),
729
+ };
730
+ return {
731
+ ...settlement,
732
+ resolution: {
733
+ ...settlement.resolution,
734
+ result,
735
+ },
736
+ };
737
+ }
738
+ #sameChannel(left, right) {
739
+ return left.scheme === right.scheme
740
+ && left.identityPathname === right.identityPathname
741
+ && left.channel === right.channel;
742
+ }
743
+ #withProposalRoute(result, selection) {
744
+ if (result.status !== 202)
745
+ return result;
746
+ return {
747
+ ...result,
748
+ attrs: {
749
+ ...result.attrs,
750
+ proposalScheme: selection.scheme,
751
+ proposalTarget: {
752
+ scheme: selection.scheme,
753
+ pathname: selection.identityPathname,
754
+ },
755
+ },
756
+ };
757
+ }
758
+ async #invokeEditBatch(selection, edits, ctx, precondition = null) {
759
+ const handler = this.#schemes.get(selection.scheme);
760
+ if (typeof handler?.editBatch !== "function") {
761
+ return _a.#failure("operation-not-implemented", 501, `Scheme '${selection.scheme}' does not implement EDIT batches.`, {}, {
762
+ scheme: selection.scheme,
763
+ operation: "EDIT",
764
+ retryable: false,
765
+ });
766
+ }
767
+ const statements = edits.map(({ marker, body, position }) => ({
768
+ op: "EDIT",
769
+ suffix: "",
770
+ signal: null,
771
+ target: selection.target,
772
+ lineMarker: marker,
773
+ body,
774
+ position,
775
+ }));
776
+ const addressedScheme = selection.target.kind === "url"
777
+ ? selection.target.scheme
778
+ : selection.scheme;
779
+ try {
780
+ const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, {
781
+ publishedChannel: selection.channel,
782
+ editPrecondition: precondition,
783
+ })));
784
+ return this.#withProposalRoute(result, selection);
785
+ }
786
+ catch (err) {
787
+ if (err instanceof InvalidOperationResultError)
788
+ throw err;
789
+ console.error(`Scheme '${selection.scheme}' COPY/MOVE edit threw outside its operation result contract:`, err);
790
+ return _a.#failure("scheme-handler-threw", 500, `The '${selection.scheme}' scheme did not produce a COPY/MOVE edit result.`, {}, {
791
+ stage: "scheme-dispatch",
792
+ scheme: selection.scheme,
793
+ operation: "EDIT",
794
+ });
795
+ }
796
+ }
797
+ async #writeDestination(statement, source, destination, ctx) {
798
+ const existingResult = await this.#readEntry(destination.scheme, destination.pathname, ctx);
799
+ if (existingResult.status >= 400 && existingResult.status !== 404) {
800
+ return existingResult;
801
+ }
802
+ const existing = existingResult.status === 200
803
+ ? existingResult.entry
804
+ : null;
805
+ if (existingResult.status === 200 && existing === null) {
806
+ throw new InvalidOperationResultError(`The '${destination.scheme}' scheme returned 200 without a destination entry.`);
807
+ }
808
+ const destinationChannel = existing?.channels[destination.channel];
809
+ const expectedMimetype = destinationChannel?.mimetype
810
+ ?? await PathMimetype.resolveEntryMimetype(destination.pathname, destination.manifest.channels[destination.channel] ?? source.mimetype, ctx.mimetypes);
811
+ if (source.mimetype !== expectedMimetype) {
812
+ return _a.#failure("mimetype-mismatch", 415, `COPY or MOVE cannot write '${source.mimetype}' into a '${expectedMimetype}' channel.`, {}, {
813
+ channel: destination.channel,
814
+ sourceMimetype: source.mimetype,
815
+ destinationMimetype: expectedMimetype,
816
+ retryable: false,
817
+ });
818
+ }
819
+ const destinationEffect = this.#pendingEffect(destination, destinationChannel === undefined ? "create" : "update");
820
+ if (destination.lineMarker !== null) {
821
+ if (destinationChannel === undefined) {
822
+ return _a.#failure("destination-region-not-found", 404, `A destination region requires an existing #${destination.channel} channel.`, {}, {
823
+ destination: this.#resourceAddress(destination),
824
+ retryable: false,
825
+ });
826
+ }
827
+ if (await MimetypeBinary.isBinaryMimetype(destinationChannel.mimetype, ctx.mimetypes)) {
828
+ return _a.#failure("binary-region-unsupported", 415, `Channel #${destination.channel} is binary and cannot receive a textual region.`, {}, {
829
+ channel: destination.channel,
830
+ mimetype: destinationChannel.mimetype,
831
+ retryable: false,
832
+ });
833
+ }
834
+ const resolvedMarker = this.#resolveResourceLineMarker(destination, destinationChannel.content, statement.op);
835
+ if ("result" in resolvedMarker)
836
+ return resolvedMarker.result;
837
+ const edited = await this.#invokeEditBatch(resolvedMarker.selection, [{
838
+ marker: resolvedMarker.selection.lineMarker,
839
+ body: source.content,
840
+ position: statement.position,
841
+ }], ctx, resolvedMarker.precondition);
842
+ return this.#finalizeEffects(edited, resolvedMarker.selection, [destinationEffect]);
843
+ }
844
+ if (destinationChannel !== undefined
845
+ && destinationChannel.content !== source.content) {
846
+ return _a.#failure("copy-destination-exists", 409, `COPY or MOVE destination ${this.#resourceAddress(destination)} already contains different content.`, {}, {
847
+ destination: this.#resourceAddress(destination),
848
+ retryable: false,
849
+ });
850
+ }
851
+ if (destinationChannel !== undefined)
852
+ return { status: 304 };
853
+ const channels = {
854
+ ...(existing?.channels ?? {}),
855
+ [destination.channel]: {
856
+ content: source.content,
857
+ mimetype: source.mimetype,
858
+ },
859
+ };
860
+ const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels }, ctx);
861
+ const exactWritten = Results.assert(written);
862
+ const parseIssues = exactWritten.status === 200 || exactWritten.status === 201
863
+ ? await new DbProjectionCaps(ctx).parseIssues(source.content, source.mimetype)
864
+ : undefined;
865
+ const materialized = source.lineMarker === null
866
+ || (exactWritten.status !== 200 && exactWritten.status !== 201 && exactWritten.status !== 202)
867
+ ? exactWritten
868
+ : _a.#withEditMaterialization(exactWritten, editReceipt("", source.content, [{
869
+ marker: { marks: [1, -1] },
870
+ body: source.content,
871
+ }], parseIssues));
872
+ return this.#finalizeEffects(materialized, destination, [destinationEffect]);
873
+ }
874
+ async #copyOrchestration({ statement, source, destination, ctx, }) {
875
+ const resolvedSource = await this.#resolveResourceSelection(source, ctx);
876
+ if (_a.#isDispatchResult(resolvedSource))
877
+ return resolvedSource;
878
+ const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
879
+ if (_a.#isDispatchResult(resolvedDestination))
880
+ return resolvedDestination;
881
+ const selected = await this.#selectSource(resolvedSource, ctx, "COPY");
882
+ if (_a.#isDispatchResult(selected))
883
+ return selected;
884
+ const result = await this.#writeDestination(statement, selected, resolvedDestination, ctx);
885
+ return _a.#prependScopeNormalizations(result, selected.scopeNormalizations);
886
+ }
887
+ #deferredMoveSource(source, destination, lineAnchorPrecondition) {
888
+ return {
889
+ target: source.target,
890
+ lineMarker: source.lineMarker,
891
+ scheme: source.scheme,
892
+ pathname: source.pathname,
893
+ channel: source.channel,
894
+ destination: this.#resourceAddress(destination),
895
+ lineAnchorPrecondition,
896
+ };
897
+ }
898
+ #moveFailureAfterDestination(result, destination, destinationEffects) {
899
+ const exact = Results.assert(result);
900
+ if (exact.status < 400) {
901
+ throw new InvalidOperationResultError("A successful MOVE source result was classified as a partial failure.");
902
+ }
903
+ if (exact.problem === undefined) {
904
+ throw new InvalidOperationResultError("A failed MOVE source mutation has no Problem Details.");
905
+ }
906
+ const failed = Results.assert({
907
+ ...exact,
908
+ problem: {
909
+ ...exact.problem,
910
+ operation: "MOVE",
911
+ destinationWritten: true,
912
+ destination,
913
+ },
914
+ });
915
+ return _a.#withCombinedEffects(failed, destinationEffects);
916
+ }
917
+ async #removeMoveSource(statement, source, ctx, lineAnchorPrecondition = null) {
918
+ const effect = this.#pendingEffect(source, source.lineMarker === null ? "delete" : "update");
919
+ if (source.lineMarker === null) {
920
+ const deleted = await this.#deleteChannel(source.scheme, source.pathname, source.channel, ctx);
921
+ return this.#finalizeEffects(Results.assert(deleted), source, [effect]);
922
+ }
923
+ const edited = await this.#invokeEditBatch(source, [{
924
+ marker: source.lineMarker,
925
+ body: "",
926
+ position: statement.position,
927
+ }], ctx, lineAnchorPrecondition);
928
+ return this.#finalizeEffects(edited, source, [effect]);
929
+ }
930
+ async #moveWithinChannel(statement, source, destination, ctx) {
931
+ if (source.lineMarker === null) {
932
+ if (destination.lineMarker !== null) {
933
+ return _a.#failure("move-region-overlap", 409, "MOVE cannot insert a whole channel into itself and then remove that channel.", {}, {
934
+ source: this.#resourceAddress(source),
935
+ destination: this.#resourceAddress(destination),
936
+ retryable: false,
937
+ });
938
+ }
939
+ return this.#writeDestination(statement, source, destination, ctx);
940
+ }
941
+ if (destination.lineMarker === null) {
942
+ return this.#writeDestination(statement, source, destination, ctx);
943
+ }
944
+ const resolvedDestination = this.#resolveResourceLineMarker(destination, source.completeContent, "MOVE");
945
+ if ("result" in resolvedDestination)
946
+ return resolvedDestination.result;
947
+ const precondition = _a.#mergeLineAnchorPreconditions(source.lineAnchorPrecondition, resolvedDestination.precondition);
948
+ const moved = await this.#invokeEditBatch(resolvedDestination.selection, [
949
+ {
950
+ marker: resolvedDestination.selection.lineMarker,
951
+ body: source.content,
952
+ position: statement.position,
953
+ },
954
+ {
955
+ marker: source.lineMarker,
956
+ body: "",
957
+ position: statement.position,
958
+ },
959
+ ], ctx, precondition);
960
+ const effect = this.#pendingEffect(resolvedDestination.selection, "update");
961
+ return this.#finalizeEffects(moved, resolvedDestination.selection, [effect, effect]);
962
+ }
963
+ async #moveOrchestration({ statement, source, destination, ctx, }) {
964
+ const resolvedSource = await this.#resolveResourceSelection(source, ctx);
965
+ if (_a.#isDispatchResult(resolvedSource))
966
+ return resolvedSource;
967
+ const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
968
+ if (_a.#isDispatchResult(resolvedDestination))
969
+ return resolvedDestination;
970
+ const selected = await this.#selectSource(resolvedSource, ctx, "MOVE");
971
+ if (_a.#isDispatchResult(selected))
972
+ return selected;
973
+ if (this.#sameChannel(resolvedSource, resolvedDestination)) {
974
+ const result = await this.#moveWithinChannel(statement, selected, resolvedDestination, ctx);
975
+ return resolvedDestination.lineMarker === null
976
+ ? _a.#prependScopeNormalizations(result, selected.scopeNormalizations)
977
+ : result;
978
+ }
979
+ const destinationResult = _a.#prependScopeNormalizations(await this.#writeDestination(statement, selected, resolvedDestination, ctx), selected.scopeNormalizations);
980
+ if (destinationResult.status >= 400)
981
+ return destinationResult;
982
+ const destinationAddress = this.#resourceAddress(resolvedDestination);
983
+ const destinationEffects = _a.#effectsOf(destinationResult);
984
+ if (destinationResult.status === 202) {
985
+ return {
986
+ ...destinationResult,
987
+ attrs: {
988
+ ...destinationResult.attrs,
989
+ moveSource: this.#deferredMoveSource(selected, resolvedDestination, selected.lineAnchorPrecondition),
990
+ },
991
+ };
992
+ }
993
+ const sourceResult = await this.#removeMoveSource(statement, selected, ctx, selected.lineAnchorPrecondition);
994
+ if (sourceResult.status === 202) {
995
+ return {
996
+ ...sourceResult,
997
+ ...(destinationResult.scopeNormalizations === undefined
998
+ ? {}
999
+ : { scopeNormalizations: destinationResult.scopeNormalizations }),
1000
+ attrs: {
1001
+ ...sourceResult.attrs,
1002
+ moveDestinationWritten: destinationAddress,
1003
+ moveDestinationEffects: destinationEffects,
1004
+ },
1005
+ };
1006
+ }
1007
+ if (sourceResult.status >= 400) {
1008
+ return this.#moveFailureAfterDestination(destinationResult.scopeNormalizations === undefined
1009
+ ? sourceResult
1010
+ : Results.assert({
1011
+ ...sourceResult,
1012
+ scopeNormalizations: destinationResult.scopeNormalizations,
1013
+ }), destinationAddress, destinationEffects);
1014
+ }
1015
+ const base = destinationResult.status === 304
1016
+ ? { ...destinationResult, status: 200 }
1017
+ : destinationResult;
1018
+ return _a.#withCombinedEffects(base, _a.#effectsOf(sourceResult));
1019
+ }
1020
+ async #settleMoveProposal({ statement, result, settlement, ctx, ids, }) {
1021
+ if (statement.op !== "MOVE")
1022
+ return settlement;
1023
+ const attrs = result.attrs;
1024
+ const destinationWritten = attrs?.moveDestinationWritten;
1025
+ if (destinationWritten !== undefined) {
1026
+ const destinationEffects = attrs?.moveDestinationEffects === undefined
1027
+ ? []
1028
+ : assertResourceEffects(attrs.moveDestinationEffects);
1029
+ if (settlement.resolution.decision !== "accept") {
1030
+ const decision = settlement.resolution.decision;
1031
+ return {
1032
+ resolution: settlement.resolution,
1033
+ applied: this.#moveFailureAfterDestination(_a.#failure("move-source-not-applied", decision === "cancel" ? 499 : 409, `The MOVE destination was written, but source removal was ${decision === "cancel" ? "cancelled" : "rejected"}.`, {}, {
1034
+ retryable: false,
1035
+ }), destinationWritten, destinationEffects),
1036
+ };
1037
+ }
1038
+ if (settlement.applied === undefined) {
1039
+ return {
1040
+ resolution: settlement.resolution,
1041
+ applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
1042
+ stage: "proposal-application",
1043
+ retryable: false,
1044
+ }), destinationWritten, destinationEffects),
1045
+ };
1046
+ }
1047
+ if (settlement.applied.status >= 400) {
1048
+ return {
1049
+ resolution: settlement.resolution,
1050
+ applied: this.#moveFailureAfterDestination(settlement.applied, destinationWritten, destinationEffects),
1051
+ };
1052
+ }
1053
+ return _a.#withSettlementEffects(settlement, [
1054
+ ...destinationEffects,
1055
+ ..._a.#settlementEffects(settlement),
1056
+ ]);
1057
+ }
1058
+ const deferred = attrs?.moveSource;
1059
+ if (deferred === undefined
1060
+ || settlement.resolution.decision !== "accept"
1061
+ || (settlement.applied?.status ?? 200) >= 400) {
1062
+ return settlement;
1063
+ }
1064
+ if (settlement.applied === undefined) {
1065
+ return {
1066
+ resolution: settlement.resolution,
1067
+ applied: _a.#failure("proposal-apply-missing", 500, "The destination scheme accepted its MOVE proposal without applying the destination mutation.", {}, {
1068
+ stage: "proposal-application",
1069
+ retryable: false,
1070
+ }),
1071
+ };
1072
+ }
1073
+ const destinationEffects = _a.#settlementEffects(settlement);
1074
+ const resolvedSource = await this.#resolveResourceSelection({
1075
+ target: deferred.target,
1076
+ lineMarker: deferred.lineMarker,
1077
+ }, ctx);
1078
+ if (_a.#isDispatchResult(resolvedSource)) {
1079
+ return {
1080
+ resolution: settlement.resolution,
1081
+ applied: this.#moveFailureAfterDestination(resolvedSource, deferred.destination, destinationEffects),
1082
+ };
1083
+ }
1084
+ if (resolvedSource.scheme !== deferred.scheme
1085
+ || resolvedSource.pathname !== deferred.pathname
1086
+ || resolvedSource.channel !== deferred.channel) {
1087
+ throw new InvalidOperationResultError("A deferred MOVE source no longer resolves to its recorded identity.");
1088
+ }
1089
+ const removed = await this.#removeMoveSource(statement, {
1090
+ ...resolvedSource,
1091
+ lineMarker: resolvedSource.lineMarker,
1092
+ }, ctx, deferred.lineAnchorPrecondition);
1093
+ if (removed.status >= 400) {
1094
+ return {
1095
+ resolution: settlement.resolution,
1096
+ applied: this.#moveFailureAfterDestination(removed, deferred.destination, destinationEffects),
1097
+ };
1098
+ }
1099
+ if (removed.status !== 202) {
1100
+ return _a.#withSettlementEffects(settlement, [
1101
+ ...destinationEffects,
1102
+ ..._a.#effectsOf(removed),
1103
+ ]);
1104
+ }
1105
+ const initialSourceSettlement = await this.#applyProposal(statement, removed, { decision: "accept" }, ids);
1106
+ const sourceSettlement = _a.#settleProposalEffects(removed, initialSourceSettlement);
1107
+ if (sourceSettlement.applied === undefined) {
1108
+ return {
1109
+ resolution: settlement.resolution,
1110
+ applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
1111
+ stage: "proposal-application",
1112
+ retryable: false,
1113
+ }), deferred.destination, destinationEffects),
1114
+ };
1115
+ }
1116
+ if (sourceSettlement.applied.status >= 400) {
1117
+ return {
1118
+ resolution: settlement.resolution,
1119
+ applied: this.#moveFailureAfterDestination(sourceSettlement.applied, deferred.destination, destinationEffects),
1120
+ };
1121
+ }
1122
+ return _a.#withSettlementEffects(settlement, [
1123
+ ...destinationEffects,
1124
+ ..._a.#settlementEffects(sourceSettlement),
1125
+ ]);
1126
+ }
1127
+ async settleProposal({ statement, result, settlement, ctx, ids, }) {
1128
+ const withEffects = _a.#settleProposalEffects(result, settlement);
1129
+ const effective = await this.#settleMoveProposal({
1130
+ statement,
1131
+ result,
1132
+ settlement: withEffects,
1133
+ ctx,
1134
+ ids,
1135
+ });
1136
+ this.#recordEditSettlement(statement, effective);
1137
+ return effective;
1138
+ }
1139
+ }
1140
+ _a = ResourceMutations;
1141
+ export default ResourceMutations;
1142
+ //# sourceMappingURL=ResourceMutations.js.map