@plurnk/plurnk-service 1.5.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 (248) hide show
  1. package/.env.defaults +22 -27
  2. package/INSTALL.md +22 -17
  3. package/README.md +1 -1
  4. package/SPEC.md +505 -362
  5. package/dist/build-info.json +1 -1
  6. package/dist/content/edit-collision.d.ts +5 -0
  7. package/dist/content/edit-collision.d.ts.map +1 -0
  8. package/dist/content/edit-collision.js +14 -0
  9. package/dist/content/edit-collision.js.map +1 -0
  10. package/dist/content/edit-receipt.d.ts +3 -2
  11. package/dist/content/edit-receipt.d.ts.map +1 -1
  12. package/dist/content/edit-receipt.js +58 -13
  13. package/dist/content/edit-receipt.js.map +1 -1
  14. package/dist/content/index.d.ts +4 -1
  15. package/dist/content/index.d.ts.map +1 -1
  16. package/dist/content/index.js +3 -1
  17. package/dist/content/index.js.map +1 -1
  18. package/dist/content/line-anchors.d.ts +41 -0
  19. package/dist/content/line-anchors.d.ts.map +1 -0
  20. package/dist/content/line-anchors.js +191 -0
  21. package/dist/content/line-anchors.js.map +1 -0
  22. package/dist/content/read-projector.d.ts +7 -1
  23. package/dist/content/read-projector.d.ts.map +1 -1
  24. package/dist/content/read-projector.js +46 -10
  25. package/dist/content/read-projector.js.map +1 -1
  26. package/dist/content/read-resolve.js +3 -3
  27. package/dist/content/read-resolve.js.map +1 -1
  28. package/dist/core/BudgetReadout.d.ts.map +1 -1
  29. package/dist/core/BudgetReadout.js +9 -2
  30. package/dist/core/BudgetReadout.js.map +1 -1
  31. package/dist/core/ChannelWrite.js +1 -1
  32. package/dist/core/ChannelWrite.js.map +1 -1
  33. package/dist/core/ChannelWrite.sql +3 -3
  34. package/dist/core/CoreSchemeServices.d.ts +1 -1
  35. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  36. package/dist/core/CoreSchemeServices.js +1 -1
  37. package/dist/core/CoreSchemeServices.js.map +1 -1
  38. package/dist/core/Dispatcher.d.ts +15 -5
  39. package/dist/core/Dispatcher.d.ts.map +1 -1
  40. package/dist/core/Dispatcher.js +220 -978
  41. package/dist/core/Dispatcher.js.map +1 -1
  42. package/dist/core/DurableStatement.d.ts.map +1 -1
  43. package/dist/core/DurableStatement.js +2 -0
  44. package/dist/core/DurableStatement.js.map +1 -1
  45. package/dist/core/Engine.d.ts +18 -43
  46. package/dist/core/Engine.d.ts.map +1 -1
  47. package/dist/core/Engine.js +90 -1554
  48. package/dist/core/Engine.js.map +1 -1
  49. package/dist/core/Engine.sql +192 -112
  50. package/dist/core/ExecutableTools.d.ts +10 -0
  51. package/dist/core/ExecutableTools.d.ts.map +1 -0
  52. package/dist/core/ExecutableTools.js +41 -0
  53. package/dist/core/ExecutableTools.js.map +1 -0
  54. package/dist/core/ExecutorRegistry.d.ts +3 -3
  55. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  56. package/dist/core/ExecutorRegistry.js +2 -2
  57. package/dist/core/ExecutorRegistry.js.map +1 -1
  58. package/dist/core/LogBody.d.ts +3 -0
  59. package/dist/core/LogBody.d.ts.map +1 -1
  60. package/dist/core/LogBody.js +21 -5
  61. package/dist/core/LogBody.js.map +1 -1
  62. package/dist/core/LogEntryProjection.d.ts +14 -0
  63. package/dist/core/LogEntryProjection.d.ts.map +1 -0
  64. package/dist/core/LogEntryProjection.js +38 -0
  65. package/dist/core/LogEntryProjection.js.map +1 -0
  66. package/dist/core/ModelCall.d.ts +27 -0
  67. package/dist/core/ModelCall.d.ts.map +1 -0
  68. package/dist/core/ModelCall.js +127 -0
  69. package/dist/core/ModelCall.js.map +1 -0
  70. package/dist/core/PacketBuilder.d.ts +14 -25
  71. package/dist/core/PacketBuilder.d.ts.map +1 -1
  72. package/dist/core/PacketBuilder.js +170 -244
  73. package/dist/core/PacketBuilder.js.map +1 -1
  74. package/dist/core/ProblemLog.d.ts +1 -1
  75. package/dist/core/ProblemLog.d.ts.map +1 -1
  76. package/dist/core/ProblemLog.js +15 -3
  77. package/dist/core/ProblemLog.js.map +1 -1
  78. package/dist/core/ProposalLifecycle.d.ts +2 -2
  79. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  80. package/dist/core/ProposalLifecycle.js +17 -9
  81. package/dist/core/ProposalLifecycle.js.map +1 -1
  82. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  83. package/dist/core/ProviderInstantiate.js +25 -8
  84. package/dist/core/ProviderInstantiate.js.map +1 -1
  85. package/dist/core/ResourceMutations.d.ts +73 -0
  86. package/dist/core/ResourceMutations.d.ts.map +1 -0
  87. package/dist/core/ResourceMutations.js +1142 -0
  88. package/dist/core/ResourceMutations.js.map +1 -0
  89. package/dist/core/SchemeRegistry.js +10 -10
  90. package/dist/core/SchemeRegistry.js.map +1 -1
  91. package/dist/core/StoredPacket.d.ts +2 -2
  92. package/dist/core/StoredPacket.js +7 -7
  93. package/dist/core/StrikeRail.d.ts +0 -1
  94. package/dist/core/StrikeRail.d.ts.map +1 -1
  95. package/dist/core/StrikeRail.js +6 -4
  96. package/dist/core/StrikeRail.js.map +1 -1
  97. package/dist/core/TurnRunner.d.ts +72 -0
  98. package/dist/core/TurnRunner.d.ts.map +1 -0
  99. package/dist/core/TurnRunner.js +1730 -0
  100. package/dist/core/TurnRunner.js.map +1 -0
  101. package/dist/core/caps/CapsResolve.js +1 -1
  102. package/dist/core/caps/CapsResolve.js.map +1 -1
  103. package/dist/core/caps/DbChannelCaps.js +4 -4
  104. package/dist/core/caps/DbChannelCaps.js.map +1 -1
  105. package/dist/core/caps/DbEntryCaps.d.ts +2 -1
  106. package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
  107. package/dist/core/caps/DbEntryCaps.js +8 -5
  108. package/dist/core/caps/DbEntryCaps.js.map +1 -1
  109. package/dist/core/caps/DbProjectionCaps.d.ts +1 -0
  110. package/dist/core/caps/DbProjectionCaps.d.ts.map +1 -1
  111. package/dist/core/caps/DbProjectionCaps.js +29 -0
  112. package/dist/core/caps/DbProjectionCaps.js.map +1 -1
  113. package/dist/core/caps/SchemeCtxImpl.d.ts +5 -2
  114. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  115. package/dist/core/caps/SchemeCtxImpl.js +6 -4
  116. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  117. package/dist/core/content-hash.js +2 -2
  118. package/dist/core/content-hash.js.map +1 -1
  119. package/dist/core/content-weight.d.ts +2 -0
  120. package/dist/core/content-weight.d.ts.map +1 -0
  121. package/dist/core/content-weight.js +4 -0
  122. package/dist/core/content-weight.js.map +1 -0
  123. package/dist/core/content_weight.d.ts +4 -0
  124. package/dist/core/content_weight.d.ts.map +1 -0
  125. package/dist/core/content_weight.js +6 -0
  126. package/dist/core/content_weight.js.map +1 -0
  127. package/dist/core/fork.d.ts.map +1 -1
  128. package/dist/core/fork.js +23 -7
  129. package/dist/core/fork.js.map +1 -1
  130. package/dist/core/fork.sql +38 -17
  131. package/dist/core/git-membership.d.ts.map +1 -1
  132. package/dist/core/git-membership.js +1 -2
  133. package/dist/core/git-membership.js.map +1 -1
  134. package/dist/core/git-state.js +1 -1
  135. package/dist/core/git-state.js.map +1 -1
  136. package/dist/core/packet-wire.d.ts +6 -3
  137. package/dist/core/packet-wire.d.ts.map +1 -1
  138. package/dist/core/packet-wire.js +257 -99
  139. package/dist/core/packet-wire.js.map +1 -1
  140. package/dist/core/provider-accounting.d.ts +24 -0
  141. package/dist/core/provider-accounting.d.ts.map +1 -0
  142. package/dist/core/provider-accounting.js +76 -0
  143. package/dist/core/provider-accounting.js.map +1 -0
  144. package/dist/core/scheme-types.d.ts +1 -1
  145. package/dist/core/scheme-types.d.ts.map +1 -1
  146. package/dist/core/search-gate.d.ts +2 -2
  147. package/dist/core/search-gate.d.ts.map +1 -1
  148. package/dist/core/search-gate.js +8 -8
  149. package/dist/core/search-gate.js.map +1 -1
  150. package/dist/core/turn-scheduler.js +1 -1
  151. package/dist/core/turn-scheduler.js.map +1 -1
  152. package/dist/digest/Digest.d.ts.map +1 -1
  153. package/dist/digest/Digest.js +221 -110
  154. package/dist/digest/Digest.js.map +1 -1
  155. package/dist/digest/digest.sql +40 -18
  156. package/dist/schemes/Exec.d.ts.map +1 -1
  157. package/dist/schemes/Exec.js +136 -110
  158. package/dist/schemes/Exec.js.map +1 -1
  159. package/dist/schemes/ExecOutputScheme.js +1 -1
  160. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  161. package/dist/schemes/File.d.ts +5 -4
  162. package/dist/schemes/File.d.ts.map +1 -1
  163. package/dist/schemes/File.js +25 -23
  164. package/dist/schemes/File.js.map +1 -1
  165. package/dist/schemes/Log.d.ts +11 -0
  166. package/dist/schemes/Log.d.ts.map +1 -1
  167. package/dist/schemes/Log.js +172 -107
  168. package/dist/schemes/Log.js.map +1 -1
  169. package/dist/schemes/Log.sql +48 -32
  170. package/dist/schemes/Prompt.d.ts +3 -3
  171. package/dist/schemes/Prompt.d.ts.map +1 -1
  172. package/dist/schemes/Prompt.js +4 -1
  173. package/dist/schemes/Prompt.js.map +1 -1
  174. package/dist/schemes/Skill.d.ts +4 -4
  175. package/dist/schemes/Skill.d.ts.map +1 -1
  176. package/dist/schemes/Skill.js +3 -0
  177. package/dist/schemes/Skill.js.map +1 -1
  178. package/dist/schemes/Worker.d.ts +4 -4
  179. package/dist/schemes/Worker.d.ts.map +1 -1
  180. package/dist/schemes/Worker.js +11 -7
  181. package/dist/schemes/Worker.js.map +1 -1
  182. package/dist/schemes/_entry-crud.d.ts +0 -1
  183. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  184. package/dist/schemes/_entry-crud.js +19 -13
  185. package/dist/schemes/_entry-crud.js.map +1 -1
  186. package/dist/schemes/_entry-crud.sql +4 -16
  187. package/dist/schemes/_entry-find.d.ts +17 -16
  188. package/dist/schemes/_entry-find.d.ts.map +1 -1
  189. package/dist/schemes/_entry-find.js +58 -52
  190. package/dist/schemes/_entry-find.js.map +1 -1
  191. package/dist/schemes/_entry-find.sql +2 -21
  192. package/dist/schemes/_entry-graph.sql +1 -1
  193. package/dist/schemes/_entry-manifest.d.ts +5 -17
  194. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  195. package/dist/schemes/_entry-manifest.js +32 -28
  196. package/dist/schemes/_entry-manifest.js.map +1 -1
  197. package/dist/schemes/_entry-ops.d.ts +5 -4
  198. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  199. package/dist/schemes/_entry-ops.js +89 -49
  200. package/dist/schemes/_entry-ops.js.map +1 -1
  201. package/dist/schemes/_entry-ops.sql +30 -4
  202. package/dist/schemes/_entry-send.js +4 -4
  203. package/dist/schemes/_entry-send.js.map +1 -1
  204. package/dist/schemes/_search-index.d.ts.map +1 -1
  205. package/dist/schemes/_search-index.js +10 -2
  206. package/dist/schemes/_search-index.js.map +1 -1
  207. package/dist/schemes/exec-abort.js +2 -2
  208. package/dist/schemes/exec-abort.js.map +1 -1
  209. package/dist/server/Daemon.d.ts +7 -42
  210. package/dist/server/Daemon.d.ts.map +1 -1
  211. package/dist/server/Daemon.js +139 -900
  212. package/dist/server/Daemon.js.map +1 -1
  213. package/dist/server/DrainSupervisor.d.ts +117 -0
  214. package/dist/server/DrainSupervisor.d.ts.map +1 -0
  215. package/dist/server/DrainSupervisor.js +802 -0
  216. package/dist/server/DrainSupervisor.js.map +1 -0
  217. package/dist/server/drain.sql +2 -2
  218. package/dist/server/envelope.d.ts +0 -2
  219. package/dist/server/envelope.d.ts.map +1 -1
  220. package/dist/server/envelope.js.map +1 -1
  221. package/dist/server/envelope.sql +2 -2
  222. package/dist/server/lifecycle-recovery.sql +28 -15
  223. package/dist/server/logEntry.d.ts +2 -1
  224. package/dist/server/logEntry.d.ts.map +1 -1
  225. package/dist/server/logEntry.js +14 -2
  226. package/dist/server/logEntry.js.map +1 -1
  227. package/dist/server/logEntry.sql +5 -1
  228. package/dist/server/seam-entry-read.sql +2 -2
  229. package/dist/service.js +1 -1
  230. package/dist/service.js.map +1 -1
  231. package/migrations/001_schema.sql +622 -216
  232. package/package.json +39 -34
  233. package/dist/core/BudgetOverflow.d.ts +0 -20
  234. package/dist/core/BudgetOverflow.d.ts.map +0 -1
  235. package/dist/core/BudgetOverflow.js +0 -49
  236. package/dist/core/BudgetOverflow.js.map +0 -1
  237. package/dist/core/caps/DbTagCaps.d.ts +0 -10
  238. package/dist/core/caps/DbTagCaps.d.ts.map +0 -1
  239. package/dist/core/caps/DbTagCaps.js +0 -46
  240. package/dist/core/caps/DbTagCaps.js.map +0 -1
  241. package/dist/core/ruler_count.d.ts +0 -4
  242. package/dist/core/ruler_count.d.ts.map +0 -1
  243. package/dist/core/ruler_count.js +0 -7
  244. package/dist/core/ruler_count.js.map +0 -1
  245. package/dist/core/token-ruler.d.ts +0 -2
  246. package/dist/core/token-ruler.d.ts.map +0 -1
  247. package/dist/core/token-ruler.js +0 -5
  248. package/dist/core/token-ruler.js.map +0 -1
@@ -5,11 +5,11 @@ import WorkerControlAddress from "./WorkerControlAddress.js";
5
5
  import { entryPathnameOf, foldAuthorityIntoPath, renderAddress, renderTarget, schemeNameOf } from "./plurnk-uri.js";
6
6
  import Fork from "./fork.js";
7
7
  import WorkerCap from "./worker-cap.js";
8
- import { PathSyntax } from "@plurnk/plurnk-contracts";
8
+ import { PathSyntax, TagSignal } from "@plurnk/plurnk-contracts";
9
9
  import Namespace from "./namespace.js";
10
10
  import LoopFlagsReader from "./LoopFlagsReader.js";
11
11
  import ChannelWrite from "./ChannelWrite.js";
12
- import { assertEditBatchReceipt, assertEditReceipt, assertResourceEffects, editReceipt, LineMarkerOps, MimetypeBinary, PathMimetype, ReadProjector, projectEditReceipt, } from "../content/index.js";
12
+ import { ReadProjector } from "../content/index.js";
13
13
  import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
14
14
  import EntryCrud from "../schemes/_entry-crud.js";
15
15
  import EntryOps from "../schemes/_entry-ops.js";
@@ -23,12 +23,23 @@ import EffectPolicy from "../schemes/EffectPolicy.js";
23
23
  import { CoreSchemeAdapterBase, } from "./CoreSchemeServices.js";
24
24
  import { InvalidOperationResultError, NetworkAddress, } from "@plurnk/plurnk-schemes";
25
25
  import DurableStatement from "./DurableStatement.js";
26
+ import ResourceMutations from "./ResourceMutations.js";
27
+ import LogBody from "./LogBody.js";
26
28
  // SPEC {§scheme-surface}: writer must be in target scheme's manifest.writableBy.
27
29
  // OPEN/FOLD/READ/FIND are not gated — they curate the log or read, never mutating an entry.
28
30
  const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC", "KILL", "FORK", "WORK"]);
29
- // Op dispatch ({§op-methods-op-dispatch}): gates (writableBy, loop flags), the
30
- // engine-owned op orchestrations (COPY/MOVE/KILL/SEND), scheme
31
- // routing, the durable log write, and the proposal pause.
31
+ const assertClassifyingSignal = (statement) => {
32
+ if (statement.op === "FIND"
33
+ || statement.op === "READ"
34
+ || statement.op === "EDIT"
35
+ || statement.op === "COPY"
36
+ || statement.op === "MOVE"
37
+ || statement.op === "BARE") {
38
+ TagSignal.applied(statement.signal);
39
+ }
40
+ };
41
+ // Op dispatch ({§op-methods-op-dispatch}): admission, operation-owner routing,
42
+ // durable log writing, and proposal lifecycle.
32
43
  class Dispatcher {
33
44
  static #failure(code, status, detail, fields = {}, extensions = {}) {
34
45
  return Results.failure("engine:dispatcher", code, status, detail, fields, extensions);
@@ -41,7 +52,7 @@ class Dispatcher {
41
52
  #db;
42
53
  #schemes;
43
54
  #mimetypes;
44
- #tokenize;
55
+ #weighContent;
45
56
  #notices;
46
57
  #proposals;
47
58
  // Boot-discovered runtime executors, late-injected on Engine — thunked.
@@ -55,7 +66,7 @@ class Dispatcher {
55
66
  #branchCompletionGate;
56
67
  #cancelWorker;
57
68
  #cancelDescendants;
58
- // {§send-premature-terminate}/SEND[202]<T> — the engine-owned park-deadline registry (loopId → seconds;
69
+ // {§send-premature-terminate}/SEND signal 202 with scope — the engine-owned park-deadline registry (loopId → seconds;
59
70
  // -1 = indefinite). The dispatcher WRITES at park; the daemon's drain park-exit consumes.
60
71
  #parkDeadlines;
61
72
  #searchGate;
@@ -63,12 +74,12 @@ class Dispatcher {
63
74
  #joinTargets;
64
75
  #liveSubscriptions;
65
76
  #lifecycle;
66
- #preparedEdits = new WeakMap();
67
- constructor({ db, schemes, mimetypes, tokenize, notices, proposals, executors, loopSignal, streamEventNotify, wakeWorkerNotify, injectWorker, branchWorker, branchCompletionGate, cancelWorker, cancelDescendants, searchGate, parkDeadlines, joinTargets, liveSubscriptions }) {
77
+ #resourceMutations;
78
+ constructor({ db, schemes, mimetypes, weigh, notices, proposals, executors, loopSignal, streamEventNotify, wakeWorkerNotify, injectWorker, branchWorker, branchCompletionGate, cancelWorker, cancelDescendants, searchGate, parkDeadlines, joinTargets, liveSubscriptions }) {
68
79
  this.#db = db;
69
80
  this.#schemes = schemes;
70
81
  this.#mimetypes = mimetypes;
71
- this.#tokenize = tokenize;
82
+ this.#weighContent = weigh;
72
83
  this.#notices = notices;
73
84
  this.#proposals = proposals;
74
85
  this.#executors = executors;
@@ -85,6 +96,23 @@ class Dispatcher {
85
96
  this.#joinTargets = joinTargets ?? new Set();
86
97
  this.#liveSubscriptions = liveSubscriptions;
87
98
  this.#lifecycle = new LoopLifecycle(db);
99
+ this.#resourceMutations = new ResourceMutations({
100
+ schemes,
101
+ liveSubscriptions,
102
+ run: (schemeName, statement, ctx) => this.#run(schemeName, statement, ctx),
103
+ checkWritable: (statement, origin) => this.#checkWritable(statement, origin),
104
+ checkFlagsGate: (statement, loopId) => this.#checkFlagsGate(statement, loopId),
105
+ editTargetIdentity: (statement, workspaceId) => this.#editTargetIdentity(statement, workspaceId),
106
+ canonicalFilePath: (pathname, workspaceId) => this.#canonicalFilePath(pathname, workspaceId),
107
+ prepareDataRepresentation: (args) => this.#prepareDataRepresentation({
108
+ ...args,
109
+ handler: args.handler,
110
+ }),
111
+ readEntry: (scheme, pathname, ctx) => this.#readEntry(scheme, pathname, ctx),
112
+ writeEntry: (scheme, pathname, entry, ctx) => this.#writeEntry(scheme, pathname, entry, ctx),
113
+ deleteChannel: (scheme, pathname, channel, ctx) => this.#deleteChannel(scheme, pathname, channel, ctx),
114
+ applyProposal: (statement, result, resolution, ids) => this.#proposals.workerApply(statement, result, resolution, ids),
115
+ });
88
116
  }
89
117
  // workspace → project_root, memoized: {§fs-namespace} fixes the root immutably at
90
118
  // workspace creation, so a process-lifetime cache can never go stale.
@@ -124,7 +152,12 @@ class Dispatcher {
124
152
  status: result.status,
125
153
  entry: result.entry === null
126
154
  ? null
127
- : { channels: { ...result.entry.channels }, tags: [...result.entry.tags] },
155
+ : {
156
+ channels: { ...result.entry.channels },
157
+ ...(result.entry.attributes === undefined
158
+ ? {}
159
+ : { attributes: { ...result.entry.attributes } }),
160
+ },
128
161
  });
129
162
  }
130
163
  async #writeEntry(scheme, pathname, entry, ctx) {
@@ -197,113 +230,35 @@ class Dispatcher {
197
230
  if (key !== null)
198
231
  target.pathname = key;
199
232
  }
200
- async prepareEditBatches(statements, context) {
201
- const { workspaceId, workerId, loopId, turnId, origin } = context;
202
- const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
203
- const groups = new Map();
204
- for (const statement of statements) {
205
- const target = this.#extractTarget(statement.target);
206
- await this.#canonColumns(target, workspaceId);
207
- const key = JSON.stringify([
233
+ async #canonicalFilePath(pathname, workspaceId) {
234
+ return Namespace.canonicalizeSpelling(pathname, await this.#workspaceRoot(workspaceId));
235
+ }
236
+ async #editTargetIdentity(statement, workspaceId) {
237
+ const target = this.#extractTarget(statement.target);
238
+ await this.#canonColumns(target, workspaceId);
239
+ return {
240
+ key: JSON.stringify([
208
241
  schemeNameOf(statement.target),
209
242
  target.scheme,
210
243
  target.hostname,
211
244
  target.pathname,
212
245
  target.fragment,
213
- ]);
214
- const group = groups.get(key);
215
- if (group === undefined)
216
- groups.set(key, [statement]);
217
- else
218
- group.push(statement);
219
- }
220
- for (const group of groups.values()) {
221
- const first = group[0];
222
- const schemeName = schemeNameOf(first.target);
223
- let initial;
224
- let denial = group.map((statement) => this.#checkWritable(statement, origin)).find((result) => result !== null) ?? null;
225
- if (denial === null) {
226
- for (const statement of group) {
227
- denial = await this.#checkFlagsGate(statement, loopId);
228
- if (denial !== null)
229
- break;
230
- }
231
- }
232
- if (denial !== null) {
233
- initial = denial;
234
- }
235
- else if (schemeName === null) {
236
- initial = _a.#failure("target-required", 400, "EDIT requires a target scheme.", {}, { retryable: false });
237
- }
238
- else {
239
- const handler = this.#schemes.get(schemeName);
240
- const method = handler?.editBatch;
241
- const manifest = this.#schemes.manifestFor(schemeName);
242
- if (typeof method !== "function" || manifest === undefined) {
243
- initial = _a.#failure("operation-not-implemented", 501, `Scheme '${schemeName}' does not implement EDIT batches.`, {}, {
244
- scheme: schemeName,
245
- operation: "EDIT",
246
- retryable: false,
247
- });
248
- }
249
- else {
250
- try {
251
- const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
252
- const publishedChannel = first.target?.kind === "url"
253
- ? first.target.fragment ?? manifest.defaultChannel
254
- : manifest.defaultChannel;
255
- initial = Results.assert(await method.call(handler, group, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, { publishedChannel })));
256
- }
257
- catch (err) {
258
- if (err instanceof InvalidOperationResultError)
259
- throw err;
260
- console.error(`Scheme '${schemeName}' EDIT batch threw outside its operation result contract:`, err);
261
- initial = _a.#failure("scheme-handler-threw", 500, `The '${schemeName}' scheme did not produce an EDIT result.`, {}, {
262
- stage: "scheme-dispatch",
263
- scheme: schemeName,
264
- operation: "EDIT",
265
- });
266
- }
267
- }
268
- }
269
- let resolveSettled;
270
- const settled = new Promise((resolve) => { resolveSettled = resolve; });
271
- const candidate = initial.editReceipt;
272
- const expectedNormalizations = group.filter(({ lineMarker }) => lineMarker?.marks.length === 3).length;
273
- if (initial.status < 400
274
- && (initial.scopeNormalizations?.length ?? 0) !== expectedNormalizations) {
275
- throw new InvalidOperationResultError(`EDIT batch normalized ${initial.scopeNormalizations?.length ?? 0} scope(s), expected ${expectedNormalizations}.`);
276
- }
277
- const batch = {
278
- initial,
279
- settled,
280
- aggregate: candidate === undefined || candidate === null
281
- ? undefined
282
- : assertEditBatchReceipt(candidate),
283
- settle: resolveSettled,
284
- };
285
- let normalizationIndex = 0;
286
- for (const [index, statement] of group.entries()) {
287
- const ownsNormalization = statement.lineMarker?.marks.length === 3 && initial.status < 400;
288
- this.#preparedEdits.set(statement, {
289
- first: index === 0,
290
- index,
291
- normalizationIndex: ownsNormalization ? normalizationIndex++ : null,
292
- batch,
293
- });
294
- }
295
- }
246
+ ]),
247
+ identity: renderTarget(target),
248
+ };
249
+ }
250
+ async prepareEditBatches(statements, context) {
251
+ for (const statement of statements)
252
+ assertClassifyingSignal(statement); // {§log-tag-signal}
253
+ const { workspaceId, workerId, loopId, turnId, origin } = context;
254
+ const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
255
+ await this.#resourceMutations.prepareEditBatches(statements, context, schemeCtx);
296
256
  }
297
257
  async dispatch(context) {
258
+ assertClassifyingSignal(context.statement); // {§log-tag-signal}
298
259
  const result = await this.#dispatchOne(context);
299
260
  if (context.statement.op === "EDIT") {
300
- const prepared = this.#preparedEdits.get(context.statement);
301
- if (prepared?.first === true) {
302
- const normalizations = prepared.batch.initial.scopeNormalizations;
303
- prepared.batch.settle(normalizations === undefined
304
- ? result
305
- : Results.assert({ ...result, scopeNormalizations: normalizations }));
306
- }
261
+ this.#resourceMutations.settleEdit(context.statement, result);
307
262
  }
308
263
  return result;
309
264
  }
@@ -311,6 +266,7 @@ class Dispatcher {
311
266
  const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, } = context;
312
267
  const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
313
268
  let result;
269
+ let curationPlan = null;
314
270
  let denial = this.#checkWritable(statement, origin);
315
271
  if (denial === null)
316
272
  denial = await this.#checkFlagsGate(statement, loopId);
@@ -325,49 +281,24 @@ class Dispatcher {
325
281
  // those are system failures.
326
282
  try {
327
283
  if (statement.op === "EDIT") {
328
- const prepared = this.#preparedEdits.get(statement);
329
- if (prepared === undefined) {
330
- throw new InvalidOperationResultError("EDIT reached dispatch without a prepared resource batch.");
331
- }
332
- else {
333
- const settled = prepared.first
334
- ? prepared.batch.initial
335
- : await prepared.batch.settled;
336
- const normalization = prepared.normalizationIndex === null
337
- ? undefined
338
- : settled.scopeNormalizations?.[prepared.normalizationIndex];
339
- const { scopeNormalizations: _scopeNormalizations, ...projectedSettlement } = settled;
340
- const statementResult = normalization === undefined
341
- ? projectedSettlement
342
- : { ...projectedSettlement, scopeNormalizations: [normalization] };
343
- const aggregate = prepared.batch.aggregate;
344
- if (aggregate !== undefined) {
345
- const receipt = assertEditBatchReceipt(aggregate);
346
- const { editReceipt: _editReceipt, ...withoutAggregate } = statementResult;
347
- result = {
348
- ...withoutAggregate,
349
- receipt: projectEditReceipt(receipt, prepared.index),
350
- };
351
- }
352
- else {
353
- result = statementResult;
354
- }
355
- }
284
+ result = await this.#resourceMutations.preparedEditResult(statement);
356
285
  }
357
286
  else if (statement.op === "SEND" && statement.target === null) {
358
287
  result = await this.#handleSendBroadcast(statement, { workspaceId, workerId, loopId, turnId, sequence });
359
288
  }
360
289
  else if (statement.op === "OPEN" || statement.op === "FOLD") {
361
- result = await this.#runLogCuration(statement, schemeCtx);
290
+ const curation = await this.#runLogCuration(statement, schemeCtx);
291
+ result = curation.result;
292
+ curationPlan = curation.plan;
362
293
  }
363
294
  else if (statement.op === "FORK" || statement.op === "WORK") {
364
295
  result = await this.#handleWorkerControl(statement, schemeCtx);
365
296
  }
366
297
  else if (statement.op === "COPY") {
367
- result = await this.#handleCopy(statement, schemeCtx);
298
+ result = await this.#resourceMutations.handleCopy(statement, schemeCtx);
368
299
  }
369
300
  else if (statement.op === "MOVE") {
370
- result = await this.#handleMove(statement, schemeCtx);
301
+ result = await this.#resourceMutations.handleMove(statement, schemeCtx);
371
302
  }
372
303
  else if (statement.op === "KILL") {
373
304
  result = await this.#handleKill(statement, schemeCtx);
@@ -376,10 +307,8 @@ class Dispatcher {
376
307
  result = this.#handlePlan(statement);
377
308
  }
378
309
  else if (statement.op === "EXEC") {
379
- // EXEC's target slot is `cwd`, not a scheme address.
380
- // Per plurnk.md the op routes unconditionally to the
381
- // exec scheme; the scheme handler reads runtime
382
- // (signal), cwd (target), and command (body).
310
+ // EXEC routes unconditionally to its operation owner. The
311
+ // resolved runtime declaration owns body/target semantics.
383
312
  result = await this.#gatedExec(statement, schemeCtx, loopId, turnId);
384
313
  }
385
314
  else {
@@ -406,19 +335,30 @@ class Dispatcher {
406
335
  // {§join-blocking-collect}
407
336
  if (typeof result.awaitWorker === "string")
408
337
  this.#joinTargets.add(loopId);
409
- const logEntryId = await this.#writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin });
338
+ const logEntryId = await this.#writeLog({
339
+ statement,
340
+ result,
341
+ workspaceId,
342
+ workerId,
343
+ loopId,
344
+ turnId,
345
+ sequence,
346
+ origin,
347
+ curationPlan,
348
+ modelCallId: null,
349
+ });
410
350
  // {§search-gate} — register successful searches AFTER #writeLog stamps the runtime
411
351
  // entry's coordinate onto result.attrs.pathname (the gate's dedup serves from it).
412
352
  if (statement.op === "EXEC" && result.status < 400) {
413
353
  const rt = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
414
- const cmd = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
354
+ const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
415
355
  const attrPath = result.attrs?.pathname;
416
356
  if (typeof attrPath === "string")
417
- this.#searchGate?.registerPending(loopId, turnId, rt, cmd, attrPath);
357
+ this.#searchGate?.registerPending(loopId, turnId, rt, body, attrPath);
418
358
  }
419
359
  onDispatch?.(logEntryId);
420
360
  // Proposal lifecycle (SPEC.md {§engine-rails} + {§methods-proposal-resolve}; {§proposal-202-pauses}). When a
421
- // side-effecting op returns status 202 (a broadcast SEND[202] park is model
361
+ // side-effecting op returns status 202 (a broadcast SEND signal 202 park is model
422
362
  // speech, not a proposal — #isProposal), the entry is written
423
363
  // state='proposed'; dispatch then PAUSES on a per-entry waiter until
424
364
  // resolution arrives via Engine.resolveProposal (from a client-interface resume,
@@ -438,15 +378,13 @@ class Dispatcher {
438
378
  }
439
379
  if (autoAccept) {
440
380
  const initialSettlement = await this.#proposals.workerApply(statement, result, { decision: "accept" }, { workspaceId, workerId, loopId, turnId });
441
- const settlement = _a.#settleProposalEffects(result, initialSettlement);
442
- const effective = await this.#settleMoveProposal({
381
+ const effective = await this.#resourceMutations.settleProposal({
443
382
  statement,
444
383
  result,
445
- settlement,
384
+ settlement: initialSettlement,
446
385
  ctx: schemeCtx,
447
386
  ids: { workspaceId, workerId, loopId, turnId },
448
387
  });
449
- this.#recordEditSettlement(statement, effective);
450
388
  return this.#proposals.applyResolution(logEntryId, effective);
451
389
  }
452
390
  // Register the resolution waiter SYNCHRONOUSLY before any await
@@ -475,15 +413,13 @@ class Dispatcher {
475
413
  // preserved: an apply failure keeps its original status and
476
414
  // Problem Details instead of masquerading as a client rejection.
477
415
  const initialSettlement = await this.#proposals.workerApply(statement, result, resolution, { workspaceId, workerId, loopId, turnId });
478
- const settlement = _a.#settleProposalEffects(result, initialSettlement);
479
- const effective = await this.#settleMoveProposal({
416
+ const effective = await this.#resourceMutations.settleProposal({
480
417
  statement,
481
418
  result,
482
- settlement,
419
+ settlement: initialSettlement,
483
420
  ctx: schemeCtx,
484
421
  ids: { workspaceId, workerId, loopId, turnId },
485
422
  });
486
- this.#recordEditSettlement(statement, effective);
487
423
  const post = await this.#proposals.applyResolution(logEntryId, effective);
488
424
  return post;
489
425
  }
@@ -660,7 +596,7 @@ class Dispatcher {
660
596
  wakeWorkerNotify: this.#wakeWorkerNotify,
661
597
  injectWorker: this.#injectWorker,
662
598
  mimetypes: this.#mimetypes,
663
- tokenize: this.#tokenize,
599
+ weigh: this.#weighContent,
664
600
  pushNotice: (notice) => this.#notices.push(workspaceId, loopId, notice),
665
601
  executors: this.#executors(),
666
602
  };
@@ -676,8 +612,8 @@ class Dispatcher {
676
612
  return null;
677
613
  if (statement.op === "SEND" && statement.target === null)
678
614
  return null;
679
- // EXEC's target slot is `cwd`, not a scheme address. The op's
680
- // authority always belongs to the exec scheme regardless of cwd.
615
+ // EXEC's operation authority always belongs to the exec scheme;
616
+ // runtime-specific resource authority is gated separately below.
681
617
  if (statement.op === "EXEC") {
682
618
  return this.#denyIfDisallowed("exec", origin);
683
619
  }
@@ -709,8 +645,8 @@ class Dispatcher {
709
645
  // the prior durable digest and the per-turn cap refuses without execution.
710
646
  async #gatedExec(statement, ctx, loopId, turnId) {
711
647
  const runtime = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
712
- const command = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
713
- const verdict = this.#searchGate?.check(loopId, turnId, runtime, command) ?? { verdict: "pass" };
648
+ const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
649
+ const verdict = this.#searchGate?.check(loopId, turnId, runtime, body) ?? { verdict: "pass" };
714
650
  if (verdict.verdict === "capped") {
715
651
  return _a.#failure("search-limit-reached", 429, `This turn already used its ${verdict.cap} permitted searches.`, {}, {
716
652
  searchLimit: verdict.cap,
@@ -817,19 +753,24 @@ class Dispatcher {
817
753
  if (statement.op === "COPY" || statement.op === "MOVE") {
818
754
  return check(statement.target) ?? check(statement.body?.target ?? null);
819
755
  }
820
- // {§exec-target-routing} — the operation owner and a non-file source
821
- // are independent authorities; local/file targets stay executor-local.
756
+ // {§exec-target-routing} — only a runtime-declared resource target adds
757
+ // source authority. Literal identifiers and path targets stay executor-local.
822
758
  if (statement.op === "EXEC") {
823
759
  const operationDenial = checkScheme("exec");
824
760
  if (operationDenial !== null)
825
761
  return operationDenial;
762
+ const requested = typeof statement.signal === "string" ? statement.signal : "";
763
+ const runtime = requested === "" ? "sh" : requested;
764
+ const targetKind = this.#executors()?.entry(runtime)?.invocation.target?.kind;
765
+ if (targetKind !== "resource")
766
+ return null;
826
767
  const sourceScheme = schemeNameOf(statement.target);
827
768
  return sourceScheme === null || sourceScheme === "file" ? null : checkScheme(sourceScheme);
828
769
  }
829
770
  return check(statement.target);
830
771
  }
831
772
  // Worker control is FORK/WORK (grammar 0.74.55), not COPY — its body
832
- // is the new worker's seed prompt, not a destination path. The COPY gates and #handleCopy
773
+ // is the new worker's seed prompt, not a destination path. The COPY gates and ResourceMutations.handleCopy
833
774
  // branch on this so they never parse the prompt as a dst path.
834
775
  #isWorkerControl(statement) {
835
776
  return statement.op === "FORK" || statement.op === "WORK"; // worker control targets worker://<name> (grammar 0.74.55)
@@ -906,45 +847,6 @@ class Dispatcher {
906
847
  await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: row.id, prompt, flags, parentLoopId: ctx.loopId });
907
848
  return { status: 200, body: name };
908
849
  }
909
- async #handleCopy(statement, ctx) {
910
- if (statement.target === null) {
911
- return _a.#failure("copy-source-required", 400, "COPY requires a source path.", {}, { retryable: false });
912
- }
913
- if (statement.body === null) {
914
- return _a.#failure("copy-destination-required", 400, "COPY requires a destination.", {}, { retryable: false });
915
- }
916
- return this.#copyOrchestration({
917
- statement,
918
- source: {
919
- target: statement.target,
920
- lineMarker: statement.lineMarker,
921
- },
922
- destination: statement.body,
923
- ctx,
924
- });
925
- }
926
- async #handleMove(statement, ctx) {
927
- if (statement.target === null) {
928
- return _a.#failure("move-source-required", 400, "MOVE requires a source path.", {}, { retryable: false });
929
- }
930
- // MOVE is relocation only - deletion is KILL's job ({§move}, {§move-dev-null-not-special}). The /dev/null
931
- // and null-body delete-by-MOVE has no alternate meaning.
932
- if (statement.body === null) {
933
- return _a.#failure("move-destination-required", 400, "MOVE requires a destination.", {}, {
934
- recovery: "Use KILL when the intended operation is deletion.",
935
- retryable: false,
936
- });
937
- }
938
- return this.#moveOrchestration({
939
- statement,
940
- source: {
941
- target: statement.target,
942
- lineMarker: statement.lineMarker,
943
- },
944
- destination: statement.body,
945
- ctx,
946
- });
947
- }
948
850
  // KILL is target-polymorphic. Scheme handlers own the optional numeric code's
949
851
  // meaning; core retains worker and entry dispatch. {§operation-code-polymorphism}
950
852
  async #handleKill(statement, ctx) {
@@ -1014,35 +916,59 @@ class Dispatcher {
1014
916
  // carries the delete target to review (#isProposal fires on 202). Plurnk-internal deletes execute inline.
1015
917
  return this.#deleteEntry(schemeName, entryPathnameOf(path), ctx);
1016
918
  }
1017
- // {§model-entry} — mirror a model emission back as an actionless log row, so
1018
- // the model can inspect and curate its own prior behavior. Ordinary admitted
1019
- // emissions are folded; the bounded invalid-emission lifeline is the one
1020
- // born-open rejected item under {§invalid-emission-attempts} and labels
1021
- // that admission fact structurally.
1022
- // Born FOLDED by default (budget-neutral until OPENed); the turn-0 exemplar passes folded:false
1023
- // (born open — the one worked example the model orients on, thinning the grammar). text/vnd.plurnk.
1024
- async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model", admission = "accepted", reasoningItems }) {
919
+ async #writeActionlessEntry({ verbatim, workerId, loopId, turnId, sequence, origin, kind, folded, modelCallId = null, attrs = {} }) {
920
+ const durableAttrs = { ...attrs, kind };
921
+ const rx = JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" });
1025
922
  const row = await this.#db.engine_insert_log_entry.get({
1026
923
  worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
1027
- origin, source: null, op: null, suffix: "", signal: null,
924
+ origin, source: null, model_call_id: modelCallId,
925
+ op: null, suffix: "", signal: null,
1028
926
  scheme: null, username: null, password: null, hostname: null, port: null,
1029
927
  pathname: null, query: null, fragment: null, lineMarker: null,
1030
928
  tx: "", mimetype_tx: "text/vnd.plurnk",
1031
- rx: JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" }),
929
+ rx,
1032
930
  mimetype_rx: "application/json",
1033
- status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null,
1034
- attrs: JSON.stringify({
1035
- kind: "model_emission",
1036
- ...(admission === "rejected" ? { admission } : {}),
1037
- ...(reasoningItems !== undefined && reasoningItems.length > 0 ? { reasoning: reasoningItems } : {}),
1038
- }),
931
+ status_rx: 200,
932
+ weight: LogBody.weight({
933
+ op: null,
934
+ attrs: durableAttrs,
935
+ tx: "",
936
+ rx,
937
+ mimetypeTx: "text/vnd.plurnk",
938
+ mimetypeRx: "application/json",
939
+ }, this.#weighContent),
940
+ state: "resolved", outcome: null,
941
+ attrs: JSON.stringify(durableAttrs),
1039
942
  });
1040
943
  if (row === undefined)
1041
- throw new Error("Dispatcher.writeModelEntry: insert returned no row");
944
+ throw new Error("Dispatcher.#writeActionlessEntry: insert returned no row");
1042
945
  if (folded)
1043
946
  await this.#db.engine_fold_log_entry.run({ id: row.id });
1044
947
  return row.id;
1045
948
  }
949
+ // {§worker-initialization-entry} — the kernel-authored, born-open worked
950
+ // turn that initializes a worker's log without impersonating model output.
951
+ async writeInitializationEntry({ verbatim, workerId, loopId, turnId, sequence }) {
952
+ return this.#writeActionlessEntry({
953
+ verbatim, workerId, loopId, turnId, sequence,
954
+ origin: "plurnk", kind: "initialization", folded: false,
955
+ });
956
+ }
957
+ // {§model-entry} — mirror a model emission back as an actionless log row, so
958
+ // the model can inspect and curate its own prior behavior. Ordinary admitted
959
+ // emissions are folded; the bounded invalid-emission lifeline is the one
960
+ // born-open rejected item under {§invalid-emission-attempts} and labels
961
+ // that admission fact structurally.
962
+ async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, modelCallId, admission = "accepted", reasoningItems }) {
963
+ return this.#writeActionlessEntry({
964
+ verbatim, workerId, loopId, turnId, sequence,
965
+ origin: "model", kind: "model_emission", folded, modelCallId,
966
+ attrs: {
967
+ ...(admission === "rejected" ? { admission } : {}),
968
+ ...(reasoningItems !== undefined && reasoningItems.length > 0 ? { reasoning: reasoningItems } : {}),
969
+ },
970
+ });
971
+ }
1046
972
  // PLAN — the model's intended-goals op. An ordinary op: dispatched like any
1047
973
  // other, logged, and broadcast to the client as a log entry — but a pure no-op for
1048
974
  // state (PLAN ∉ MUTATING_OPS); its body serializes into the log row's tx, no effect.
@@ -1051,732 +977,24 @@ class Dispatcher {
1051
977
  throw new Error("unreachable");
1052
978
  return { status: 200 };
1053
979
  }
1054
- static #isDispatchResult(value) {
1055
- return "status" in value;
1056
- }
1057
- async #resolveResourceSelection(selection, ctx) {
1058
- const { target, lineMarker } = selection;
1059
- const scheme = schemeNameOf(target);
1060
- if (scheme === null) {
1061
- return _a.#failure("resource-scheme-required", 400, "COPY and MOVE resources require a scheme.", {}, { retryable: false });
1062
- }
1063
- if (target.kind === "url" && target.scheme === "worker" && (target.hostname ?? "") !== "") {
1064
- return _a.#failure("worker-copy-address-invalid", 400, "COPY and MOVE do not address named worker spaces.", {}, {
1065
- recovery: "Move worker-space content with READ and EDIT.",
1066
- retryable: false,
1067
- });
1068
- }
1069
- const handler = this.#schemes.get(scheme);
1070
- const manifest = this.#schemes.manifestFor(scheme);
1071
- if (handler === undefined || manifest === undefined) {
1072
- return _a.#failure("scheme-not-found", 501, `COPY or MOVE addressed the unregistered scheme '${scheme}'.`, {}, {
1073
- scheme,
1074
- retryable: false,
1075
- });
1076
- }
1077
- if (manifest.category !== "data") {
1078
- return _a.#failure("entry-operation-unsupported", 400, `COPY and MOVE require entry-bearing resources; '${scheme}' is a ${manifest.category} scheme.`, {}, {
1079
- scheme,
1080
- category: manifest.category,
1081
- retryable: false,
1082
- });
1083
- }
1084
- const fragment = target.kind === "url" ? target.fragment : null;
1085
- const channel = fragment ?? manifest.defaultChannel;
1086
- if (channel.length === 0) {
1087
- return _a.#failure("channel-required", 400, `The '${scheme}' scheme has no default channel.`, {}, {
1088
- scheme,
1089
- recovery: "Address a named channel with a URI fragment.",
1090
- retryable: false,
1091
- });
1092
- }
1093
- if (fragment !== null
1094
- && fragment !== manifest.defaultChannel
1095
- && !Object.hasOwn(manifest.channels, fragment)) {
1096
- const availableChannels = [
1097
- ...new Set([manifest.defaultChannel, ...Object.keys(manifest.channels)]),
1098
- ].filter((candidate) => candidate.length > 0);
1099
- return _a.#failure("channel-not-found", 400, `Channel #${fragment} is not declared by the '${scheme}' scheme.`, {}, {
1100
- requestedChannel: fragment,
1101
- availableChannels,
1102
- retryable: false,
1103
- });
1104
- }
1105
- const pathname = entryPathnameOf(target);
1106
- const canonicalFilePath = scheme === "file"
1107
- ? Namespace.canonicalizeSpelling(pathname, await this.#workspaceRoot(ctx.workspaceId))
1108
- : pathname;
1109
- return {
1110
- target,
1111
- lineMarker,
1112
- scheme,
1113
- pathname,
1114
- identityPathname: canonicalFilePath ?? pathname,
1115
- channel,
1116
- manifest,
1117
- };
1118
- }
1119
- async #selectSource(selection, ctx) {
1120
- const handler = this.#schemes.get(selection.scheme);
1121
- if (handler === undefined) {
1122
- throw new InvalidOperationResultError(`Resolved COPY/MOVE source scheme '${selection.scheme}' is no longer registered.`);
1123
- }
1124
- const addressedScheme = selection.target.kind === "url"
1125
- ? selection.target.scheme
1126
- : selection.scheme;
1127
- const schemeCtx = new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, { publishedChannel: selection.channel });
1128
- const prepared = await this.#prepareDataRepresentation({
1129
- target: selection.target,
1130
- routedScheme: selection.scheme,
1131
- handler,
1132
- manifest: selection.manifest,
1133
- ctx,
1134
- schemeCtx,
1135
- publishedChannel: selection.channel,
1136
- });
1137
- if (prepared.result !== null)
1138
- return prepared.result;
1139
- if (prepared.address === null) {
1140
- return _a.#failure("entry-not-found", 404, `No entry exists at ${this.#resourceAddress(selection)}.`, {}, { target: this.#resourceAddress(selection) });
1141
- }
1142
- const storageAddress = prepared.address;
1143
- const read = await EntryCrud.readEntry(storageAddress.pathname, ctx, storageAddress.scheme, storageAddress.ownerId);
1144
- if (read.status >= 400)
1145
- return read;
1146
- if (read.status !== 200 || read.entry === null) {
1147
- throw new InvalidOperationResultError(`The '${selection.scheme}' scheme returned status ${read.status} without a COPY/MOVE source entry.`);
1148
- }
1149
- const selected = read.entry.channels[selection.channel];
1150
- if (selected === undefined) {
1151
- return _a.#failure("channel-not-found", 404, `No channel named #${selection.channel} exists at ${renderAddress(storageAddress.scheme, storageAddress.pathname)}.`, {}, {
1152
- target: renderAddress(storageAddress.scheme, storageAddress.pathname),
1153
- requestedChannel: selection.channel,
1154
- availableChannels: Object.keys(read.entry.channels),
1155
- retryable: false,
1156
- });
1157
- }
1158
- let content = selected.content;
1159
- let scopeNormalizations;
1160
- if (await MimetypeBinary.isBinaryMimetype(selected.mimetype, ctx.mimetypes)) {
1161
- return _a.#failure("binary-source-unsupported", 415, `Channel #${selection.channel} is a binary marker and cannot be copied or moved.`, {}, {
1162
- channel: selection.channel,
1163
- mimetype: selected.mimetype,
1164
- recovery: "Use a source with a readable text projection.",
1165
- retryable: false,
1166
- });
1167
- }
1168
- if (selection.lineMarker !== null) {
1169
- const sliced = LineMarkerOps.sliceLinesRaw(content, selection.lineMarker);
1170
- if (sliced.status !== 200)
1171
- return Results.assert(sliced);
1172
- content = sliced.text ?? "";
1173
- scopeNormalizations = sliced.scopeNormalizations;
1174
- }
1175
- if (selected.producerResult !== undefined && selected.producerResult.status >= 400) {
1176
- return Results.assert(selected.producerResult);
1177
- }
1178
- return {
1179
- ...selection,
1180
- storageAddress,
1181
- content,
1182
- mimetype: selected.mimetype,
1183
- ...(scopeNormalizations === undefined ? {} : { scopeNormalizations }),
1184
- };
1185
- }
1186
- static #prependScopeNormalizations(result, scopeNormalizations) {
1187
- if (scopeNormalizations === undefined)
1188
- return result;
1189
- return Results.assert({
1190
- ...result,
1191
- scopeNormalizations: [
1192
- ...scopeNormalizations,
1193
- ...(result.scopeNormalizations ?? []),
1194
- ],
1195
- });
1196
- }
1197
- #resourceAddress(selection) {
1198
- const address = renderTarget({
1199
- scheme: selection.scheme === "file" ? null : selection.scheme,
1200
- pathname: selection.scheme === "file"
1201
- ? selection.identityPathname.replace(/^\//, "")
1202
- : selection.identityPathname,
1203
- fragment: selection.channel === selection.manifest.defaultChannel
1204
- ? null
1205
- : selection.channel,
980
+ // {§bare-inference} Provider work is prepared concurrently by Engine; the
981
+ // dispatcher owns only the ordinary operation-result commit and notification.
982
+ async recordBareResult(context, result, modelCallId) {
983
+ assertClassifyingSignal(context.statement);
984
+ Results.assert(result);
985
+ const logEntryId = await this.#writeLog({
986
+ ...context,
987
+ result,
988
+ curationPlan: null,
989
+ modelCallId,
1206
990
  });
1207
- if (address === null)
1208
- throw new Error("resolved resource selection has no renderable address");
1209
- return address;
1210
- }
1211
- #pendingEffect(selection, action) {
1212
- return {
1213
- target: this.#resourceAddress(selection),
1214
- action,
1215
- };
1216
- }
1217
- static #appliedEffects(result, pending) {
1218
- // {§edit-result-copy-move-effects} — only an applied mutation earns
1219
- // engine-composed effects; native scheme receipts remain internal input.
1220
- const exact = Results.assert(result);
1221
- if (exact.status === 304 || exact.status === 202 || exact.status >= 300)
1222
- return exact;
1223
- if (exact.effects !== undefined) {
1224
- throw new InvalidOperationResultError("A COPY/MOVE mutation result supplied effects before engine composition.");
1225
- }
1226
- const batch = exact.editReceipt === undefined
1227
- ? undefined
1228
- : assertEditBatchReceipt(exact.editReceipt);
1229
- const single = batch === undefined && exact.receipt !== undefined
1230
- ? assertEditReceipt(exact.receipt)
1231
- : undefined;
1232
- const batchSize = batch === undefined
1233
- ? undefined
1234
- : "disposition" in batch
1235
- ? batch.superseded.length
1236
- : batch.effects.length;
1237
- if (batchSize !== undefined && batchSize !== pending.length) {
1238
- throw new InvalidOperationResultError(`COPY/MOVE expected ${pending.length} receipt projections, got ${batchSize}.`);
1239
- }
1240
- if (single !== undefined && pending.length !== 1) {
1241
- throw new InvalidOperationResultError(`COPY/MOVE received one receipt for ${pending.length} resource effects.`);
1242
- }
1243
- let effects;
1244
- if (batch !== undefined && "disposition" in batch) {
1245
- const replacement = pending[0];
1246
- if (replacement === undefined) {
1247
- throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch has no resource effect.");
1248
- }
1249
- if (pending.some(({ target, action }) => target !== replacement.target || action !== replacement.action)) {
1250
- throw new InvalidOperationResultError("A reviewer-replaced COPY/MOVE batch spans incompatible resource effects.");
1251
- }
1252
- effects = [{
1253
- ...replacement,
1254
- receipt: projectEditReceipt(batch, 0),
1255
- }];
1256
- }
1257
- else {
1258
- effects = pending.map((effect, index) => ({
1259
- ...effect,
1260
- ...(batch !== undefined
1261
- ? { receipt: projectEditReceipt(batch, index) }
1262
- : single !== undefined
1263
- ? { receipt: single }
1264
- : {}),
1265
- }));
1266
- }
1267
- assertResourceEffects(effects);
1268
- const { editReceipt: _editReceipt, receipt: _receipt, ...withoutInternalReceipts } = exact;
1269
- return {
1270
- ...withoutInternalReceipts,
1271
- effects,
1272
- };
1273
- }
1274
- // {§copy-move-observation} {§edit-result-copy-move-effects} — scoped
1275
- // COPY/MOVE into an unscoped channel is a text materialization even when
1276
- // CRUD creates the channel wholesale. Carry the receipt through synchronous
1277
- // writes and proposals; the destination scheme owns reviewed output.
1278
- static #withEditMaterialization(result, receipt) {
1279
- const exact = Results.assert(result);
1280
- if (exact.status !== 200 && exact.status !== 201 && exact.status !== 202)
1281
- return exact;
1282
- const materialized = exact.editReceipt === undefined
1283
- ? assertEditBatchReceipt(receipt)
1284
- : assertEditBatchReceipt(exact.editReceipt);
1285
- return {
1286
- ...exact,
1287
- editReceipt: materialized,
1288
- ...(exact.status === 202
1289
- ? {
1290
- attrs: {
1291
- ...exact.attrs,
1292
- editReceipt: materialized,
1293
- },
1294
- }
1295
- : {}),
1296
- };
1297
- }
1298
- #finalizeEffects(result, selection, pending) {
1299
- const routed = this.#withProposalRoute(result, selection);
1300
- if (routed.status !== 202)
1301
- return _a.#appliedEffects(routed, pending);
1302
- return {
1303
- ...routed,
1304
- attrs: {
1305
- ...routed.attrs,
1306
- proposalEffects: pending,
1307
- },
1308
- };
1309
- }
1310
- static #effectsOf(result) {
1311
- return result.effects === undefined
1312
- ? []
1313
- : assertResourceEffects(result.effects);
1314
- }
1315
- static #withCombinedEffects(result, ...additional) {
1316
- const existing = _a.#effectsOf(result);
1317
- const effects = [...existing, ...additional.flat()];
1318
- const { effects: _effects, ...withoutEffects } = result;
1319
- return effects.length === 0
1320
- ? withoutEffects
1321
- : { ...withoutEffects, effects: assertResourceEffects(effects) };
1322
- }
1323
- static #settleProposalEffects(original, settlement) {
1324
- const pending = original.attrs
1325
- ?.proposalEffects;
1326
- if (pending === undefined
1327
- || settlement.resolution.decision !== "accept"
1328
- || settlement.applied === undefined
1329
- || settlement.applied.status >= 300) {
1330
- return settlement;
1331
- }
1332
- const projected = settlement.resolution.result ?? {};
1333
- const aggregate = settlement.applied.editReceipt;
1334
- const applied = _a.#appliedEffects({
1335
- ...projected,
1336
- status: settlement.applied.status,
1337
- ...(aggregate === undefined ? {} : { editReceipt: aggregate }),
1338
- }, pending);
1339
- const { status: _status, body: _body, ...result } = applied;
1340
- const { body: _resolutionBody, ...resolution } = settlement.resolution;
1341
- return {
1342
- ...settlement,
1343
- resolution: {
1344
- ...resolution,
1345
- result,
1346
- },
1347
- };
1348
- }
1349
- #recordEditSettlement(statement, settlement) {
1350
- if (statement.op !== "EDIT")
1351
- return;
1352
- const prepared = this.#preparedEdits.get(statement);
1353
- if (prepared === undefined || !prepared.first) {
1354
- throw new InvalidOperationResultError("An EDIT proposal settled without its prepared batch owner.");
1355
- }
1356
- const { resolution, applied } = settlement;
1357
- if (resolution.decision !== "accept"
1358
- || applied === undefined
1359
- || applied.status >= 300) {
1360
- prepared.batch.aggregate = undefined;
1361
- return;
1362
- }
1363
- if (applied.editReceipt === null) {
1364
- prepared.batch.aggregate = undefined;
1365
- return;
1366
- }
1367
- if (applied.editReceipt !== undefined) {
1368
- prepared.batch.aggregate = assertEditBatchReceipt(applied.editReceipt);
1369
- return;
1370
- }
1371
- if (resolution.body !== undefined)
1372
- prepared.batch.aggregate = undefined;
1373
- }
1374
- static #settlementEffects(settlement) {
1375
- const effects = settlement.resolution.result
1376
- ?.effects;
1377
- return effects === undefined ? [] : assertResourceEffects(effects);
1378
- }
1379
- static #withSettlementEffects(settlement, effects) {
1380
- const projected = (settlement.resolution.result ?? {});
1381
- const { effects: _effects, ...withoutEffects } = projected;
1382
- const result = effects.length === 0
1383
- ? withoutEffects
1384
- : {
1385
- ...withoutEffects,
1386
- effects: assertResourceEffects(effects),
1387
- };
1388
- return {
1389
- ...settlement,
1390
- resolution: {
1391
- ...settlement.resolution,
1392
- result,
1393
- },
1394
- };
1395
- }
1396
- #sameChannel(left, right) {
1397
- return left.scheme === right.scheme
1398
- && left.identityPathname === right.identityPathname
1399
- && left.channel === right.channel;
1400
- }
1401
- #withProposalRoute(result, selection) {
1402
- if (result.status !== 202)
1403
- return result;
1404
- return {
1405
- ...result,
1406
- attrs: {
1407
- ...result.attrs,
1408
- proposalScheme: selection.scheme,
1409
- proposalTarget: {
1410
- scheme: selection.scheme,
1411
- pathname: selection.identityPathname,
1412
- },
1413
- },
1414
- };
1415
- }
1416
- async #invokeEditBatch(selection, edits, ctx) {
1417
- const handler = this.#schemes.get(selection.scheme);
1418
- if (typeof handler?.editBatch !== "function") {
1419
- return _a.#failure("operation-not-implemented", 501, `Scheme '${selection.scheme}' does not implement EDIT batches.`, {}, {
1420
- scheme: selection.scheme,
1421
- operation: "EDIT",
1422
- retryable: false,
1423
- });
1424
- }
1425
- const statements = edits.map(({ marker, body, tags, position }) => ({
1426
- op: "EDIT",
1427
- suffix: "",
1428
- signal: tags,
1429
- target: selection.target,
1430
- lineMarker: marker,
1431
- body,
1432
- position,
1433
- }));
1434
- const addressedScheme = selection.target.kind === "url"
1435
- ? selection.target.scheme
1436
- : selection.scheme;
1437
- try {
1438
- const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions)));
1439
- return this.#withProposalRoute(result, selection);
1440
- }
1441
- catch (err) {
1442
- if (err instanceof InvalidOperationResultError)
1443
- throw err;
1444
- console.error(`Scheme '${selection.scheme}' COPY/MOVE edit threw outside its operation result contract:`, err);
1445
- return _a.#failure("scheme-handler-threw", 500, `The '${selection.scheme}' scheme did not produce a COPY/MOVE edit result.`, {}, {
1446
- stage: "scheme-dispatch",
1447
- scheme: selection.scheme,
1448
- operation: "EDIT",
1449
- });
1450
- }
1451
- }
1452
- async #writeDestination(statement, source, destination, ctx) {
1453
- const existingResult = await this.#readEntry(destination.scheme, destination.pathname, ctx);
1454
- if (existingResult.status >= 400 && existingResult.status !== 404) {
1455
- return existingResult;
1456
- }
1457
- const existing = existingResult.status === 200
1458
- ? existingResult.entry
1459
- : null;
1460
- if (existingResult.status === 200 && existing === null) {
1461
- throw new InvalidOperationResultError(`The '${destination.scheme}' scheme returned 200 without a destination entry.`);
1462
- }
1463
- const destinationChannel = existing?.channels[destination.channel];
1464
- const expectedMimetype = destinationChannel?.mimetype
1465
- ?? await PathMimetype.resolveEntryMimetype(destination.pathname, destination.manifest.channels[destination.channel] ?? source.mimetype, ctx.mimetypes);
1466
- if (source.mimetype !== expectedMimetype) {
1467
- return _a.#failure("mimetype-mismatch", 415, `COPY or MOVE cannot write '${source.mimetype}' into a '${expectedMimetype}' channel.`, {}, {
1468
- channel: destination.channel,
1469
- sourceMimetype: source.mimetype,
1470
- destinationMimetype: expectedMimetype,
1471
- retryable: false,
1472
- });
1473
- }
1474
- const tags = Array.isArray(statement.signal) ? statement.signal : [];
1475
- const destinationEffect = this.#pendingEffect(destination, destinationChannel === undefined ? "create" : "update");
1476
- if (destination.lineMarker !== null) {
1477
- if (destinationChannel === undefined) {
1478
- return _a.#failure("destination-region-not-found", 404, `A destination region requires an existing #${destination.channel} channel.`, {}, {
1479
- destination: this.#resourceAddress(destination),
1480
- retryable: false,
1481
- });
1482
- }
1483
- if (await MimetypeBinary.isBinaryMimetype(destinationChannel.mimetype, ctx.mimetypes)) {
1484
- return _a.#failure("binary-region-unsupported", 415, `Channel #${destination.channel} is binary and cannot receive a textual region.`, {}, {
1485
- channel: destination.channel,
1486
- mimetype: destinationChannel.mimetype,
1487
- retryable: false,
1488
- });
1489
- }
1490
- const edited = await this.#invokeEditBatch(destination, [{
1491
- marker: destination.lineMarker,
1492
- body: source.content,
1493
- tags,
1494
- position: statement.position,
1495
- }], ctx);
1496
- return this.#finalizeEffects(edited, destination, [destinationEffect]);
1497
- }
1498
- if (destinationChannel !== undefined
1499
- && destinationChannel.content !== source.content) {
1500
- return _a.#failure("copy-destination-exists", 409, `COPY or MOVE destination ${this.#resourceAddress(destination)} already contains different content.`, {}, {
1501
- destination: this.#resourceAddress(destination),
1502
- retryable: false,
1503
- });
1504
- }
1505
- const priorTags = existing?.tags ?? [];
1506
- const mergedTags = [...new Set([...priorTags, ...tags])];
1507
- const addsTags = mergedTags.length !== priorTags.length;
1508
- if (destinationChannel !== undefined && !addsTags)
1509
- return { status: 304 };
1510
- const channels = {
1511
- ...(existing?.channels ?? {}),
1512
- [destination.channel]: {
1513
- content: source.content,
1514
- mimetype: source.mimetype,
1515
- },
1516
- };
1517
- const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels, tags: mergedTags }, ctx);
1518
- const exactWritten = Results.assert(written);
1519
- const materialized = source.lineMarker === null
1520
- || (exactWritten.status !== 200 && exactWritten.status !== 201 && exactWritten.status !== 202)
1521
- ? exactWritten
1522
- : _a.#withEditMaterialization(exactWritten, editReceipt(destinationChannel?.content ?? "", source.content, [{
1523
- marker: { marks: [1, -1] },
1524
- body: source.content,
1525
- }]));
1526
- return this.#finalizeEffects(materialized, destination, [destinationEffect]);
1527
- }
1528
- async #copyOrchestration({ statement, source, destination, ctx, }) {
1529
- const resolvedSource = await this.#resolveResourceSelection(source, ctx);
1530
- if (_a.#isDispatchResult(resolvedSource))
1531
- return resolvedSource;
1532
- const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
1533
- if (_a.#isDispatchResult(resolvedDestination))
1534
- return resolvedDestination;
1535
- const selected = await this.#selectSource(resolvedSource, ctx);
1536
- if (_a.#isDispatchResult(selected))
1537
- return selected;
1538
- const result = await this.#writeDestination(statement, selected, resolvedDestination, ctx);
1539
- return _a.#prependScopeNormalizations(result, selected.scopeNormalizations);
1540
- }
1541
- #deferredMoveSource(source, destination) {
1542
- return {
1543
- target: source.target,
1544
- lineMarker: source.lineMarker,
1545
- scheme: source.scheme,
1546
- pathname: source.pathname,
1547
- channel: source.channel,
1548
- destination: this.#resourceAddress(destination),
1549
- };
1550
- }
1551
- #moveFailureAfterDestination(result, destination, destinationEffects) {
1552
- const exact = Results.assert(result);
1553
- if (exact.status < 400) {
1554
- throw new InvalidOperationResultError("A successful MOVE source result was classified as a partial failure.");
1555
- }
1556
- if (exact.problem === undefined) {
1557
- throw new InvalidOperationResultError("A failed MOVE source mutation has no Problem Details.");
1558
- }
1559
- const failed = Results.assert({
1560
- ...exact,
1561
- problem: {
1562
- ...exact.problem,
1563
- operation: "MOVE",
1564
- destinationWritten: true,
1565
- destination,
1566
- },
1567
- });
1568
- return _a.#withCombinedEffects(failed, destinationEffects);
1569
- }
1570
- async #removeMoveSource(statement, source, ctx) {
1571
- const effect = this.#pendingEffect(source, source.lineMarker === null ? "delete" : "update");
1572
- if (source.lineMarker === null) {
1573
- const deleted = await this.#deleteChannel(source.scheme, source.pathname, source.channel, ctx);
1574
- return this.#finalizeEffects(Results.assert(deleted), source, [effect]);
1575
- }
1576
- const edited = await this.#invokeEditBatch(source, [{
1577
- marker: source.lineMarker,
1578
- body: "",
1579
- tags: null,
1580
- position: statement.position,
1581
- }], ctx);
1582
- return this.#finalizeEffects(edited, source, [effect]);
1583
- }
1584
- async #moveWithinChannel(statement, source, destination, ctx) {
1585
- if (source.lineMarker === null) {
1586
- if (destination.lineMarker !== null) {
1587
- return _a.#failure("move-region-overlap", 409, "MOVE cannot insert a whole channel into itself and then remove that channel.", {}, {
1588
- source: this.#resourceAddress(source),
1589
- destination: this.#resourceAddress(destination),
1590
- retryable: false,
1591
- });
1592
- }
1593
- return this.#writeDestination(statement, source, destination, ctx);
1594
- }
1595
- if (destination.lineMarker === null) {
1596
- return this.#writeDestination(statement, source, destination, ctx);
1597
- }
1598
- const moved = await this.#invokeEditBatch(destination, [
1599
- {
1600
- marker: destination.lineMarker,
1601
- body: source.content,
1602
- tags: Array.isArray(statement.signal) ? statement.signal : [],
1603
- position: statement.position,
1604
- },
1605
- {
1606
- marker: source.lineMarker,
1607
- body: "",
1608
- tags: null,
1609
- position: statement.position,
1610
- },
1611
- ], ctx);
1612
- const effect = this.#pendingEffect(destination, "update");
1613
- return this.#finalizeEffects(moved, destination, [effect, effect]);
1614
- }
1615
- async #moveOrchestration({ statement, source, destination, ctx, }) {
1616
- const resolvedSource = await this.#resolveResourceSelection(source, ctx);
1617
- if (_a.#isDispatchResult(resolvedSource))
1618
- return resolvedSource;
1619
- const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
1620
- if (_a.#isDispatchResult(resolvedDestination))
1621
- return resolvedDestination;
1622
- const selected = await this.#selectSource(resolvedSource, ctx);
1623
- if (_a.#isDispatchResult(selected))
1624
- return selected;
1625
- if (this.#sameChannel(resolvedSource, resolvedDestination)) {
1626
- const result = await this.#moveWithinChannel(statement, selected, resolvedDestination, ctx);
1627
- return resolvedDestination.lineMarker === null
1628
- ? _a.#prependScopeNormalizations(result, selected.scopeNormalizations)
1629
- : result;
1630
- }
1631
- const destinationResult = _a.#prependScopeNormalizations(await this.#writeDestination(statement, selected, resolvedDestination, ctx), selected.scopeNormalizations);
1632
- if (destinationResult.status >= 400)
1633
- return destinationResult;
1634
- const destinationAddress = this.#resourceAddress(resolvedDestination);
1635
- const destinationEffects = _a.#effectsOf(destinationResult);
1636
- if (destinationResult.status === 202) {
1637
- return {
1638
- ...destinationResult,
1639
- attrs: {
1640
- ...destinationResult.attrs,
1641
- moveSource: this.#deferredMoveSource(resolvedSource, resolvedDestination),
1642
- },
1643
- };
1644
- }
1645
- const sourceResult = await this.#removeMoveSource(statement, resolvedSource, ctx);
1646
- if (sourceResult.status === 202) {
1647
- return {
1648
- ...sourceResult,
1649
- ...(destinationResult.scopeNormalizations === undefined
1650
- ? {}
1651
- : { scopeNormalizations: destinationResult.scopeNormalizations }),
1652
- attrs: {
1653
- ...sourceResult.attrs,
1654
- moveDestinationWritten: destinationAddress,
1655
- moveDestinationEffects: destinationEffects,
1656
- },
1657
- };
1658
- }
1659
- if (sourceResult.status >= 400) {
1660
- return this.#moveFailureAfterDestination(destinationResult.scopeNormalizations === undefined
1661
- ? sourceResult
1662
- : Results.assert({
1663
- ...sourceResult,
1664
- scopeNormalizations: destinationResult.scopeNormalizations,
1665
- }), destinationAddress, destinationEffects);
1666
- }
1667
- const base = destinationResult.status === 304
1668
- ? { ...destinationResult, status: 200 }
1669
- : destinationResult;
1670
- return _a.#withCombinedEffects(base, _a.#effectsOf(sourceResult));
1671
- }
1672
- async #settleMoveProposal({ statement, result, settlement, ctx, ids, }) {
1673
- if (statement.op !== "MOVE")
1674
- return settlement;
1675
- const attrs = result.attrs;
1676
- const destinationWritten = attrs?.moveDestinationWritten;
1677
- if (destinationWritten !== undefined) {
1678
- const destinationEffects = attrs?.moveDestinationEffects === undefined
1679
- ? []
1680
- : assertResourceEffects(attrs.moveDestinationEffects);
1681
- if (settlement.resolution.decision !== "accept") {
1682
- const decision = settlement.resolution.decision;
1683
- return {
1684
- resolution: settlement.resolution,
1685
- 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"}.`, {}, {
1686
- retryable: false,
1687
- }), destinationWritten, destinationEffects),
1688
- };
1689
- }
1690
- if (settlement.applied === undefined) {
1691
- return {
1692
- resolution: settlement.resolution,
1693
- applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
1694
- stage: "proposal-application",
1695
- retryable: false,
1696
- }), destinationWritten, destinationEffects),
1697
- };
1698
- }
1699
- if (settlement.applied.status >= 400) {
1700
- return {
1701
- resolution: settlement.resolution,
1702
- applied: this.#moveFailureAfterDestination(settlement.applied, destinationWritten, destinationEffects),
1703
- };
1704
- }
1705
- return _a.#withSettlementEffects(settlement, [
1706
- ...destinationEffects,
1707
- ..._a.#settlementEffects(settlement),
1708
- ]);
1709
- }
1710
- const deferred = attrs?.moveSource;
1711
- if (deferred === undefined
1712
- || settlement.resolution.decision !== "accept"
1713
- || (settlement.applied?.status ?? 200) >= 400) {
1714
- return settlement;
1715
- }
1716
- if (settlement.applied === undefined) {
1717
- return {
1718
- resolution: settlement.resolution,
1719
- applied: _a.#failure("proposal-apply-missing", 500, "The destination scheme accepted its MOVE proposal without applying the destination mutation.", {}, {
1720
- stage: "proposal-application",
1721
- retryable: false,
1722
- }),
1723
- };
1724
- }
1725
- const destinationEffects = _a.#settlementEffects(settlement);
1726
- const resolvedSource = await this.#resolveResourceSelection({
1727
- target: deferred.target,
1728
- lineMarker: deferred.lineMarker,
1729
- }, ctx);
1730
- if (_a.#isDispatchResult(resolvedSource)) {
1731
- return {
1732
- resolution: settlement.resolution,
1733
- applied: this.#moveFailureAfterDestination(resolvedSource, deferred.destination, destinationEffects),
1734
- };
1735
- }
1736
- if (resolvedSource.scheme !== deferred.scheme
1737
- || resolvedSource.pathname !== deferred.pathname
1738
- || resolvedSource.channel !== deferred.channel) {
1739
- throw new InvalidOperationResultError("A deferred MOVE source no longer resolves to its recorded identity.");
1740
- }
1741
- const removed = await this.#removeMoveSource(statement, resolvedSource, ctx);
1742
- if (removed.status >= 400) {
1743
- return {
1744
- resolution: settlement.resolution,
1745
- applied: this.#moveFailureAfterDestination(removed, deferred.destination, destinationEffects),
1746
- };
1747
- }
1748
- if (removed.status !== 202) {
1749
- return _a.#withSettlementEffects(settlement, [
1750
- ...destinationEffects,
1751
- ..._a.#effectsOf(removed),
1752
- ]);
1753
- }
1754
- const initialSourceSettlement = await this.#proposals.workerApply(statement, removed, { decision: "accept" }, ids);
1755
- const sourceSettlement = _a.#settleProposalEffects(removed, initialSourceSettlement);
1756
- if (sourceSettlement.applied === undefined) {
1757
- return {
1758
- resolution: settlement.resolution,
1759
- applied: this.#moveFailureAfterDestination(_a.#failure("proposal-apply-missing", 500, "The source scheme accepted its MOVE proposal without applying the source mutation.", {}, {
1760
- stage: "proposal-application",
1761
- retryable: false,
1762
- }), deferred.destination, destinationEffects),
1763
- };
1764
- }
1765
- if (sourceSettlement.applied.status >= 400) {
1766
- return {
1767
- resolution: settlement.resolution,
1768
- applied: this.#moveFailureAfterDestination(sourceSettlement.applied, deferred.destination, destinationEffects),
1769
- };
1770
- }
1771
- return _a.#withSettlementEffects(settlement, [
1772
- ...destinationEffects,
1773
- ..._a.#settlementEffects(sourceSettlement),
1774
- ]);
991
+ context.onDispatch?.(logEntryId);
992
+ return result;
1775
993
  }
1776
994
  // {§send-premature-terminate} — the unified PENDING SET, judged at the terminal's OWN dispatch
1777
995
  // (post-batch: the emission's earlier ops already executed, so a same-turn KILL+[200] repairs in
1778
996
  // ONE turn, and a same-turn WORK+[200] is caught — the spawn is live by the time the SEND lands).
1779
- // pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN, results unseen
997
+ // pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN/BARE, results unseen
1780
998
  // until next packet). Failed operations are the separate next-packet leg shared by explicit
1781
999
  // completion and empty-join completion. Nothing pending may be silently discarded; 499 discards
1782
1000
  // BY STATED INTENT and is never gated.
@@ -1789,30 +1007,32 @@ class Dispatcher {
1789
1007
  const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
1790
1008
  if (liveChild !== undefined)
1791
1009
  pending.push("surviving workers");
1792
- const observations = await this.#pendingObservations(workerId, turnId);
1793
- if (observations.retrievals)
1010
+ const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
1011
+ if (boundaries.retrievals)
1794
1012
  pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
1795
- if (observations.streamTerminations) {
1013
+ if (boundaries.streamTerminations) {
1796
1014
  pending.push("completed stream results that land in the NEXT packet's Log");
1797
1015
  }
1798
- if (observations.childTerminations)
1016
+ if (boundaries.childTerminations)
1799
1017
  pending.push("worker results that arrived during this turn (they land NEXT turn)");
1800
1018
  return pending;
1801
1019
  }
1802
- // Results cross an observation boundary only when they have appeared in a
1803
- // packet. Completion alone is not delivery. Keep the three next-packet
1804
- // producers in one classifier so SEND[200]'s discard gate and SEND[202]'s
1805
- // empty-join decision cannot disagree about what remains unseen.
1806
- async #pendingObservations(workerId, turnId) {
1807
- const [retrievals, streamTermination, childTermination] = await Promise.all([
1808
- this.#db.engine_turn_retrievals.all({ turn_id: turnId }),
1020
+ // Results cross an observation boundary only when they have appeared in a packet;
1021
+ // successful FOLD effects likewise become useful through the curated next packet.
1022
+ // Keep every next-packet boundary in one classifier while letting the callers apply
1023
+ // their distinct contracts: retrievals block explicit completion, whereas FOLD only
1024
+ // prevents an empty wait from being inferred as completion.
1025
+ async #nextPacketBoundaries(workerId, turnId) {
1026
+ const [turnBoundaries, streamTermination, childTermination] = await Promise.all([
1027
+ this.#db.engine_turn_packet_boundaries.all({ turn_id: turnId }),
1809
1028
  this.#db.engine_worker_has_undelivered_stream_term
1810
1029
  .get({ worker_id: workerId }),
1811
1030
  this.#db.engine_worker_has_undelivered_child_term
1812
1031
  .get({ worker_id: workerId }),
1813
1032
  ]);
1814
1033
  return {
1815
- retrievals: retrievals.length > 0,
1034
+ retrievals: turnBoundaries.some(({ op }) => op !== "FOLD"),
1035
+ folds: turnBoundaries.some(({ op }) => op === "FOLD"),
1816
1036
  streamTerminations: streamTermination !== undefined,
1817
1037
  childTerminations: childTermination !== undefined,
1818
1038
  };
@@ -1854,14 +1074,14 @@ class Dispatcher {
1854
1074
  return _a.#failure("send-status-required", 400, "SEND requires a numeric status.", {}, { retryable: false });
1855
1075
  }
1856
1076
  const raw = statement.body === null ? "" : statement.body.raw;
1857
- // The park rides SEND[202] only ({§park-202-only}). A scoped SEND[102] is neither
1077
+ // The park rides SEND signal 202 only ({§park-202-only}). A scoped signal 102 is neither
1858
1078
  // a wait nor a meaningful continuation, so reject it instead of preserving the
1859
1079
  // retired dual spelling.
1860
1080
  if (status === 102 && statement.lineMarker !== null) {
1861
- return _a.#failure("send-scope-invalid", 400, "SEND[102] does not accept a scope.", {}, {
1081
+ return _a.#failure("send-scope-invalid", 400, "`## SEND0 [102]` does not accept a scope.", {}, {
1862
1082
  requestedStatus: 102,
1863
1083
  scope: statement.lineMarker,
1864
- recovery: "Use SEND[202] with a scope to wait, or remove the scope to continue.",
1084
+ recovery: "Use `## SEND0 [202] <scope>` to wait, or remove the scope to continue.",
1865
1085
  retryable: false,
1866
1086
  });
1867
1087
  }
@@ -1875,7 +1095,7 @@ class Dispatcher {
1875
1095
  this.#parkDeadlines.set(loopId, -1); // indefinite: the bounded child's terminal is the wake edge
1876
1096
  return { status: 102, attrs: { parked: -1, join: true } };
1877
1097
  }
1878
- // {§wait-obligation-matrix} — SEND[202] is the obligation-checked join. A live
1098
+ // {§wait-obligation-matrix} — SEND signal 202 is the obligation-checked join. A live
1879
1099
  // obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
1880
1100
  // reawakens it ({§worker-lifecycle-child-wake}); a wait on nothing (∅) is already satisfied and
1881
1101
  // resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
@@ -1894,8 +1114,8 @@ class Dispatcher {
1894
1114
  // all complete-but-unobserved. Their wake edge may already have
1895
1115
  // fired, so do not park; continue directly to the packet that
1896
1116
  // materializes them.
1897
- const observations = await this.#pendingObservations(workerId, turnId);
1898
- if (observations.retrievals || observations.streamTerminations || observations.childTerminations) {
1117
+ const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
1118
+ if (boundaries.retrievals || boundaries.folds || boundaries.streamTerminations || boundaries.childTerminations) {
1899
1119
  return { status: 102 };
1900
1120
  }
1901
1121
  const failCount = await this.#unobservedFailureCount(turnId);
@@ -1950,7 +1170,7 @@ class Dispatcher {
1950
1170
  return _a.#failure("retrieval-results-unobserved", 409, "Last turn both performed retrieval operations and attempted to terminate. Retrieval operations force an additional turn so their results can be reviewed.", {}, {
1951
1171
  pending: [...pending],
1952
1172
  stage: "completion",
1953
- recovery: "Review the results, then use only PLAN and SEND[200] to conclude.",
1173
+ recovery: "Review the results, then use only `# PLAN0` and `## SEND0 [200]` to conclude.",
1954
1174
  retryable: false,
1955
1175
  });
1956
1176
  }
@@ -1977,7 +1197,7 @@ class Dispatcher {
1977
1197
  turn_id: turnId,
1978
1198
  });
1979
1199
  if (seqs === undefined) {
1980
- throw new Error(`SEND[499]: no coordinate for loop=${loopId} turn=${turnId}`);
1200
+ throw new Error(`SEND signal 499: no coordinate for loop=${loopId} turn=${turnId}`);
1981
1201
  }
1982
1202
  Results.attachInstance(failure, `log:///${seqs.loop_seq}/${seqs.turn_seq}/${ctx.sequence}/SEND`);
1983
1203
  const finished = await this.#lifecycle.finish(loopId, failure);
@@ -1999,8 +1219,8 @@ class Dispatcher {
1999
1219
  content: null,
2000
1220
  mimetype: null,
2001
1221
  results: [],
2002
- itemsTokenTotal: 0,
2003
- returnedItemsTokenTotal: 0,
1222
+ itemsWeightTotal: 0,
1223
+ returnedItemsWeightTotal: 0,
2004
1224
  matchingPathCount: 0,
2005
1225
  matchLocationCount: 0,
2006
1226
  }
@@ -2070,6 +1290,7 @@ class Dispatcher {
2070
1290
  statement,
2071
1291
  manifest,
2072
1292
  target,
1293
+ identity: target,
2073
1294
  representation: resolved.representation,
2074
1295
  mimetypes: ctx.mimetypes,
2075
1296
  }));
@@ -2142,8 +1363,8 @@ class Dispatcher {
2142
1363
  content: null,
2143
1364
  mimetype: null,
2144
1365
  results: [],
2145
- itemsTokenTotal: 0,
2146
- returnedItemsTokenTotal: 0,
1366
+ itemsWeightTotal: 0,
1367
+ returnedItemsWeightTotal: 0,
2147
1368
  matchingPathCount: 0,
2148
1369
  matchLocationCount: 0,
2149
1370
  }, { target });
@@ -2171,11 +1392,14 @@ class Dispatcher {
2171
1392
  async #runLogCuration(statement, ctx) {
2172
1393
  const addressedScheme = schemeNameOf(statement.target);
2173
1394
  if (addressedScheme !== null && addressedScheme !== "log") {
2174
- return _a.#failure("operation-not-implemented", 501, `Scheme '${addressedScheme}' does not implement ${statement.op}.`, {}, {
2175
- scheme: addressedScheme,
2176
- operation: statement.op,
2177
- retryable: false,
2178
- });
1395
+ return {
1396
+ result: _a.#failure("operation-not-implemented", 501, `Scheme '${addressedScheme}' does not implement ${statement.op}.`, {}, {
1397
+ scheme: addressedScheme,
1398
+ operation: statement.op,
1399
+ retryable: false,
1400
+ }),
1401
+ plan: null,
1402
+ };
2179
1403
  }
2180
1404
  const handler = this.#schemes.get("log");
2181
1405
  const manifest = this.#schemes.manifestFor("log");
@@ -2183,21 +1407,20 @@ class Dispatcher {
2183
1407
  throw new Error("the core log curation owner is not registered");
2184
1408
  }
2185
1409
  const schemeCtx = new SchemeCtxImpl(ctx, "log", manifest, this.#liveSubscriptions);
2186
- return statement.op === "OPEN"
2187
- ? Results.assert(await handler.open(statement, schemeCtx))
2188
- : Results.assert(await handler.fold(statement, schemeCtx));
1410
+ const outcome = await handler.curate(statement, schemeCtx);
1411
+ return { result: Results.assert(outcome.result), plan: outcome.plan };
2189
1412
  }
2190
1413
  // {§proposal}/{§send} — status 202 is a proposal except for broadcast
2191
- // SEND[202], which parks the loop. The operation disambiguates the status.
1414
+ // SEND signal 202, which parks the loop. The operation disambiguates the status.
2192
1415
  static #isProposal(statement, result) {
2193
- // {§send-300-choices} — SEND[300] is the proposal exception.
1416
+ // {§send-300-choices} — SEND signal 300 is the proposal exception.
2194
1417
  if (result.status !== 202)
2195
1418
  return false;
2196
1419
  if (statement.op === "SEND" && statement.signal === 300)
2197
1420
  return true;
2198
1421
  return !(statement.op === "SEND" && statement.target === null);
2199
1422
  }
2200
- async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, }) {
1423
+ async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, curationPlan, modelCallId, }) {
2201
1424
  const durableStatement = DurableStatement.project(statement);
2202
1425
  const target = this.#extractTarget(durableStatement.target);
2203
1426
  await this.#canonColumns(target, workspaceId); // {§fs-answer-in-canon}
@@ -2207,12 +1430,23 @@ class Dispatcher {
2207
1430
  // A proposal (status 202 from a side-effecting op) is written to the log in
2208
1431
  // state='proposed' until the proposal lifecycle resolves it; attrs holds the
2209
1432
  // scheme-supplied payload (file diff, exec command, etc.) the client renders
2210
- // for review and the scheme consumes on accept. A broadcast SEND[202] is a
1433
+ // for review and the scheme consumes on accept. A broadcast SEND signal 202 is a
2211
1434
  // parked-terminal, not a proposal (#isProposal) → state='resolved'.
2212
1435
  const isProposed = _a.#isProposal(statement, result);
2213
1436
  let attrsObj = (result.attrs !== undefined && result.attrs !== null)
2214
1437
  ? { ...result.attrs }
2215
1438
  : {};
1439
+ if (curationPlan !== null) {
1440
+ if (Object.hasOwn(attrsObj, "__plurnk_curation")) {
1441
+ throw new Error("Dispatcher.#writeLog: result attrs collide with private log curation state");
1442
+ }
1443
+ attrsObj.__plurnk_curation = {
1444
+ ids: curationPlan.targetLogEntryIds,
1445
+ expanded: curationPlan.expanded,
1446
+ add: curationPlan.add,
1447
+ remove: curationPlan.remove,
1448
+ };
1449
+ }
2216
1450
  const seqs = statement.op === "EXEC" || result.problem !== undefined
2217
1451
  ? await this.#db.engine_loop_turn_seqs.get({
2218
1452
  loop_id: loopId,
@@ -2263,6 +1497,7 @@ class Dispatcher {
2263
1497
  sequence: sequence,
2264
1498
  origin,
2265
1499
  source: null, // dispatch entries are self-authored; {§env-delta} deltas set this
1500
+ model_call_id: modelCallId,
2266
1501
  op: durableStatement.op,
2267
1502
  suffix: durableStatement.suffix,
2268
1503
  signal: this.#signalToJson(durableStatement.signal),
@@ -2280,7 +1515,14 @@ class Dispatcher {
2280
1515
  rx: rxJson,
2281
1516
  mimetype_rx: "application/json",
2282
1517
  status_rx: result.status,
2283
- tokens: this.#tokenize(txJson) + this.#tokenize(rxJson),
1518
+ weight: LogBody.weight({
1519
+ op: durableStatement.op,
1520
+ attrs,
1521
+ tx: txJson,
1522
+ rx: rxJson,
1523
+ mimetypeTx: "application/json",
1524
+ mimetypeRx: "application/json",
1525
+ }, this.#weighContent),
2284
1526
  state: isProposed ? "proposed" : "resolved",
2285
1527
  outcome: null,
2286
1528
  attrs,