@plurnk/plurnk-service 1.4.0 → 1.6.0

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 (302) hide show
  1. package/.env.defaults +37 -31
  2. package/INSTALL.md +16 -11
  3. package/README.md +1 -0
  4. package/SPEC.md +631 -397
  5. package/dist/Paths.d.ts.map +1 -1
  6. package/dist/Paths.js +2 -3
  7. package/dist/Paths.js.map +1 -1
  8. package/dist/build-info.json +1 -1
  9. package/dist/content/edit-collision.d.ts +5 -0
  10. package/dist/content/edit-collision.d.ts.map +1 -0
  11. package/dist/content/edit-collision.js +14 -0
  12. package/dist/content/edit-collision.js.map +1 -0
  13. package/dist/content/edit-receipt.d.ts +3 -2
  14. package/dist/content/edit-receipt.d.ts.map +1 -1
  15. package/dist/content/edit-receipt.js +58 -13
  16. package/dist/content/edit-receipt.js.map +1 -1
  17. package/dist/content/index.d.ts +5 -1
  18. package/dist/content/index.d.ts.map +1 -1
  19. package/dist/content/index.js +4 -1
  20. package/dist/content/index.js.map +1 -1
  21. package/dist/content/line-anchors.d.ts +41 -0
  22. package/dist/content/line-anchors.d.ts.map +1 -0
  23. package/dist/content/line-anchors.js +191 -0
  24. package/dist/content/line-anchors.js.map +1 -0
  25. package/dist/content/line-marker.d.ts +7 -3
  26. package/dist/content/line-marker.d.ts.map +1 -1
  27. package/dist/content/line-marker.js +2 -1
  28. package/dist/content/line-marker.js.map +1 -1
  29. package/dist/content/matcher.d.ts +1 -1
  30. package/dist/content/matcher.d.ts.map +1 -1
  31. package/dist/content/matcher.js +2 -2
  32. package/dist/content/matcher.js.map +1 -1
  33. package/dist/content/read-projector.d.ts +20 -0
  34. package/dist/content/read-projector.d.ts.map +1 -0
  35. package/dist/content/read-projector.js +122 -0
  36. package/dist/content/read-projector.js.map +1 -0
  37. package/dist/content/read-resolve.d.ts +2 -7
  38. package/dist/content/read-resolve.d.ts.map +1 -1
  39. package/dist/content/read-resolve.js +32 -107
  40. package/dist/content/read-resolve.js.map +1 -1
  41. package/dist/core/BranchReceipt.d.ts +0 -1
  42. package/dist/core/BranchReceipt.d.ts.map +1 -1
  43. package/dist/core/BranchReceipt.js +0 -3
  44. package/dist/core/BranchReceipt.js.map +1 -1
  45. package/dist/core/BudgetReadout.d.ts.map +1 -1
  46. package/dist/core/BudgetReadout.js +9 -2
  47. package/dist/core/BudgetReadout.js.map +1 -1
  48. package/dist/core/ChannelWrite.d.ts +3 -0
  49. package/dist/core/ChannelWrite.d.ts.map +1 -1
  50. package/dist/core/ChannelWrite.js +3 -3
  51. package/dist/core/ChannelWrite.js.map +1 -1
  52. package/dist/core/ChannelWrite.sql +4 -3
  53. package/dist/core/CoreSchemeServices.d.ts +10 -2
  54. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  55. package/dist/core/CoreSchemeServices.js.map +1 -1
  56. package/dist/core/Dispatcher.d.ts +14 -8
  57. package/dist/core/Dispatcher.d.ts.map +1 -1
  58. package/dist/core/Dispatcher.js +656 -312
  59. package/dist/core/Dispatcher.js.map +1 -1
  60. package/dist/core/DurableStatement.d.ts.map +1 -1
  61. package/dist/core/DurableStatement.js +2 -0
  62. package/dist/core/DurableStatement.js.map +1 -1
  63. package/dist/core/Engine.d.ts +24 -18
  64. package/dist/core/Engine.d.ts.map +1 -1
  65. package/dist/core/Engine.js +620 -392
  66. package/dist/core/Engine.js.map +1 -1
  67. package/dist/core/Engine.sql +188 -119
  68. package/dist/core/ExecutableTools.d.ts +10 -0
  69. package/dist/core/ExecutableTools.d.ts.map +1 -0
  70. package/dist/core/ExecutableTools.js +41 -0
  71. package/dist/core/ExecutableTools.js.map +1 -0
  72. package/dist/core/ExecutorRegistry.d.ts +3 -3
  73. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  74. package/dist/core/ExecutorRegistry.js +2 -2
  75. package/dist/core/ExecutorRegistry.js.map +1 -1
  76. package/dist/core/LogBody.d.ts +4 -1
  77. package/dist/core/LogBody.d.ts.map +1 -1
  78. package/dist/core/LogBody.js +46 -2
  79. package/dist/core/LogBody.js.map +1 -1
  80. package/dist/core/LogEntryProjection.d.ts +14 -0
  81. package/dist/core/LogEntryProjection.d.ts.map +1 -0
  82. package/dist/core/LogEntryProjection.js +38 -0
  83. package/dist/core/LogEntryProjection.js.map +1 -0
  84. package/dist/core/LoopLifecycle.d.ts +1 -2
  85. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  86. package/dist/core/LoopLifecycle.js +1 -4
  87. package/dist/core/LoopLifecycle.js.map +1 -1
  88. package/dist/core/LoopLifecycle.sql +1 -4
  89. package/dist/core/ModelCall.d.ts +27 -0
  90. package/dist/core/ModelCall.d.ts.map +1 -0
  91. package/dist/core/ModelCall.js +125 -0
  92. package/dist/core/ModelCall.js.map +1 -0
  93. package/dist/core/PacketBuilder.d.ts +13 -12
  94. package/dist/core/PacketBuilder.d.ts.map +1 -1
  95. package/dist/core/PacketBuilder.js +146 -118
  96. package/dist/core/PacketBuilder.js.map +1 -1
  97. package/dist/core/ProblemLog.d.ts.map +1 -1
  98. package/dist/core/ProblemLog.js +1 -0
  99. package/dist/core/ProblemLog.js.map +1 -1
  100. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  101. package/dist/core/ProposalLifecycle.js +3 -4
  102. package/dist/core/ProposalLifecycle.js.map +1 -1
  103. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  104. package/dist/core/ProviderInstantiate.js +23 -6
  105. package/dist/core/ProviderInstantiate.js.map +1 -1
  106. package/dist/core/SchemeRegistry.js +7 -7
  107. package/dist/core/SchemeRegistry.js.map +1 -1
  108. package/dist/core/StrikeRail.d.ts +5 -2
  109. package/dist/core/StrikeRail.d.ts.map +1 -1
  110. package/dist/core/StrikeRail.js +10 -7
  111. package/dist/core/StrikeRail.js.map +1 -1
  112. package/dist/core/TerminalResult.d.ts +16 -0
  113. package/dist/core/TerminalResult.d.ts.map +1 -0
  114. package/dist/core/TerminalResult.js +49 -0
  115. package/dist/core/TerminalResult.js.map +1 -0
  116. package/dist/core/caps/CapsResolve.d.ts +5 -1
  117. package/dist/core/caps/CapsResolve.d.ts.map +1 -1
  118. package/dist/core/caps/CapsResolve.js +9 -5
  119. package/dist/core/caps/CapsResolve.js.map +1 -1
  120. package/dist/core/caps/DbChannelCaps.d.ts +1 -1
  121. package/dist/core/caps/DbChannelCaps.d.ts.map +1 -1
  122. package/dist/core/caps/DbChannelCaps.js +6 -4
  123. package/dist/core/caps/DbChannelCaps.js.map +1 -1
  124. package/dist/core/caps/DbEntryCaps.d.ts +2 -1
  125. package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
  126. package/dist/core/caps/DbEntryCaps.js +21 -8
  127. package/dist/core/caps/DbEntryCaps.js.map +1 -1
  128. package/dist/core/caps/DbNotifyCaps.d.ts +1 -1
  129. package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
  130. package/dist/core/caps/DbNotifyCaps.js +6 -4
  131. package/dist/core/caps/DbNotifyCaps.js.map +1 -1
  132. package/dist/core/caps/DbProjectionCaps.d.ts +1 -0
  133. package/dist/core/caps/DbProjectionCaps.d.ts.map +1 -1
  134. package/dist/core/caps/DbProjectionCaps.js +29 -0
  135. package/dist/core/caps/DbProjectionCaps.js.map +1 -1
  136. package/dist/core/caps/DbSubscriptionCaps.d.ts +4 -6
  137. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  138. package/dist/core/caps/DbSubscriptionCaps.js +27 -13
  139. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  140. package/dist/core/caps/SchemeCtxImpl.d.ts +11 -3
  141. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  142. package/dist/core/caps/SchemeCtxImpl.js +10 -8
  143. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  144. package/dist/core/fork.d.ts.map +1 -1
  145. package/dist/core/fork.js +23 -7
  146. package/dist/core/fork.js.map +1 -1
  147. package/dist/core/fork.sql +35 -14
  148. package/dist/core/git-iso.d.ts +3 -2
  149. package/dist/core/git-iso.d.ts.map +1 -1
  150. package/dist/core/git-iso.js +27 -12
  151. package/dist/core/git-iso.js.map +1 -1
  152. package/dist/core/git-membership.d.ts +1 -0
  153. package/dist/core/git-membership.d.ts.map +1 -1
  154. package/dist/core/git-membership.js +87 -26
  155. package/dist/core/git-membership.js.map +1 -1
  156. package/dist/core/git-state.d.ts +8 -1
  157. package/dist/core/git-state.d.ts.map +1 -1
  158. package/dist/core/git-state.js +36 -17
  159. package/dist/core/git-state.js.map +1 -1
  160. package/dist/core/namespace.d.ts +1 -0
  161. package/dist/core/namespace.d.ts.map +1 -1
  162. package/dist/core/namespace.js +20 -0
  163. package/dist/core/namespace.js.map +1 -1
  164. package/dist/core/optimistic-settlement.d.ts +2 -0
  165. package/dist/core/optimistic-settlement.d.ts.map +1 -0
  166. package/dist/core/optimistic-settlement.js +14 -0
  167. package/dist/core/optimistic-settlement.js.map +1 -0
  168. package/dist/core/packet-wire.d.ts +5 -2
  169. package/dist/core/packet-wire.d.ts.map +1 -1
  170. package/dist/core/packet-wire.js +318 -114
  171. package/dist/core/packet-wire.js.map +1 -1
  172. package/dist/core/provider-accounting.d.ts +24 -0
  173. package/dist/core/provider-accounting.d.ts.map +1 -0
  174. package/dist/core/provider-accounting.js +76 -0
  175. package/dist/core/provider-accounting.js.map +1 -0
  176. package/dist/core/results.d.ts +4 -2
  177. package/dist/core/results.d.ts.map +1 -1
  178. package/dist/core/results.js +6 -0
  179. package/dist/core/results.js.map +1 -1
  180. package/dist/core/scheme-types.d.ts +0 -12
  181. package/dist/core/scheme-types.d.ts.map +1 -1
  182. package/dist/core/scheme-types.js.map +1 -1
  183. package/dist/core/search-gate.d.ts +2 -2
  184. package/dist/core/search-gate.d.ts.map +1 -1
  185. package/dist/core/search-gate.js +8 -8
  186. package/dist/core/search-gate.js.map +1 -1
  187. package/dist/core/token-ruler.js +1 -1
  188. package/dist/core/token-ruler.js.map +1 -1
  189. package/dist/core/turn-scheduler.js +1 -1
  190. package/dist/core/turn-scheduler.js.map +1 -1
  191. package/dist/core/worker-ops.sql +4 -4
  192. package/dist/core/workspace-settings.js +1 -1
  193. package/dist/core/workspace-settings.js.map +1 -1
  194. package/dist/digest/Digest.d.ts.map +1 -1
  195. package/dist/digest/Digest.js +349 -76
  196. package/dist/digest/Digest.js.map +1 -1
  197. package/dist/digest/digest.sql +42 -16
  198. package/dist/schemes/Exec.d.ts +4 -4
  199. package/dist/schemes/Exec.d.ts.map +1 -1
  200. package/dist/schemes/Exec.js +167 -134
  201. package/dist/schemes/Exec.js.map +1 -1
  202. package/dist/schemes/ExecOutputScheme.d.ts +4 -5
  203. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  204. package/dist/schemes/ExecOutputScheme.js +12 -20
  205. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  206. package/dist/schemes/File.d.ts +5 -6
  207. package/dist/schemes/File.d.ts.map +1 -1
  208. package/dist/schemes/File.js +35 -41
  209. package/dist/schemes/File.js.map +1 -1
  210. package/dist/schemes/Log.d.ts +16 -5
  211. package/dist/schemes/Log.d.ts.map +1 -1
  212. package/dist/schemes/Log.js +229 -262
  213. package/dist/schemes/Log.js.map +1 -1
  214. package/dist/schemes/Log.sql +50 -27
  215. package/dist/schemes/Prompt.d.ts +3 -4
  216. package/dist/schemes/Prompt.d.ts.map +1 -1
  217. package/dist/schemes/Prompt.js +5 -5
  218. package/dist/schemes/Prompt.js.map +1 -1
  219. package/dist/schemes/Skill.d.ts +4 -5
  220. package/dist/schemes/Skill.d.ts.map +1 -1
  221. package/dist/schemes/Skill.js +3 -3
  222. package/dist/schemes/Skill.js.map +1 -1
  223. package/dist/schemes/Worker.d.ts +7 -7
  224. package/dist/schemes/Worker.d.ts.map +1 -1
  225. package/dist/schemes/Worker.js +99 -84
  226. package/dist/schemes/Worker.js.map +1 -1
  227. package/dist/schemes/_entry-crud.d.ts +2 -2
  228. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  229. package/dist/schemes/_entry-crud.js +29 -10
  230. package/dist/schemes/_entry-crud.js.map +1 -1
  231. package/dist/schemes/_entry-crud.sql +11 -17
  232. package/dist/schemes/_entry-find.d.ts +41 -14
  233. package/dist/schemes/_entry-find.d.ts.map +1 -1
  234. package/dist/schemes/_entry-find.js +159 -166
  235. package/dist/schemes/_entry-find.js.map +1 -1
  236. package/dist/schemes/_entry-find.sql +2 -21
  237. package/dist/schemes/_entry-graph.sql +1 -1
  238. package/dist/schemes/_entry-manifest.d.ts +5 -17
  239. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  240. package/dist/schemes/_entry-manifest.js +28 -24
  241. package/dist/schemes/_entry-manifest.js.map +1 -1
  242. package/dist/schemes/_entry-ops.d.ts +13 -8
  243. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  244. package/dist/schemes/_entry-ops.js +98 -119
  245. package/dist/schemes/_entry-ops.js.map +1 -1
  246. package/dist/schemes/_entry-ops.sql +31 -6
  247. package/dist/schemes/_entry-semantic.d.ts +5 -7
  248. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  249. package/dist/schemes/_entry-semantic.js +10 -31
  250. package/dist/schemes/_entry-semantic.js.map +1 -1
  251. package/dist/schemes/_entry-semantic.sql +2 -2
  252. package/dist/schemes/_entry-send.js +4 -4
  253. package/dist/schemes/_entry-send.js.map +1 -1
  254. package/dist/schemes/_search-index.d.ts.map +1 -1
  255. package/dist/schemes/_search-index.js +16 -6
  256. package/dist/schemes/_search-index.js.map +1 -1
  257. package/dist/schemes/exec-abort.js +2 -2
  258. package/dist/schemes/exec-abort.js.map +1 -1
  259. package/dist/server/BranchBatches.d.ts +1 -0
  260. package/dist/server/BranchBatches.d.ts.map +1 -1
  261. package/dist/server/BranchBatches.js +1 -0
  262. package/dist/server/BranchBatches.js.map +1 -1
  263. package/dist/server/Daemon.d.ts +6 -8
  264. package/dist/server/Daemon.d.ts.map +1 -1
  265. package/dist/server/Daemon.js +238 -68
  266. package/dist/server/Daemon.js.map +1 -1
  267. package/dist/server/DaemonModule.d.ts +3 -3
  268. package/dist/server/DaemonModule.d.ts.map +1 -1
  269. package/dist/server/branch-batch.sql +3 -1
  270. package/dist/server/client-input.d.ts +2 -1
  271. package/dist/server/client-input.d.ts.map +1 -1
  272. package/dist/server/client-input.js +7 -1
  273. package/dist/server/client-input.js.map +1 -1
  274. package/dist/server/drain.sql +8 -7
  275. package/dist/server/envelope.d.ts +0 -2
  276. package/dist/server/envelope.d.ts.map +1 -1
  277. package/dist/server/envelope.js +0 -1
  278. package/dist/server/envelope.js.map +1 -1
  279. package/dist/server/envelope.sql +3 -4
  280. package/dist/server/lifecycle-recovery.sql +44 -1
  281. package/dist/server/logEntry.d.ts +2 -1
  282. package/dist/server/logEntry.d.ts.map +1 -1
  283. package/dist/server/logEntry.js +15 -2
  284. package/dist/server/logEntry.js.map +1 -1
  285. package/dist/server/logEntry.sql +5 -1
  286. package/dist/service.d.ts.map +1 -1
  287. package/dist/service.js +13 -1
  288. package/dist/service.js.map +1 -1
  289. package/migrations/001_schema.sql +730 -221
  290. package/package.json +53 -46
  291. package/dist/core/BudgetOverflow.d.ts +0 -23
  292. package/dist/core/BudgetOverflow.d.ts.map +0 -1
  293. package/dist/core/BudgetOverflow.js +0 -61
  294. package/dist/core/BudgetOverflow.js.map +0 -1
  295. package/dist/core/caps/DbTagCaps.d.ts +0 -10
  296. package/dist/core/caps/DbTagCaps.d.ts.map +0 -1
  297. package/dist/core/caps/DbTagCaps.js +0 -44
  298. package/dist/core/caps/DbTagCaps.js.map +0 -1
  299. package/dist/matrix/live-specimen.d.ts +0 -9
  300. package/dist/matrix/live-specimen.d.ts.map +0 -1
  301. package/dist/matrix/live-specimen.js +0 -37
  302. package/dist/matrix/live-specimen.js.map +0 -1
@@ -5,27 +5,40 @@ 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 { parsePath, 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, projectEditReceipt, } from "../content/index.js";
12
+ import { assertEditBatchReceipt, assertEditReceipt, assertResourceEffects, EditCollision, editReceipt, LineAnchors, LineMarkerOps, MimetypeBinary, PathMimetype, ReadProjector, projectEditReceipt, } from "../content/index.js";
13
+ import DbProjectionCaps from "./caps/DbProjectionCaps.js";
13
14
  import SchemeCtxImpl from "./caps/SchemeCtxImpl.js";
14
15
  import EntryCrud from "../schemes/_entry-crud.js";
15
16
  import EntryOps from "../schemes/_entry-ops.js";
17
+ import EntryFind from "../schemes/_entry-find.js";
16
18
  import WorkspaceSettings from "./workspace-settings.js";
17
19
  import LoopLifecycle from "./LoopLifecycle.js";
20
+ import TerminalResult from "./TerminalResult.js";
18
21
  import Results from "./results.js";
19
22
  import { OperationFailureError } from "./results.js";
20
23
  import EffectPolicy from "../schemes/EffectPolicy.js";
21
- import { CoreSchemeAdapterBase } from "./CoreSchemeServices.js";
24
+ import { CoreSchemeAdapterBase, } from "./CoreSchemeServices.js";
22
25
  import { InvalidOperationResultError, NetworkAddress, } from "@plurnk/plurnk-schemes";
23
26
  import DurableStatement from "./DurableStatement.js";
24
27
  // SPEC {§scheme-surface}: writer must be in target scheme's manifest.writableBy.
25
28
  // OPEN/FOLD/READ/FIND are not gated — they curate the log or read, never mutating an entry.
26
29
  const MUTATING_OPS = new Set(["EDIT", "SEND", "COPY", "MOVE", "EXEC", "KILL", "FORK", "WORK"]);
30
+ const assertClassifyingSignal = (statement) => {
31
+ if (statement.op === "FIND"
32
+ || statement.op === "READ"
33
+ || statement.op === "EDIT"
34
+ || statement.op === "COPY"
35
+ || statement.op === "MOVE"
36
+ || statement.op === "BARE") {
37
+ TagSignal.applied(statement.signal);
38
+ }
39
+ };
27
40
  // Op dispatch ({§op-methods-op-dispatch}): gates (writableBy, loop flags), the
28
- // engine-owned op orchestrations (COPY/MOVE/KILL/SEND/READ-fanout), scheme
41
+ // engine-owned op orchestrations (COPY/MOVE/KILL/SEND), scheme
29
42
  // routing, the durable log write, and the proposal pause.
30
43
  class Dispatcher {
31
44
  static #failure(code, status, detail, fields = {}, extensions = {}) {
@@ -53,7 +66,7 @@ class Dispatcher {
53
66
  #branchCompletionGate;
54
67
  #cancelWorker;
55
68
  #cancelDescendants;
56
- // {§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;
57
70
  // -1 = indefinite). The dispatcher WRITES at park; the daemon's drain park-exit consumes.
58
71
  #parkDeadlines;
59
72
  #searchGate;
@@ -95,8 +108,14 @@ class Dispatcher {
95
108
  const handlerCtx = this.#handlerContext(scheme, ctx);
96
109
  return this.#schemes.manifestFor(scheme)?.category === "data" ? handlerCtx : null;
97
110
  }
98
- async #readEntry(scheme, pathname, ctx) {
111
+ #coreCrud(scheme) {
99
112
  const handler = this.#schemes.get(scheme);
113
+ return handler instanceof CoreSchemeAdapterBase
114
+ ? handler
115
+ : undefined;
116
+ }
117
+ async #readEntry(scheme, pathname, ctx) {
118
+ const handler = this.#coreCrud(scheme);
100
119
  const handlerCtx = this.#entryContext(scheme, ctx);
101
120
  if (typeof handler?.readEntry === "function" && handlerCtx !== null) {
102
121
  return Results.assert(await handler.readEntry(pathname, handlerCtx));
@@ -116,11 +135,16 @@ class Dispatcher {
116
135
  status: result.status,
117
136
  entry: result.entry === null
118
137
  ? null
119
- : { channels: { ...result.entry.channels }, tags: [...result.entry.tags] },
138
+ : {
139
+ channels: { ...result.entry.channels },
140
+ ...(result.entry.attributes === undefined
141
+ ? {}
142
+ : { attributes: { ...result.entry.attributes } }),
143
+ },
120
144
  });
121
145
  }
122
146
  async #writeEntry(scheme, pathname, entry, ctx) {
123
- const handler = this.#schemes.get(scheme);
147
+ const handler = this.#coreCrud(scheme);
124
148
  const handlerCtx = this.#entryContext(scheme, ctx);
125
149
  if (typeof handler?.writeEntry === "function" && handlerCtx !== null) {
126
150
  return Results.assert(await handler.writeEntry(pathname, entry, handlerCtx));
@@ -137,7 +161,7 @@ class Dispatcher {
137
161
  return Results.assert(await caps.write(pathname, entry, scheme === "prompt" ? "worker" : "commons"));
138
162
  }
139
163
  async #deleteEntry(scheme, pathname, ctx) {
140
- const handler = this.#schemes.get(scheme);
164
+ const handler = this.#coreCrud(scheme);
141
165
  const handlerCtx = this.#entryContext(scheme, ctx);
142
166
  if (typeof handler?.deleteEntry === "function" && handlerCtx !== null) {
143
167
  return Results.assert(await handler.deleteEntry(pathname, handlerCtx));
@@ -154,7 +178,7 @@ class Dispatcher {
154
178
  return Results.assert(await caps.delete(pathname, scheme === "prompt" ? "worker" : "commons"));
155
179
  }
156
180
  async #deleteChannel(scheme, pathname, channel, ctx) {
157
- const handler = this.#schemes.get(scheme);
181
+ const handler = this.#coreCrud(scheme);
158
182
  const handlerCtx = this.#entryContext(scheme, ctx);
159
183
  if (typeof handler?.deleteChannel === "function" && handlerCtx !== null) {
160
184
  return Results.assert(await handler.deleteChannel(pathname, channel, handlerCtx));
@@ -189,11 +213,114 @@ class Dispatcher {
189
213
  if (key !== null)
190
214
  target.pathname = key;
191
215
  }
216
+ async #resolveEditAnchors(statements, identity, schemeName, manifest, ctx) {
217
+ const anchored = statements.filter(({ lineMarker }) => LineAnchors.hasAnchor(lineMarker));
218
+ if (anchored.length === 0) {
219
+ return { statements: statements, precondition: null };
220
+ }
221
+ if (manifest.textEditScopes !== true || !manifest.writableBy.includes("model")) {
222
+ return {
223
+ result: _a.#failure("line-anchor-unsupported", 400, `Scheme '${schemeName}' does not support textual EDIT scopes.`, {}, {
224
+ scheme: schemeName,
225
+ operation: "EDIT",
226
+ recovery: "Remove the scope and submit the scheme's complete editable unit.",
227
+ retryable: false,
228
+ }),
229
+ };
230
+ }
231
+ if (identity === null) {
232
+ return {
233
+ 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 }),
234
+ };
235
+ }
236
+ const first = statements[0];
237
+ if (first === undefined)
238
+ return { statements: [], precondition: null };
239
+ const current = await this.#run(schemeName, {
240
+ op: "READ",
241
+ suffix: first.suffix,
242
+ signal: null,
243
+ target: first.target,
244
+ lineMarker: { marks: [1, -1] },
245
+ body: null,
246
+ position: first.position,
247
+ }, ctx);
248
+ if (current.status === 204 || current.status === 404) {
249
+ return { result: EditCollision.result(identity) };
250
+ }
251
+ if (current.status >= 300) {
252
+ return {
253
+ 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}`}`, {}, {
254
+ target: identity,
255
+ upstreamStatus: current.status,
256
+ stage: "mutation-precondition",
257
+ retryable: false,
258
+ }),
259
+ };
260
+ }
261
+ if (current.status !== 200) {
262
+ return { result: EditCollision.result(identity) };
263
+ }
264
+ const content = current.content;
265
+ if (typeof content !== "string") {
266
+ throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ ${current.status} without textual content while validating an EDIT anchor.`);
267
+ }
268
+ const lineAnchors = current.lineAnchors;
269
+ const lineAnchorIdentity = current.lineAnchorIdentity;
270
+ try {
271
+ LineAnchors.assertProjection(content, lineAnchors);
272
+ if (typeof lineAnchorIdentity !== "string" || lineAnchorIdentity.length === 0) {
273
+ throw new TypeError("READ line anchors require their canonical derivation identity.");
274
+ }
275
+ }
276
+ catch (cause) {
277
+ throw new InvalidOperationResultError(`Scheme '${schemeName}' returned READ 200 without its core-owned line-anchor projection.`, { cause });
278
+ }
279
+ const resolved = [];
280
+ const checks = [];
281
+ for (const statement of statements) {
282
+ if (statement.lineMarker === null || !LineAnchors.hasAnchor(statement.lineMarker)) {
283
+ resolved.push(statement);
284
+ continue;
285
+ }
286
+ const resolution = LineAnchors.resolve(lineAnchors, statement.lineMarker);
287
+ if (!resolution.ok) {
288
+ const { anchor, kind } = resolution.failure;
289
+ const invalid = kind === "invalid";
290
+ if (!invalid)
291
+ return { result: EditCollision.result(lineAnchorIdentity) };
292
+ return {
293
+ result: _a.#failure("line-anchor-invalid", 400, `${anchor} is not valid in that EDIT line-coordinate position.`, {}, {
294
+ anchor,
295
+ target: identity,
296
+ recovery: "Use anchors only where the EDIT scope denotes a line.",
297
+ retryable: false,
298
+ }),
299
+ };
300
+ }
301
+ for (const [index, anchor] of statement.lineMarker.marks.entries()) {
302
+ if (typeof anchor !== "string")
303
+ continue;
304
+ const line = resolution.marker.marks[index];
305
+ if (typeof line !== "number") {
306
+ throw new InvalidOperationResultError("An EDIT line anchor did not lower to a numeric line.");
307
+ }
308
+ checks.push({ anchor, line });
309
+ }
310
+ resolved.push({ ...statement, lineMarker: resolution.marker });
311
+ }
312
+ const uniqueChecks = [...new Map(checks.map((check) => [`${check.anchor}:${check.line}`, check])).values()];
313
+ return {
314
+ statements: resolved,
315
+ precondition: { identity: lineAnchorIdentity, checks: uniqueChecks },
316
+ };
317
+ }
192
318
  async prepareEditBatches(statements, context) {
193
319
  const { workspaceId, workerId, loopId, turnId, origin } = context;
194
320
  const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
195
321
  const groups = new Map();
196
322
  for (const statement of statements) {
323
+ assertClassifyingSignal(statement); // {§log-tag-signal}
197
324
  const target = this.#extractTarget(statement.target);
198
325
  await this.#canonColumns(target, workspaceId);
199
326
  const key = JSON.stringify([
@@ -205,11 +332,15 @@ class Dispatcher {
205
332
  ]);
206
333
  const group = groups.get(key);
207
334
  if (group === undefined)
208
- groups.set(key, [statement]);
335
+ groups.set(key, {
336
+ identity: renderTarget(target),
337
+ statements: [statement],
338
+ });
209
339
  else
210
- group.push(statement);
340
+ group.statements.push(statement);
211
341
  }
212
- for (const group of groups.values()) {
342
+ for (const preparedGroup of groups.values()) {
343
+ const group = preparedGroup.statements;
213
344
  const first = group[0];
214
345
  const schemeName = schemeNameOf(first.target);
215
346
  let initial;
@@ -240,8 +371,20 @@ class Dispatcher {
240
371
  }
241
372
  else {
242
373
  try {
243
- const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
244
- initial = Results.assert(await method.call(handler, group, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions)));
374
+ const resolved = await this.#resolveEditAnchors(group, preparedGroup.identity, schemeName, manifest, schemeCtx);
375
+ if ("result" in resolved) {
376
+ initial = resolved.result;
377
+ }
378
+ else {
379
+ const addressedScheme = first.target?.kind === "url" ? first.target.scheme : schemeName;
380
+ const publishedChannel = first.target?.kind === "url"
381
+ ? first.target.fragment ?? manifest.defaultChannel
382
+ : manifest.defaultChannel;
383
+ initial = Results.assert(await method.call(handler, resolved.statements, new SchemeCtxImpl(schemeCtx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, {
384
+ publishedChannel,
385
+ editPrecondition: resolved.precondition,
386
+ })));
387
+ }
245
388
  }
246
389
  catch (err) {
247
390
  if (err instanceof InvalidOperationResultError)
@@ -284,6 +427,7 @@ class Dispatcher {
284
427
  }
285
428
  }
286
429
  async dispatch(context) {
430
+ assertClassifyingSignal(context.statement); // {§log-tag-signal}
287
431
  const result = await this.#dispatchOne(context);
288
432
  if (context.statement.op === "EDIT") {
289
433
  const prepared = this.#preparedEdits.get(context.statement);
@@ -297,31 +441,16 @@ class Dispatcher {
297
441
  return result;
298
442
  }
299
443
  async #dispatchOne(context) {
300
- const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, operationConstraint, } = context;
444
+ const { statement, workspaceId, workerId, loopId, turnId, sequence, origin, onDispatch, } = context;
301
445
  const schemeCtx = this.#buildSchemeCtx({ workspaceId, workerId, loopId, turnId, origin });
302
446
  let result;
303
- let denial = operationConstraint !== undefined
304
- && !operationConstraint.allowedOperations.includes(statement.op)
305
- ? _a.#failure("operation-not-allowed", 409, `${statement.op} was not executed because ${operationConstraint.detail}.`, {}, {
306
- constraint: operationConstraint.code,
307
- allowedOperations: [...operationConstraint.allowedOperations],
308
- retryable: false,
309
- })
310
- : null;
311
- if (denial === null)
312
- denial = this.#checkWritable(statement, origin);
447
+ let curationPlan = null;
448
+ let denial = this.#checkWritable(statement, origin);
313
449
  if (denial === null)
314
450
  denial = await this.#checkFlagsGate(statement, loopId);
315
451
  if (denial !== null) {
316
452
  result = denial;
317
453
  }
318
- else if (this.#readFansOut(statement)) {
319
- // Multi-resource scopes, relation matchers, and acquisition-scheme
320
- // matchers use FIND for selection and write their own rows. An exact
321
- // stored-resource content matcher remains a direct READ, preserving
322
- // content-level outcomes such as 203.
323
- return await this.#handleReadFanout(statement, schemeCtx, { workerId, loopId, turnId, sequence, origin, onDispatch });
324
- }
325
454
  else {
326
455
  // {§scheme-surface-exception-500} Scheme-handler
327
456
  // exceptions become the action-entry's outcome (status 500), not a
@@ -363,7 +492,9 @@ class Dispatcher {
363
492
  result = await this.#handleSendBroadcast(statement, { workspaceId, workerId, loopId, turnId, sequence });
364
493
  }
365
494
  else if (statement.op === "OPEN" || statement.op === "FOLD") {
366
- result = await this.#runLogCuration(statement, schemeCtx);
495
+ const curation = await this.#runLogCuration(statement, schemeCtx);
496
+ result = curation.result;
497
+ curationPlan = curation.plan;
367
498
  }
368
499
  else if (statement.op === "FORK" || statement.op === "WORK") {
369
500
  result = await this.#handleWorkerControl(statement, schemeCtx);
@@ -381,10 +512,8 @@ class Dispatcher {
381
512
  result = this.#handlePlan(statement);
382
513
  }
383
514
  else if (statement.op === "EXEC") {
384
- // EXEC's target slot is `cwd`, not a scheme address.
385
- // Per plurnk.md the op routes unconditionally to the
386
- // exec scheme; the scheme handler reads runtime
387
- // (signal), cwd (target), and command (body).
515
+ // EXEC routes unconditionally to its operation owner. The
516
+ // resolved runtime declaration owns body/target semantics.
388
517
  result = await this.#gatedExec(statement, schemeCtx, loopId, turnId);
389
518
  }
390
519
  else {
@@ -411,19 +540,30 @@ class Dispatcher {
411
540
  // {§join-blocking-collect}
412
541
  if (typeof result.awaitWorker === "string")
413
542
  this.#joinTargets.add(loopId);
414
- const logEntryId = await this.#writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin });
543
+ const logEntryId = await this.#writeLog({
544
+ statement,
545
+ result,
546
+ workspaceId,
547
+ workerId,
548
+ loopId,
549
+ turnId,
550
+ sequence,
551
+ origin,
552
+ curationPlan,
553
+ modelCallId: null,
554
+ });
415
555
  // {§search-gate} — register successful searches AFTER #writeLog stamps the runtime
416
556
  // entry's coordinate onto result.attrs.pathname (the gate's dedup serves from it).
417
557
  if (statement.op === "EXEC" && result.status < 400) {
418
558
  const rt = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
419
- const cmd = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
559
+ const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
420
560
  const attrPath = result.attrs?.pathname;
421
561
  if (typeof attrPath === "string")
422
- this.#searchGate?.registerPending(loopId, turnId, rt, cmd, attrPath);
562
+ this.#searchGate?.registerPending(loopId, turnId, rt, body, attrPath);
423
563
  }
424
564
  onDispatch?.(logEntryId);
425
565
  // Proposal lifecycle (SPEC.md {§engine-rails} + {§methods-proposal-resolve}; {§proposal-202-pauses}). When a
426
- // side-effecting op returns status 202 (a broadcast SEND[202] park is model
566
+ // side-effecting op returns status 202 (a broadcast SEND signal 202 park is model
427
567
  // speech, not a proposal — #isProposal), the entry is written
428
568
  // state='proposed'; dispatch then PAUSES on a per-entry waiter until
429
569
  // resolution arrives via Engine.resolveProposal (from a client-interface resume,
@@ -531,11 +671,49 @@ class Dispatcher {
531
671
  origin: "client",
532
672
  });
533
673
  const schemeCtx = new SchemeCtxImpl(coreCtx, addressedScheme, manifest, this.#liveSubscriptions);
674
+ const resolved = await this.#resolveDataEntryAddress({
675
+ target,
676
+ routedScheme,
677
+ handler,
678
+ manifest,
679
+ ctx: coreCtx,
680
+ schemeCtx,
681
+ });
682
+ if (resolved.address === null)
683
+ return null;
684
+ const rendered = target.kind === "url"
685
+ ? renderTarget({ ...target, fragment: null })
686
+ : renderTarget({ scheme: null, pathname: target.raw, fragment: null });
687
+ if (rendered === null)
688
+ throw new TypeError("Resolved entry target did not render.");
689
+ return {
690
+ ownerId: resolved.address.ownerId,
691
+ scheme: resolved.address.scheme,
692
+ pathname: resolved.address.pathname,
693
+ target: rendered,
694
+ };
695
+ }
696
+ async #resolveDataEntryAddress({ target, routedScheme, handler, manifest, ctx, schemeCtx, }) {
697
+ const addressedScheme = target.kind === "url" ? target.scheme : routedScheme;
698
+ const identityTarget = target.kind === "url"
699
+ ? {
700
+ ...target,
701
+ pathname: PathSyntax.decodeParens(target.pathname),
702
+ fragment: null,
703
+ }
704
+ : { ...target, raw: PathSyntax.decodeParens(target.raw) };
534
705
  const resolved = handler.resolveEntryAddress === undefined
535
- ? { pathname: entryPathnameOf(target), owner: "commons" }
536
- : await handler.resolveEntryAddress(target, schemeCtx);
706
+ ? { pathname: entryPathnameOf(identityTarget), owner: "commons" }
707
+ : await handler.resolveEntryAddress(identityTarget, schemeCtx);
537
708
  if (resolved === null)
538
- return null;
709
+ return { address: null, result: null };
710
+ if ("status" in resolved) {
711
+ const result = Results.assert(resolved);
712
+ if (result.status < 300) {
713
+ throw new InvalidOperationResultError(`Scheme '${routedScheme}' returned a successful operation result instead of an entry address.`);
714
+ }
715
+ return { address: null, result };
716
+ }
539
717
  if (typeof resolved.pathname !== "string") {
540
718
  throw new TypeError(`Scheme '${routedScheme}' returned an invalid entry pathname.`);
541
719
  }
@@ -547,10 +725,10 @@ class Dispatcher {
547
725
  ownerId = resolved.ownerId;
548
726
  }
549
727
  else if (resolved.owner === "worker") {
550
- ownerId = workerId;
728
+ ownerId = ctx.workerId;
551
729
  }
552
730
  else if (resolved.owner === "commons") {
553
- ownerId = await Owner.commonsId(this.#db, workspaceId);
731
+ ownerId = await Owner.commonsId(this.#db, ctx.workspaceId);
554
732
  }
555
733
  else {
556
734
  throw new TypeError(`Scheme '${routedScheme}' returned an invalid entry owner.`);
@@ -558,16 +736,48 @@ class Dispatcher {
558
736
  if (!Number.isSafeInteger(ownerId) || ownerId < 1) {
559
737
  throw new TypeError(`Scheme '${routedScheme}' returned an invalid entry owner id.`);
560
738
  }
561
- const rendered = target.kind === "url"
562
- ? renderTarget({ ...target, fragment: null })
563
- : renderTarget({ scheme: null, pathname: target.raw, fragment: null });
564
- if (rendered === null)
565
- throw new TypeError("Resolved entry target did not render.");
566
739
  return {
567
- ownerId,
568
- scheme: manifest.storedScheme ?? addressedScheme,
569
- pathname: resolved.pathname,
570
- target: rendered,
740
+ address: {
741
+ ownerId,
742
+ scheme: manifest.storedScheme ?? addressedScheme,
743
+ pathname: resolved.pathname,
744
+ },
745
+ result: null,
746
+ };
747
+ }
748
+ async #prepareDataRepresentation({ target, routedScheme, handler, manifest, ctx, schemeCtx, publishedChannel, }) {
749
+ const resolved = await this.#resolveDataEntryAddress({
750
+ target,
751
+ routedScheme,
752
+ handler,
753
+ manifest,
754
+ ctx,
755
+ schemeCtx,
756
+ });
757
+ if (resolved.address === null
758
+ || resolved.result !== null
759
+ || typeof handler.prepareRepresentation !== "function") {
760
+ return resolved;
761
+ }
762
+ const address = resolved.address;
763
+ const selectionNeutralTarget = target.kind === "url"
764
+ ? {
765
+ ...target,
766
+ raw: renderTarget({ ...target, fragment: null }) ?? target.raw,
767
+ fragment: null,
768
+ }
769
+ : target;
770
+ const preparationCtx = new SchemeCtxImpl(ctx, target.kind === "url" ? target.scheme : routedScheme, manifest, this.#liveSubscriptions, {
771
+ ownerId: address.ownerId,
772
+ publishedChannel,
773
+ });
774
+ const prepared = Results.assertRepresentationPreparation(await handler.prepareRepresentation({
775
+ target: selectionNeutralTarget,
776
+ pathname: address.pathname,
777
+ }, preparationCtx));
778
+ return {
779
+ address,
780
+ result: prepared.status === 200 ? null : prepared,
571
781
  };
572
782
  }
573
783
  // An accepted EXEC reads a non-file source through the same registered
@@ -611,8 +821,8 @@ class Dispatcher {
611
821
  return null;
612
822
  if (statement.op === "SEND" && statement.target === null)
613
823
  return null;
614
- // EXEC's target slot is `cwd`, not a scheme address. The op's
615
- // authority always belongs to the exec scheme regardless of cwd.
824
+ // EXEC's operation authority always belongs to the exec scheme;
825
+ // runtime-specific resource authority is gated separately below.
616
826
  if (statement.op === "EXEC") {
617
827
  return this.#denyIfDisallowed("exec", origin);
618
828
  }
@@ -644,8 +854,8 @@ class Dispatcher {
644
854
  // the prior durable digest and the per-turn cap refuses without execution.
645
855
  async #gatedExec(statement, ctx, loopId, turnId) {
646
856
  const runtime = ("signal" in statement && typeof statement.signal === "string" && statement.signal.length > 0) ? statement.signal : "sh";
647
- const command = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
648
- const verdict = this.#searchGate?.check(loopId, turnId, runtime, command) ?? { verdict: "pass" };
857
+ const body = ("body" in statement && typeof statement.body === "string") ? statement.body : "";
858
+ const verdict = this.#searchGate?.check(loopId, turnId, runtime, body) ?? { verdict: "pass" };
649
859
  if (verdict.verdict === "capped") {
650
860
  return _a.#failure("search-limit-reached", 429, `This turn already used its ${verdict.cap} permitted searches.`, {}, {
651
861
  searchLimit: verdict.cap,
@@ -752,12 +962,17 @@ class Dispatcher {
752
962
  if (statement.op === "COPY" || statement.op === "MOVE") {
753
963
  return check(statement.target) ?? check(statement.body?.target ?? null);
754
964
  }
755
- // {§exec-target-routing} — the operation owner and a non-file source
756
- // are independent authorities; local/file targets stay executor-local.
965
+ // {§exec-target-routing} — only a runtime-declared resource target adds
966
+ // source authority. Literal identifiers and path targets stay executor-local.
757
967
  if (statement.op === "EXEC") {
758
968
  const operationDenial = checkScheme("exec");
759
969
  if (operationDenial !== null)
760
970
  return operationDenial;
971
+ const requested = typeof statement.signal === "string" ? statement.signal : "";
972
+ const runtime = requested === "" ? "sh" : requested;
973
+ const targetKind = this.#executors()?.entry(runtime)?.invocation.target?.kind;
974
+ if (targetKind !== "resource")
975
+ return null;
761
976
  const sourceScheme = schemeNameOf(statement.target);
762
977
  return sourceScheme === null || sourceScheme === "file" ? null : checkScheme(sourceScheme);
763
978
  }
@@ -829,7 +1044,7 @@ class Dispatcher {
829
1044
  if (statement.op === "FORK") {
830
1045
  // Branch the current worker's log into a named sister.
831
1046
  const branchWorkerId = await Fork.fork(this.#db, ctx.workerId, name);
832
- await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: branchWorkerId, prompt, flags });
1047
+ await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: branchWorkerId, prompt, flags, parentLoopId: ctx.loopId });
833
1048
  return { status: 200, body: name };
834
1049
  }
835
1050
  // WORK — a fresh worker sister named <name>.
@@ -838,7 +1053,7 @@ class Dispatcher {
838
1053
  });
839
1054
  if (row === undefined)
840
1055
  throw new Error("worker spawn: worker insert returned no row");
841
- await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: row.id, prompt, flags });
1056
+ await ctx.injectWorker({ workspaceId: ctx.workspaceId, workerId: row.id, prompt, flags, parentLoopId: ctx.loopId });
842
1057
  return { status: 200, body: name };
843
1058
  }
844
1059
  async #handleCopy(statement, ctx) {
@@ -870,11 +1085,19 @@ class Dispatcher {
870
1085
  retryable: false,
871
1086
  });
872
1087
  }
1088
+ const sourceMarks = statement.lineMarker?.marks;
1089
+ const sourceLineMarker = sourceMarks?.length === 2
1090
+ && sourceMarks[0] === 1
1091
+ && sourceMarks[1] === -1
1092
+ ? null
1093
+ : statement.lineMarker;
873
1094
  return this.#moveOrchestration({
874
1095
  statement,
875
1096
  source: {
876
1097
  target: statement.target,
877
- lineMarker: statement.lineMarker,
1098
+ // Canonicalize only the execution selection. #writeLog retains
1099
+ // the authored marker as operation evidence. {§move-canonical-whole-source}
1100
+ lineMarker: sourceLineMarker,
878
1101
  },
879
1102
  destination: statement.body,
880
1103
  ctx,
@@ -949,185 +1172,48 @@ class Dispatcher {
949
1172
  // carries the delete target to review (#isProposal fires on 202). Plurnk-internal deletes execute inline.
950
1173
  return this.#deleteEntry(schemeName, entryPathnameOf(path), ctx);
951
1174
  }
952
- // A multi-resource READ uses FIND as its resource selector, then writes one
953
- // READ row per selected resource. Match coordinates remain metadata on that
954
- // resource; they never create extra rows or replace its content.
955
- #readFansOut(statement) {
956
- if (statement.op !== "READ")
957
- return false;
958
- const body = statement.body;
959
- if (body?.dialect === "semantic" || body?.dialect === "graph")
960
- return true;
961
- const schemeName = schemeNameOf(statement.target);
962
- const handler = schemeName === null
963
- ? undefined
964
- : this.#schemes.get(schemeName);
965
- if (body !== null && typeof handler?.prepareFind === "function")
966
- return true;
967
- const t = statement.target;
968
- const p = t === null ? "" : (t.kind === "url" ? t.pathname : t.raw);
969
- if (PathSyntax.hasGlob(p))
970
- return true;
971
- return p.endsWith("/") && schemeName !== null && this.#schemes.manifestFor(schemeName)?.folderScopes === true;
972
- }
973
- static #retargetRead(statement, pathname, lineMarker, body) {
974
- const t = statement.target;
975
- const target = t !== null && t.kind === "url"
976
- ? { ...t, pathname, raw: `${t.scheme}://${pathname}` }
977
- : { ...t, raw: pathname };
978
- return { ...statement, target: target, lineMarker, body };
979
- }
980
- // READ scope always projects text. Semantic READ reserves a leading decimal
981
- // for similarity selection; any remaining integers are the text projection.
982
- // FIND retains its own public result-range interpretation.
983
- static #readMarkers(statement) {
984
- const marker = statement.lineMarker;
985
- if (statement.body?.dialect !== "semantic") {
986
- return { selection: null, projection: marker };
987
- }
988
- if (marker === null)
989
- return { selection: null, projection: null };
990
- const [first, ...rest] = marker.marks;
991
- if (Number.isInteger(first)) {
992
- return { selection: null, projection: marker };
993
- }
994
- return {
995
- selection: { marks: [first] },
996
- projection: rest.length === 0 ? null : { marks: rest },
997
- };
998
- }
999
- #standardEntryManifest(schemeName, statement) {
1000
- const handler = this.#schemes.get(schemeName);
1001
- if (typeof handler?.find === "function")
1002
- return null;
1003
- const target = statement.target;
1004
- if (target === null || target.kind !== "url" || target.scheme === null)
1005
- return null;
1006
- const manifest = this.#schemes.manifestFor(schemeName);
1007
- return manifest === undefined ? null : { ...manifest, name: target.scheme };
1008
- }
1009
- static #storedEntryRead(statement, pathname, lineMarker, body, storage) {
1010
- const target = statement.target;
1011
- if (target === null || target.kind !== "url")
1012
- return _a.#retargetRead(statement, pathname, lineMarker, body);
1013
- if (NetworkAddress.supports(target.scheme)) {
1014
- const parsed = parsePath(renderAddress(target.scheme, pathname));
1015
- if (parsed === null || parsed.kind !== "url") {
1016
- throw new Error(`failed to reconstruct network entry target for ${target.scheme}:${pathname}`);
1017
- }
1018
- const reconstructed = { ...parsed, fragment: target.fragment };
1019
- const raw = renderTarget(reconstructed);
1020
- if (raw === null)
1021
- throw new Error(`failed to render network entry target for ${target.scheme}:${pathname}`);
1022
- return {
1023
- ...statement,
1024
- target: { ...reconstructed, raw },
1025
- lineMarker,
1026
- body,
1027
- };
1028
- }
1029
- const hostPrefix = target.hostname === null ? null : `/${target.hostname}`;
1030
- const displayPath = hostPrefix !== null && pathname.startsWith(`${hostPrefix}/`)
1031
- ? pathname.slice(hostPrefix.length)
1032
- : pathname;
1033
- return {
1034
- ...statement,
1035
- target: storage
1036
- ? { ...target, hostname: null, pathname, raw: `${target.scheme}://${pathname}` }
1037
- : { ...target, pathname: displayPath, raw: `${target.scheme}://${target.hostname ?? ""}${displayPath}` },
1038
- lineMarker,
1039
- body,
1040
- };
1041
- }
1042
- async #handleReadFanout(statement, ctx, ids) {
1043
- const { workerId, loopId, turnId, sequence, origin, onDispatch } = ids;
1044
- const schemeName = schemeNameOf(statement.target);
1045
- const read = statement;
1046
- const markers = _a.#readMarkers(read);
1047
- const findStatement = {
1048
- ...statement,
1049
- op: "FIND",
1050
- lineMarker: markers.selection,
1051
- };
1052
- const standardEntryManifest = schemeName === null ? null : this.#standardEntryManifest(schemeName, statement);
1053
- const found = await this.#run(schemeName, findStatement, ctx);
1054
- const matches = found.matches ?? [];
1055
- const omittedItems = typeof found.omittedItems === "number" ? found.omittedItems : null;
1056
- if (omittedItems !== null) {
1057
- const maximumItems = typeof found.maximumItems === "number" ? found.maximumItems : null;
1058
- const result = _a.#failure("read-materialization-too-large", 413, `READ selected ${omittedItems} resources, exceeding the materialization limit${maximumItems === null ? "" : ` of ${maximumItems}`}.`, {}, {
1059
- resources: omittedItems,
1060
- ...(maximumItems === null ? {} : { maximumResources: maximumItems }),
1061
- stage: "materialization",
1062
- recovery: "Narrow the target or matcher.",
1063
- retryable: false,
1064
- });
1065
- const readRowId = await this.#writeLog({ statement, result, workspaceId: ctx.workspaceId, workerId, loopId, turnId, sequence, origin });
1066
- onDispatch?.(readRowId);
1067
- return { ...result, rowsWritten: 1 };
1068
- }
1069
- // Find-less scheme, a matcher/scope error, or zero matches → a single row carrying the
1070
- // status, exactly like a non-fanned READ. The model sees the empty/failed result, not silence.
1071
- if (found.status !== 200 || matches.length === 0) {
1072
- const result = found.status === 200
1073
- ? { status: 204, ...(read.body === null ? {} : { matches: [] }) }
1074
- : found;
1075
- const id = await this.#writeLog({ statement, result, workspaceId: ctx.workspaceId, workerId, loopId, turnId, sequence, origin });
1076
- onDispatch?.(id);
1077
- return { ...result, rowsWritten: 1 };
1078
- }
1079
- const fannedStatuses = [];
1080
- const scopeNormalizations = [];
1081
- let written = 0;
1082
- for (const match of matches) {
1083
- ctx.signal?.throwIfAborted();
1084
- const executionStatement = standardEntryManifest === null
1085
- ? _a.#retargetRead(statement, match.pathname, markers.projection, null)
1086
- : _a.#storedEntryRead(statement, match.pathname, markers.projection, null, true);
1087
- const readResult = standardEntryManifest === null
1088
- ? await this.#run(schemeName, executionStatement, ctx)
1089
- : await EntryOps.readWorkspaceEntry(executionStatement, ctx, standardEntryManifest);
1090
- const result = read.body === null
1091
- ? readResult
1092
- : { ...readResult, matches: match.matches };
1093
- const loggedStatement = standardEntryManifest === null
1094
- ? _a.#retargetRead(statement, match.pathname, statement.lineMarker, read.body)
1095
- : _a.#storedEntryRead(statement, match.pathname, statement.lineMarker, read.body, false);
1096
- const id = await this.#writeLog({ statement: loggedStatement, result, workspaceId: ctx.workspaceId, workerId, loopId, turnId, sequence: sequence + written, origin });
1097
- onDispatch?.(id);
1098
- fannedStatuses.push(result.status);
1099
- scopeNormalizations.push(...(result.scopeNormalizations ?? []));
1100
- written++;
1101
- }
1102
- return {
1103
- status: 200,
1104
- rowsWritten: written,
1105
- fannedStatuses,
1106
- ...(scopeNormalizations.length === 0 ? {} : { scopeNormalizations }),
1107
- };
1108
- }
1109
- // {§model-entry} — mirror an admitted model emission back as an actionless `model` log row, so
1110
- // the model can inspect and curate its own prior behavior.
1111
- // Born FOLDED by default (budget-neutral until OPENed); the turn-0 exemplar passes folded:false
1112
- // (born open — the one worked example the model orients on, thinning the grammar). text/vnd.plurnk.
1113
- async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, origin = "model", reasoningItems }) {
1175
+ async #writeActionlessEntry({ verbatim, workerId, loopId, turnId, sequence, origin, kind, folded, modelCallId = null, attrs = {} }) {
1114
1176
  const row = await this.#db.engine_insert_log_entry.get({
1115
1177
  worker_id: workerId, loop_id: loopId, turn_id: turnId, sequence,
1116
- origin, source: null, op: "model", suffix: "", signal: null,
1178
+ origin, source: null, model_call_id: modelCallId,
1179
+ op: null, suffix: "", signal: null,
1117
1180
  scheme: null, username: null, password: null, hostname: null, port: null,
1118
1181
  pathname: null, query: null, fragment: null, lineMarker: null,
1119
1182
  tx: "", mimetype_tx: "text/vnd.plurnk",
1120
1183
  rx: JSON.stringify({ content: verbatim, mimetype: "text/vnd.plurnk" }),
1121
1184
  mimetype_rx: "application/json",
1122
1185
  status_rx: 200, tokens: this.#tokenize(verbatim), state: "resolved", outcome: null,
1123
- attrs: reasoningItems !== undefined && reasoningItems.length > 0 ? JSON.stringify({ reasoning: reasoningItems }) : "{}",
1186
+ attrs: JSON.stringify({ ...attrs, kind }),
1124
1187
  });
1125
1188
  if (row === undefined)
1126
- throw new Error("Dispatcher.writeModelEntry: insert returned no row");
1189
+ throw new Error("Dispatcher.#writeActionlessEntry: insert returned no row");
1127
1190
  if (folded)
1128
1191
  await this.#db.engine_fold_log_entry.run({ id: row.id });
1129
1192
  return row.id;
1130
1193
  }
1194
+ // {§worker-initialization-entry} — the kernel-authored, born-open worked
1195
+ // turn that initializes a worker's log without impersonating model output.
1196
+ async writeInitializationEntry({ verbatim, workerId, loopId, turnId, sequence }) {
1197
+ return this.#writeActionlessEntry({
1198
+ verbatim, workerId, loopId, turnId, sequence,
1199
+ origin: "plurnk", kind: "initialization", folded: false,
1200
+ });
1201
+ }
1202
+ // {§model-entry} — mirror a model emission back as an actionless log row, so
1203
+ // the model can inspect and curate its own prior behavior. Ordinary admitted
1204
+ // emissions are folded; the bounded invalid-emission lifeline is the one
1205
+ // born-open rejected item under {§invalid-emission-attempts} and labels
1206
+ // that admission fact structurally.
1207
+ async writeModelEntry({ verbatim, workerId, loopId, turnId, sequence, folded, modelCallId, admission = "accepted", reasoningItems }) {
1208
+ return this.#writeActionlessEntry({
1209
+ verbatim, workerId, loopId, turnId, sequence,
1210
+ origin: "model", kind: "model_emission", folded, modelCallId,
1211
+ attrs: {
1212
+ ...(admission === "rejected" ? { admission } : {}),
1213
+ ...(reasoningItems !== undefined && reasoningItems.length > 0 ? { reasoning: reasoningItems } : {}),
1214
+ },
1215
+ });
1216
+ }
1131
1217
  // PLAN — the model's intended-goals op. An ordinary op: dispatched like any
1132
1218
  // other, logged, and broadcast to the client as a log entry — but a pure no-op for
1133
1219
  // state (PLAN ∉ MUTATING_OPS); its body serializes into the log row's tx, no effect.
@@ -1136,6 +1222,20 @@ class Dispatcher {
1136
1222
  throw new Error("unreachable");
1137
1223
  return { status: 200 };
1138
1224
  }
1225
+ // {§bare-inference} Provider work is prepared concurrently by Engine; the
1226
+ // dispatcher owns only the ordinary operation-result commit and notification.
1227
+ async recordBareResult(context, result, modelCallId) {
1228
+ assertClassifyingSignal(context.statement);
1229
+ Results.assert(result);
1230
+ const logEntryId = await this.#writeLog({
1231
+ ...context,
1232
+ result,
1233
+ curationPlan: null,
1234
+ modelCallId,
1235
+ });
1236
+ context.onDispatch?.(logEntryId);
1237
+ return result;
1238
+ }
1139
1239
  static #isDispatchResult(value) {
1140
1240
  return "status" in value;
1141
1241
  }
@@ -1201,8 +1301,81 @@ class Dispatcher {
1201
1301
  manifest,
1202
1302
  };
1203
1303
  }
1204
- async #selectSource(selection, ctx) {
1205
- const read = await this.#readEntry(selection.scheme, selection.pathname, ctx);
1304
+ #resolveResourceLineMarker(selection, content, operation) {
1305
+ if (!LineAnchors.hasAnchor(selection.lineMarker)) {
1306
+ return {
1307
+ selection: {
1308
+ ...selection,
1309
+ lineMarker: selection.lineMarker,
1310
+ },
1311
+ precondition: null,
1312
+ };
1313
+ }
1314
+ const target = this.#resourceAddress(selection);
1315
+ if (selection.manifest.textEditScopes !== true || !selection.manifest.writableBy.includes("model")) {
1316
+ return {
1317
+ result: _a.#failure("line-anchor-unsupported", 400, `The representation at ${target} does not publish line anchors.`, {}, {
1318
+ operation,
1319
+ target,
1320
+ recovery: "Use numeric text coordinates.",
1321
+ retryable: false,
1322
+ }),
1323
+ };
1324
+ }
1325
+ const resolution = LineAnchors.resolve(LineAnchors.tokens(target, content), selection.lineMarker);
1326
+ if (!resolution.ok) {
1327
+ if (resolution.failure.kind === "invalid") {
1328
+ return {
1329
+ result: _a.#failure("line-anchor-invalid", 400, "A line anchor appeared outside a line-coordinate position.", {}, {
1330
+ operation,
1331
+ target,
1332
+ recovery: "Use anchors only for L, SL, or EL; columns remain numeric.",
1333
+ retryable: false,
1334
+ }),
1335
+ };
1336
+ }
1337
+ return {
1338
+ result: _a.#failure("line-anchor-collision", 409, `${operation} coordinates collided with current content at ${target}.`, {}, {
1339
+ operation,
1340
+ target,
1341
+ recovery: `READ ${target} again and retry against its current coordinates.`,
1342
+ retryable: true,
1343
+ }),
1344
+ };
1345
+ }
1346
+ return {
1347
+ selection: { ...selection, lineMarker: resolution.marker },
1348
+ precondition: {
1349
+ identity: target,
1350
+ checks: LineAnchors.checks(selection.lineMarker, resolution.marker),
1351
+ },
1352
+ };
1353
+ }
1354
+ async #selectSource(selection, ctx, operation) {
1355
+ const handler = this.#schemes.get(selection.scheme);
1356
+ if (handler === undefined) {
1357
+ throw new InvalidOperationResultError(`Resolved COPY/MOVE source scheme '${selection.scheme}' is no longer registered.`);
1358
+ }
1359
+ const addressedScheme = selection.target.kind === "url"
1360
+ ? selection.target.scheme
1361
+ : selection.scheme;
1362
+ const schemeCtx = new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, { publishedChannel: selection.channel });
1363
+ const prepared = await this.#prepareDataRepresentation({
1364
+ target: selection.target,
1365
+ routedScheme: selection.scheme,
1366
+ handler,
1367
+ manifest: selection.manifest,
1368
+ ctx,
1369
+ schemeCtx,
1370
+ publishedChannel: selection.channel,
1371
+ });
1372
+ if (prepared.result !== null)
1373
+ return prepared.result;
1374
+ if (prepared.address === null) {
1375
+ return _a.#failure("entry-not-found", 404, `No entry exists at ${this.#resourceAddress(selection)}.`, {}, { target: this.#resourceAddress(selection) });
1376
+ }
1377
+ const storageAddress = prepared.address;
1378
+ const read = await EntryCrud.readEntry(storageAddress.pathname, ctx, storageAddress.scheme, storageAddress.ownerId);
1206
1379
  if (read.status >= 400)
1207
1380
  return read;
1208
1381
  if (read.status !== 200 || read.entry === null) {
@@ -1210,13 +1383,16 @@ class Dispatcher {
1210
1383
  }
1211
1384
  const selected = read.entry.channels[selection.channel];
1212
1385
  if (selected === undefined) {
1213
- return _a.#failure("channel-not-found", 404, `No channel named #${selection.channel} exists at ${renderAddress(selection.scheme, selection.identityPathname)}.`, {}, {
1214
- target: renderAddress(selection.scheme, selection.identityPathname),
1386
+ return _a.#failure("channel-not-found", 404, `No channel named #${selection.channel} exists at ${renderAddress(storageAddress.scheme, storageAddress.pathname)}.`, {}, {
1387
+ target: renderAddress(storageAddress.scheme, storageAddress.pathname),
1215
1388
  requestedChannel: selection.channel,
1216
1389
  availableChannels: Object.keys(read.entry.channels),
1217
1390
  retryable: false,
1218
1391
  });
1219
1392
  }
1393
+ const resolvedMarker = this.#resolveResourceLineMarker(selection, selected.content, operation);
1394
+ if ("result" in resolvedMarker)
1395
+ return resolvedMarker.result;
1220
1396
  let content = selected.content;
1221
1397
  let scopeNormalizations;
1222
1398
  if (await MimetypeBinary.isBinaryMimetype(selected.mimetype, ctx.mimetypes)) {
@@ -1227,17 +1403,23 @@ class Dispatcher {
1227
1403
  retryable: false,
1228
1404
  });
1229
1405
  }
1230
- if (selection.lineMarker !== null) {
1231
- const sliced = LineMarkerOps.sliceLinesRaw(content, selection.lineMarker);
1406
+ if (resolvedMarker.selection.lineMarker !== null) {
1407
+ const sliced = LineMarkerOps.sliceLinesRaw(content, resolvedMarker.selection.lineMarker);
1232
1408
  if (sliced.status !== 200)
1233
1409
  return Results.assert(sliced);
1234
1410
  content = sliced.text ?? "";
1235
1411
  scopeNormalizations = sliced.scopeNormalizations;
1236
1412
  }
1413
+ if (selected.producerResult !== undefined && selected.producerResult.status >= 400) {
1414
+ return Results.assert(selected.producerResult);
1415
+ }
1237
1416
  return {
1238
- ...selection,
1417
+ ...resolvedMarker.selection,
1418
+ storageAddress,
1239
1419
  content,
1420
+ completeContent: selected.content,
1240
1421
  mimetype: selected.mimetype,
1422
+ lineAnchorPrecondition: resolvedMarker.precondition,
1241
1423
  ...(scopeNormalizations === undefined ? {} : { scopeNormalizations }),
1242
1424
  };
1243
1425
  }
@@ -1252,6 +1434,18 @@ class Dispatcher {
1252
1434
  ],
1253
1435
  });
1254
1436
  }
1437
+ static #mergeLineAnchorPreconditions(...values) {
1438
+ const present = values.filter((value) => value !== null);
1439
+ if (present.length === 0)
1440
+ return null;
1441
+ const identity = present[0].identity;
1442
+ if (present.some((value) => value.identity !== identity)) {
1443
+ throw new TypeError("Line-anchor preconditions for one edit batch must share a resource identity.");
1444
+ }
1445
+ const checks = [...new Map(present.flatMap(({ checks: valueChecks }) => valueChecks)
1446
+ .map((check) => [`${check.anchor}:${check.line}`, check])).values()];
1447
+ return { identity, checks };
1448
+ }
1255
1449
  #resourceAddress(selection) {
1256
1450
  const address = renderTarget({
1257
1451
  scheme: selection.scheme === "file" ? null : selection.scheme,
@@ -1471,7 +1665,7 @@ class Dispatcher {
1471
1665
  },
1472
1666
  };
1473
1667
  }
1474
- async #invokeEditBatch(selection, edits, ctx) {
1668
+ async #invokeEditBatch(selection, edits, ctx, precondition = null) {
1475
1669
  const handler = this.#schemes.get(selection.scheme);
1476
1670
  if (typeof handler?.editBatch !== "function") {
1477
1671
  return _a.#failure("operation-not-implemented", 501, `Scheme '${selection.scheme}' does not implement EDIT batches.`, {}, {
@@ -1480,10 +1674,10 @@ class Dispatcher {
1480
1674
  retryable: false,
1481
1675
  });
1482
1676
  }
1483
- const statements = edits.map(({ marker, body, tags, position }) => ({
1677
+ const statements = edits.map(({ marker, body, position }) => ({
1484
1678
  op: "EDIT",
1485
1679
  suffix: "",
1486
- signal: tags,
1680
+ signal: null,
1487
1681
  target: selection.target,
1488
1682
  lineMarker: marker,
1489
1683
  body,
@@ -1493,7 +1687,10 @@ class Dispatcher {
1493
1687
  ? selection.target.scheme
1494
1688
  : selection.scheme;
1495
1689
  try {
1496
- const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions)));
1690
+ const result = Results.assert(await handler.editBatch(statements, new SchemeCtxImpl(ctx, addressedScheme, selection.manifest, this.#liveSubscriptions, {
1691
+ publishedChannel: selection.channel,
1692
+ editPrecondition: precondition,
1693
+ })));
1497
1694
  return this.#withProposalRoute(result, selection);
1498
1695
  }
1499
1696
  catch (err) {
@@ -1529,7 +1726,6 @@ class Dispatcher {
1529
1726
  retryable: false,
1530
1727
  });
1531
1728
  }
1532
- const tags = Array.isArray(statement.signal) ? statement.signal : [];
1533
1729
  const destinationEffect = this.#pendingEffect(destination, destinationChannel === undefined ? "create" : "update");
1534
1730
  if (destination.lineMarker !== null) {
1535
1731
  if (destinationChannel === undefined) {
@@ -1545,13 +1741,15 @@ class Dispatcher {
1545
1741
  retryable: false,
1546
1742
  });
1547
1743
  }
1548
- const edited = await this.#invokeEditBatch(destination, [{
1549
- marker: destination.lineMarker,
1744
+ const resolvedMarker = this.#resolveResourceLineMarker(destination, destinationChannel.content, statement.op);
1745
+ if ("result" in resolvedMarker)
1746
+ return resolvedMarker.result;
1747
+ const edited = await this.#invokeEditBatch(resolvedMarker.selection, [{
1748
+ marker: resolvedMarker.selection.lineMarker,
1550
1749
  body: source.content,
1551
- tags,
1552
1750
  position: statement.position,
1553
- }], ctx);
1554
- return this.#finalizeEffects(edited, destination, [destinationEffect]);
1751
+ }], ctx, resolvedMarker.precondition);
1752
+ return this.#finalizeEffects(edited, resolvedMarker.selection, [destinationEffect]);
1555
1753
  }
1556
1754
  if (destinationChannel !== undefined
1557
1755
  && destinationChannel.content !== source.content) {
@@ -1560,10 +1758,7 @@ class Dispatcher {
1560
1758
  retryable: false,
1561
1759
  });
1562
1760
  }
1563
- const priorTags = existing?.tags ?? [];
1564
- const mergedTags = [...new Set([...priorTags, ...tags])];
1565
- const addsTags = mergedTags.length !== priorTags.length;
1566
- if (destinationChannel !== undefined && !addsTags)
1761
+ if (destinationChannel !== undefined)
1567
1762
  return { status: 304 };
1568
1763
  const channels = {
1569
1764
  ...(existing?.channels ?? {}),
@@ -1572,15 +1767,18 @@ class Dispatcher {
1572
1767
  mimetype: source.mimetype,
1573
1768
  },
1574
1769
  };
1575
- const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels, tags: mergedTags }, ctx);
1770
+ const written = await this.#writeEntry(destination.scheme, destination.pathname, { channels }, ctx);
1576
1771
  const exactWritten = Results.assert(written);
1772
+ const parseIssues = exactWritten.status === 200 || exactWritten.status === 201
1773
+ ? await new DbProjectionCaps(ctx).parseIssues(source.content, source.mimetype)
1774
+ : undefined;
1577
1775
  const materialized = source.lineMarker === null
1578
1776
  || (exactWritten.status !== 200 && exactWritten.status !== 201 && exactWritten.status !== 202)
1579
1777
  ? exactWritten
1580
- : _a.#withEditMaterialization(exactWritten, editReceipt(destinationChannel?.content ?? "", source.content, [{
1778
+ : _a.#withEditMaterialization(exactWritten, editReceipt("", source.content, [{
1581
1779
  marker: { marks: [1, -1] },
1582
1780
  body: source.content,
1583
- }]));
1781
+ }], parseIssues));
1584
1782
  return this.#finalizeEffects(materialized, destination, [destinationEffect]);
1585
1783
  }
1586
1784
  async #copyOrchestration({ statement, source, destination, ctx, }) {
@@ -1590,13 +1788,13 @@ class Dispatcher {
1590
1788
  const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
1591
1789
  if (_a.#isDispatchResult(resolvedDestination))
1592
1790
  return resolvedDestination;
1593
- const selected = await this.#selectSource(resolvedSource, ctx);
1791
+ const selected = await this.#selectSource(resolvedSource, ctx, "COPY");
1594
1792
  if (_a.#isDispatchResult(selected))
1595
1793
  return selected;
1596
1794
  const result = await this.#writeDestination(statement, selected, resolvedDestination, ctx);
1597
1795
  return _a.#prependScopeNormalizations(result, selected.scopeNormalizations);
1598
1796
  }
1599
- #deferredMoveSource(source, destination) {
1797
+ #deferredMoveSource(source, destination, lineAnchorPrecondition) {
1600
1798
  return {
1601
1799
  target: source.target,
1602
1800
  lineMarker: source.lineMarker,
@@ -1604,6 +1802,7 @@ class Dispatcher {
1604
1802
  pathname: source.pathname,
1605
1803
  channel: source.channel,
1606
1804
  destination: this.#resourceAddress(destination),
1805
+ lineAnchorPrecondition,
1607
1806
  };
1608
1807
  }
1609
1808
  #moveFailureAfterDestination(result, destination, destinationEffects) {
@@ -1625,7 +1824,7 @@ class Dispatcher {
1625
1824
  });
1626
1825
  return _a.#withCombinedEffects(failed, destinationEffects);
1627
1826
  }
1628
- async #removeMoveSource(statement, source, ctx) {
1827
+ async #removeMoveSource(statement, source, ctx, lineAnchorPrecondition = null) {
1629
1828
  const effect = this.#pendingEffect(source, source.lineMarker === null ? "delete" : "update");
1630
1829
  if (source.lineMarker === null) {
1631
1830
  const deleted = await this.#deleteChannel(source.scheme, source.pathname, source.channel, ctx);
@@ -1634,9 +1833,8 @@ class Dispatcher {
1634
1833
  const edited = await this.#invokeEditBatch(source, [{
1635
1834
  marker: source.lineMarker,
1636
1835
  body: "",
1637
- tags: null,
1638
1836
  position: statement.position,
1639
- }], ctx);
1837
+ }], ctx, lineAnchorPrecondition);
1640
1838
  return this.#finalizeEffects(edited, source, [effect]);
1641
1839
  }
1642
1840
  async #moveWithinChannel(statement, source, destination, ctx) {
@@ -1653,22 +1851,24 @@ class Dispatcher {
1653
1851
  if (destination.lineMarker === null) {
1654
1852
  return this.#writeDestination(statement, source, destination, ctx);
1655
1853
  }
1656
- const moved = await this.#invokeEditBatch(destination, [
1854
+ const resolvedDestination = this.#resolveResourceLineMarker(destination, source.completeContent, "MOVE");
1855
+ if ("result" in resolvedDestination)
1856
+ return resolvedDestination.result;
1857
+ const precondition = _a.#mergeLineAnchorPreconditions(source.lineAnchorPrecondition, resolvedDestination.precondition);
1858
+ const moved = await this.#invokeEditBatch(resolvedDestination.selection, [
1657
1859
  {
1658
- marker: destination.lineMarker,
1860
+ marker: resolvedDestination.selection.lineMarker,
1659
1861
  body: source.content,
1660
- tags: Array.isArray(statement.signal) ? statement.signal : [],
1661
1862
  position: statement.position,
1662
1863
  },
1663
1864
  {
1664
1865
  marker: source.lineMarker,
1665
1866
  body: "",
1666
- tags: null,
1667
1867
  position: statement.position,
1668
1868
  },
1669
- ], ctx);
1670
- const effect = this.#pendingEffect(destination, "update");
1671
- return this.#finalizeEffects(moved, destination, [effect, effect]);
1869
+ ], ctx, precondition);
1870
+ const effect = this.#pendingEffect(resolvedDestination.selection, "update");
1871
+ return this.#finalizeEffects(moved, resolvedDestination.selection, [effect, effect]);
1672
1872
  }
1673
1873
  async #moveOrchestration({ statement, source, destination, ctx, }) {
1674
1874
  const resolvedSource = await this.#resolveResourceSelection(source, ctx);
@@ -1677,7 +1877,7 @@ class Dispatcher {
1677
1877
  const resolvedDestination = await this.#resolveResourceSelection(destination, ctx);
1678
1878
  if (_a.#isDispatchResult(resolvedDestination))
1679
1879
  return resolvedDestination;
1680
- const selected = await this.#selectSource(resolvedSource, ctx);
1880
+ const selected = await this.#selectSource(resolvedSource, ctx, "MOVE");
1681
1881
  if (_a.#isDispatchResult(selected))
1682
1882
  return selected;
1683
1883
  if (this.#sameChannel(resolvedSource, resolvedDestination)) {
@@ -1696,11 +1896,11 @@ class Dispatcher {
1696
1896
  ...destinationResult,
1697
1897
  attrs: {
1698
1898
  ...destinationResult.attrs,
1699
- moveSource: this.#deferredMoveSource(resolvedSource, resolvedDestination),
1899
+ moveSource: this.#deferredMoveSource(selected, resolvedDestination, selected.lineAnchorPrecondition),
1700
1900
  },
1701
1901
  };
1702
1902
  }
1703
- const sourceResult = await this.#removeMoveSource(statement, resolvedSource, ctx);
1903
+ const sourceResult = await this.#removeMoveSource(statement, selected, ctx, selected.lineAnchorPrecondition);
1704
1904
  if (sourceResult.status === 202) {
1705
1905
  return {
1706
1906
  ...sourceResult,
@@ -1796,7 +1996,10 @@ class Dispatcher {
1796
1996
  || resolvedSource.channel !== deferred.channel) {
1797
1997
  throw new InvalidOperationResultError("A deferred MOVE source no longer resolves to its recorded identity.");
1798
1998
  }
1799
- const removed = await this.#removeMoveSource(statement, resolvedSource, ctx);
1999
+ const removed = await this.#removeMoveSource(statement, {
2000
+ ...resolvedSource,
2001
+ lineMarker: resolvedSource.lineMarker,
2002
+ }, ctx, deferred.lineAnchorPrecondition);
1800
2003
  if (removed.status >= 400) {
1801
2004
  return {
1802
2005
  resolution: settlement.resolution,
@@ -1834,7 +2037,7 @@ class Dispatcher {
1834
2037
  // {§send-premature-terminate} — the unified PENDING SET, judged at the terminal's OWN dispatch
1835
2038
  // (post-batch: the emission's earlier ops already executed, so a same-turn KILL+[200] repairs in
1836
2039
  // ONE turn, and a same-turn WORK+[200] is caught — the spawn is live by the time the SEND lands).
1837
- // pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN, results unseen
2040
+ // pending = open streams ∪ live children ∪ THIS turn's retrievals (READ/FIND/OPEN/BARE, results unseen
1838
2041
  // until next packet). Failed operations are the separate next-packet leg shared by explicit
1839
2042
  // completion and empty-join completion. Nothing pending may be silently discarded; 499 discards
1840
2043
  // BY STATED INTENT and is never gated.
@@ -1847,30 +2050,32 @@ class Dispatcher {
1847
2050
  const liveChild = await this.#db.engine_worker_has_live_child.get({ worker_id: workerId });
1848
2051
  if (liveChild !== undefined)
1849
2052
  pending.push("surviving workers");
1850
- const observations = await this.#pendingObservations(workerId, turnId);
1851
- if (observations.retrievals)
2053
+ const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
2054
+ if (boundaries.retrievals)
1852
2055
  pending.push("this turn's retrieval results (they land in the NEXT packet's Log)");
1853
- if (observations.streamTerminations) {
2056
+ if (boundaries.streamTerminations) {
1854
2057
  pending.push("completed stream results that land in the NEXT packet's Log");
1855
2058
  }
1856
- if (observations.childTerminations)
2059
+ if (boundaries.childTerminations)
1857
2060
  pending.push("worker results that arrived during this turn (they land NEXT turn)");
1858
2061
  return pending;
1859
2062
  }
1860
- // Results cross an observation boundary only when they have appeared in a
1861
- // packet. Completion alone is not delivery. Keep the three next-packet
1862
- // producers in one classifier so SEND[200]'s discard gate and SEND[202]'s
1863
- // empty-join decision cannot disagree about what remains unseen.
1864
- async #pendingObservations(workerId, turnId) {
1865
- const [retrievals, streamTermination, childTermination] = await Promise.all([
1866
- this.#db.engine_turn_retrievals.all({ turn_id: turnId }),
2063
+ // Results cross an observation boundary only when they have appeared in a packet;
2064
+ // successful FOLD effects likewise become useful through the curated next packet.
2065
+ // Keep every next-packet boundary in one classifier while letting the callers apply
2066
+ // their distinct contracts: retrievals block explicit completion, whereas FOLD only
2067
+ // prevents an empty wait from being inferred as completion.
2068
+ async #nextPacketBoundaries(workerId, turnId) {
2069
+ const [turnBoundaries, streamTermination, childTermination] = await Promise.all([
2070
+ this.#db.engine_turn_packet_boundaries.all({ turn_id: turnId }),
1867
2071
  this.#db.engine_worker_has_undelivered_stream_term
1868
2072
  .get({ worker_id: workerId }),
1869
2073
  this.#db.engine_worker_has_undelivered_child_term
1870
2074
  .get({ worker_id: workerId }),
1871
2075
  ]);
1872
2076
  return {
1873
- retrievals: retrievals.length > 0,
2077
+ retrievals: turnBoundaries.some(({ op }) => op !== "FOLD"),
2078
+ folds: turnBoundaries.some(({ op }) => op === "FOLD"),
1874
2079
  streamTerminations: streamTermination !== undefined,
1875
2080
  childTerminations: childTermination !== undefined,
1876
2081
  };
@@ -1911,15 +2116,15 @@ class Dispatcher {
1911
2116
  if (status === null) {
1912
2117
  return _a.#failure("send-status-required", 400, "SEND requires a numeric status.", {}, { retryable: false });
1913
2118
  }
1914
- const raw = statement.body === null ? "" : typeof statement.body === "string" ? statement.body : statement.body.raw;
1915
- // The park rides SEND[202] only ({§park-202-only}). A scoped SEND[102] is neither
2119
+ const raw = statement.body === null ? "" : statement.body.raw;
2120
+ // The park rides SEND signal 202 only ({§park-202-only}). A scoped signal 102 is neither
1916
2121
  // a wait nor a meaningful continuation, so reject it instead of preserving the
1917
2122
  // retired dual spelling.
1918
2123
  if (status === 102 && statement.lineMarker !== null) {
1919
- return _a.#failure("send-scope-invalid", 400, "SEND[102] does not accept a scope.", {}, {
2124
+ return _a.#failure("send-scope-invalid", 400, "`## SEND0 [102]` does not accept a scope.", {}, {
1920
2125
  requestedStatus: 102,
1921
2126
  scope: statement.lineMarker,
1922
- recovery: "Use SEND[202] with a scope to wait, or remove the scope to continue.",
2127
+ recovery: "Use `## SEND0 [202] <scope>` to wait, or remove the scope to continue.",
1923
2128
  retryable: false,
1924
2129
  });
1925
2130
  }
@@ -1927,13 +2132,13 @@ class Dispatcher {
1927
2132
  // indefinite park. {§join-blocking-collect}
1928
2133
  const joinArmed = this.#joinTargets.delete(loopId);
1929
2134
  if (status === 102 && statement.lineMarker === null && joinArmed) {
1930
- if (!await this.#lifecycle.park(loopId, raw.length > 0 ? raw : "parked — awaiting a worker's result (blocking collect)")) {
2135
+ if (!await this.#lifecycle.park(loopId)) {
1931
2136
  return _a.#statusResult(await this.#lifecycle.status(loopId), "loop-already-terminal", "The loop was already terminal when SEND attempted to park it.");
1932
2137
  }
1933
2138
  this.#parkDeadlines.set(loopId, -1); // indefinite: the bounded child's terminal is the wake edge
1934
2139
  return { status: 102, attrs: { parked: -1, join: true } };
1935
2140
  }
1936
- // {§wait-obligation-matrix} — SEND[202] is the obligation-checked join. A live
2141
+ // {§wait-obligation-matrix} — SEND signal 202 is the obligation-checked join. A live
1937
2142
  // obligation (a spawned child or open stream, J) BLOCKS the loop until it concludes and
1938
2143
  // reawakens it ({§worker-lifecycle-child-wake}); a wait on nothing (∅) is already satisfied and
1939
2144
  // resolves like 200, so <-1>+∅ self-resolves rather than hang the agent; a pending own
@@ -1942,7 +2147,7 @@ class Dispatcher {
1942
2147
  const marks = statement.lineMarker?.marks[0];
1943
2148
  const seconds = typeof marks === "number" ? marks : -1; // bare 202 / absent T = indefinite, bounded by the join
1944
2149
  if (await this.#hasLiveWork(workerId)) {
1945
- if (!await this.#lifecycle.park(loopId, raw.length > 0 ? raw : "waiting on live work")) {
2150
+ if (!await this.#lifecycle.park(loopId)) {
1946
2151
  return _a.#statusResult(await this.#lifecycle.status(loopId), "loop-already-terminal", "The loop was already terminal when SEND attempted to wait.");
1947
2152
  }
1948
2153
  this.#parkDeadlines.set(loopId, seconds);
@@ -1952,8 +2157,8 @@ class Dispatcher {
1952
2157
  // all complete-but-unobserved. Their wake edge may already have
1953
2158
  // fired, so do not park; continue directly to the packet that
1954
2159
  // materializes them.
1955
- const observations = await this.#pendingObservations(workerId, turnId);
1956
- if (observations.retrievals || observations.streamTerminations || observations.childTerminations) {
2160
+ const boundaries = await this.#nextPacketBoundaries(workerId, turnId);
2161
+ if (boundaries.retrievals || boundaries.folds || boundaries.streamTerminations || boundaries.childTerminations) {
1957
2162
  return { status: 102 };
1958
2163
  }
1959
2164
  const failCount = await this.#unobservedFailureCount(turnId);
@@ -1964,7 +2169,7 @@ class Dispatcher {
1964
2169
  return branchDenial;
1965
2170
  // The joined set is already drained. Awaiting an empty task group completes
1966
2171
  // immediately; it never parks and needs no corrective model turn.
1967
- const finished = await this.#lifecycle.finish(loopId, { status: 200 }, { message: raw === "" ? null : raw });
2172
+ const finished = await this.#lifecycle.finish(loopId, TerminalResult.success(raw));
1968
2173
  return {
1969
2174
  status: finished !== null ? 200 : await this.#lifecycle.status(loopId),
1970
2175
  attrs: { joined: true, pending: 0 },
@@ -2008,7 +2213,7 @@ class Dispatcher {
2008
2213
  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.", {}, {
2009
2214
  pending: [...pending],
2010
2215
  stage: "completion",
2011
- recovery: "Review the results, then use only PLAN and SEND[200] to conclude.",
2216
+ recovery: "Review the results, then use only `# PLAN0` and `## SEND0 [200]` to conclude.",
2012
2217
  retryable: false,
2013
2218
  });
2014
2219
  }
@@ -2022,7 +2227,7 @@ class Dispatcher {
2022
2227
  const branchDenial = await this.#branchCompletionGate?.(workerId) ?? null;
2023
2228
  if (branchDenial !== null)
2024
2229
  return branchDenial;
2025
- const finished = await this.#lifecycle.finish(loopId, { status: 200 }, { message: raw === "" ? null : raw });
2230
+ const finished = await this.#lifecycle.finish(loopId, TerminalResult.success(raw));
2026
2231
  return _a.#statusResult(finished !== null ? 200 : await this.#lifecycle.status(loopId), "loop-already-terminal", "The loop was already terminal when SEND attempted to conclude it.");
2027
2232
  }
2028
2233
  if (status === 499) {
@@ -2035,7 +2240,7 @@ class Dispatcher {
2035
2240
  turn_id: turnId,
2036
2241
  });
2037
2242
  if (seqs === undefined) {
2038
- throw new Error(`SEND[499]: no coordinate for loop=${loopId} turn=${turnId}`);
2243
+ throw new Error(`SEND signal 499: no coordinate for loop=${loopId} turn=${turnId}`);
2039
2244
  }
2040
2245
  Results.attachInstance(failure, `log:///${seqs.loop_seq}/${seqs.turn_seq}/${ctx.sequence}/SEND`);
2041
2246
  const finished = await this.#lifecycle.finish(loopId, failure);
@@ -2050,7 +2255,20 @@ class Dispatcher {
2050
2255
  }
2051
2256
  async #run(schemeName, statement, ctx) {
2052
2257
  if (schemeName === null) {
2053
- return _a.#failure("target-scheme-required", 400, `${statement.op} requires a target scheme.`, {}, { operation: statement.op, retryable: false });
2258
+ const fields = statement.op === "READ"
2259
+ ? { content: null, mimetype: null, channel: null }
2260
+ : statement.op === "FIND"
2261
+ ? {
2262
+ content: null,
2263
+ mimetype: null,
2264
+ results: [],
2265
+ itemsTokenTotal: 0,
2266
+ returnedItemsTokenTotal: 0,
2267
+ matchingPathCount: 0,
2268
+ matchLocationCount: 0,
2269
+ }
2270
+ : {};
2271
+ return _a.#failure("target-scheme-required", 400, `${statement.op} requires a target scheme.`, fields, { operation: statement.op, retryable: false });
2054
2272
  }
2055
2273
  if (statement.op === "SEND" && statement.signal === 499 && statement.target?.kind === "url") {
2056
2274
  const addressedScheme = statement.target.scheme;
@@ -2084,16 +2302,128 @@ class Dispatcher {
2084
2302
  const manifest = this.#schemes.manifestFor(schemeName);
2085
2303
  if (manifest === undefined)
2086
2304
  throw new Error(`scheme '${schemeName}' has no manifest`);
2087
- const schemeCtx = new SchemeCtxImpl(ctx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions);
2088
- if (typeof method === "function")
2089
- return Results.assert(await method.call(handler, statement, schemeCtx));
2305
+ const publishedChannel = statement.target?.kind === "url"
2306
+ ? statement.target.fragment ?? manifest.defaultChannel
2307
+ : manifest.defaultChannel;
2308
+ const schemeCtx = new SchemeCtxImpl(ctx, addressedScheme ?? schemeName, manifest, this.#liveSubscriptions, { publishedChannel });
2309
+ if (statement.op === "READ"
2310
+ && handler instanceof CoreSchemeAdapterBase
2311
+ && typeof handler.resolveCoreRepresentation === "function") {
2312
+ const selectionNeutralTarget = statement.target?.kind === "url"
2313
+ ? {
2314
+ ...statement.target,
2315
+ raw: renderTarget({ ...statement.target, fragment: null }) ?? statement.target.raw,
2316
+ fragment: null,
2317
+ }
2318
+ : statement.target;
2319
+ const resolved = await handler
2320
+ .resolveCoreRepresentation(selectionNeutralTarget, schemeCtx);
2321
+ if ("result" in resolved)
2322
+ return Results.assertReadResult(resolved.result);
2323
+ if (selectionNeutralTarget === null) {
2324
+ throw new InvalidOperationResultError(`Core scheme '${schemeName}' resolved a targetless READ representation.`);
2325
+ }
2326
+ const target = renderTarget(selectionNeutralTarget.kind === "url"
2327
+ ? selectionNeutralTarget
2328
+ : { scheme: null, pathname: selectionNeutralTarget.raw });
2329
+ if (target === null) {
2330
+ throw new TypeError(`Core scheme '${schemeName}' resolved an unrenderable READ target.`);
2331
+ }
2332
+ return Results.assertReadResult(await ReadProjector.project({
2333
+ statement,
2334
+ manifest,
2335
+ target,
2336
+ identity: target,
2337
+ representation: resolved.representation,
2338
+ mimetypes: ctx.mimetypes,
2339
+ }));
2340
+ }
2341
+ if (statement.op === "READ" && manifest.category === "data") {
2342
+ const prepared = statement.target === null
2343
+ ? { address: null, result: null }
2344
+ : await this.#prepareDataRepresentation({
2345
+ target: statement.target,
2346
+ routedScheme: schemeName,
2347
+ handler: handler,
2348
+ manifest,
2349
+ ctx,
2350
+ schemeCtx,
2351
+ publishedChannel,
2352
+ });
2353
+ if (prepared.result !== null)
2354
+ return prepared.result;
2355
+ const resolved = prepared.address;
2356
+ if (statement.target !== null && resolved === null) {
2357
+ const target = renderTarget(statement.target.kind === "url"
2358
+ ? statement.target
2359
+ : { scheme: null, pathname: statement.target.raw });
2360
+ return Results.failure(`scheme:${schemeName}`, "entry-not-found", 404, `No entry exists at ${target ?? "the requested address"}.`, { content: null, mimetype: null, channel: null }, { target });
2361
+ }
2362
+ const storageScheme = resolved?.scheme ?? manifest.storedScheme ?? addressedScheme ?? schemeName;
2363
+ const projected = Results.assertReadResult(await EntryOps.readWorkspaceEntry(statement, ctx, { ...manifest, name: storageScheme, storedScheme: storageScheme }, resolved === null
2364
+ ? {}
2365
+ : {
2366
+ ownerId: resolved.ownerId,
2367
+ pathname: resolved.pathname,
2368
+ }));
2369
+ return projected;
2370
+ }
2090
2371
  if (statement.op !== "FIND" || manifest.category !== "data") {
2372
+ if (typeof method === "function") {
2373
+ return Results.assert(await method.call(handler, statement, schemeCtx));
2374
+ }
2091
2375
  return _a.#failure("operation-not-implemented", 501, `Scheme '${schemeName}' does not implement ${statement.op}.`, {}, {
2092
2376
  scheme: schemeName,
2093
2377
  operation: statement.op,
2094
2378
  retryable: false,
2095
2379
  });
2096
2380
  }
2381
+ const targetPathname = statement.target?.kind === "url"
2382
+ ? statement.target.pathname
2383
+ : statement.target?.raw ?? "";
2384
+ const collectionTarget = manifest.folderScopes === true
2385
+ && (targetPathname === "" || targetPathname.endsWith("/"));
2386
+ const exactTarget = statement.target !== null
2387
+ && !collectionTarget
2388
+ && !PathSyntax.hasGlob(entryPathnameOf(statement.target));
2389
+ if (exactTarget && statement.target !== null) {
2390
+ const prepared = await this.#prepareDataRepresentation({
2391
+ target: statement.target,
2392
+ routedScheme: schemeName,
2393
+ handler: handler,
2394
+ manifest,
2395
+ ctx,
2396
+ schemeCtx,
2397
+ publishedChannel,
2398
+ });
2399
+ if (prepared.result !== null)
2400
+ return prepared.result;
2401
+ if (prepared.address === null) {
2402
+ const target = renderTarget(statement.target.kind === "url"
2403
+ ? statement.target
2404
+ : { scheme: null, pathname: statement.target.raw });
2405
+ return Results.failure(`scheme:${schemeName}`, "entry-not-found", 404, `No entry exists at ${target ?? "the requested address"}.`, {
2406
+ content: null,
2407
+ mimetype: null,
2408
+ results: [],
2409
+ itemsTokenTotal: 0,
2410
+ returnedItemsTokenTotal: 0,
2411
+ matchingPathCount: 0,
2412
+ matchLocationCount: 0,
2413
+ }, { target });
2414
+ }
2415
+ const storageScheme = prepared.address.scheme
2416
+ ?? manifest.storedScheme
2417
+ ?? addressedScheme
2418
+ ?? schemeName;
2419
+ return Results.assert(await EntryFind.findWorkspaceEntries(statement, ctx, { ...manifest, name: storageScheme, storedScheme: storageScheme }, {
2420
+ ownerId: prepared.address.ownerId,
2421
+ pathname: prepared.address.pathname,
2422
+ }));
2423
+ }
2424
+ if (typeof method === "function") {
2425
+ return Results.assert(await method.call(handler, statement, schemeCtx));
2426
+ }
2097
2427
  const prepareFind = handler.prepareFind;
2098
2428
  if (typeof prepareFind === "function") {
2099
2429
  const prepared = await prepareFind.call(handler, statement, schemeCtx);
@@ -2105,11 +2435,14 @@ class Dispatcher {
2105
2435
  async #runLogCuration(statement, ctx) {
2106
2436
  const addressedScheme = schemeNameOf(statement.target);
2107
2437
  if (addressedScheme !== null && addressedScheme !== "log") {
2108
- return _a.#failure("operation-not-implemented", 501, `Scheme '${addressedScheme}' does not implement ${statement.op}.`, {}, {
2109
- scheme: addressedScheme,
2110
- operation: statement.op,
2111
- retryable: false,
2112
- });
2438
+ return {
2439
+ result: _a.#failure("operation-not-implemented", 501, `Scheme '${addressedScheme}' does not implement ${statement.op}.`, {}, {
2440
+ scheme: addressedScheme,
2441
+ operation: statement.op,
2442
+ retryable: false,
2443
+ }),
2444
+ plan: null,
2445
+ };
2113
2446
  }
2114
2447
  const handler = this.#schemes.get("log");
2115
2448
  const manifest = this.#schemes.manifestFor("log");
@@ -2117,21 +2450,20 @@ class Dispatcher {
2117
2450
  throw new Error("the core log curation owner is not registered");
2118
2451
  }
2119
2452
  const schemeCtx = new SchemeCtxImpl(ctx, "log", manifest, this.#liveSubscriptions);
2120
- return statement.op === "OPEN"
2121
- ? Results.assert(await handler.open(statement, schemeCtx))
2122
- : Results.assert(await handler.fold(statement, schemeCtx));
2453
+ const outcome = await handler.curate(statement, schemeCtx);
2454
+ return { result: Results.assert(outcome.result), plan: outcome.plan };
2123
2455
  }
2124
2456
  // {§proposal}/{§send} — status 202 is a proposal except for broadcast
2125
- // SEND[202], which parks the loop. The operation disambiguates the status.
2457
+ // SEND signal 202, which parks the loop. The operation disambiguates the status.
2126
2458
  static #isProposal(statement, result) {
2127
- // {§send-300-choices} — SEND[300] is the proposal exception.
2459
+ // {§send-300-choices} — SEND signal 300 is the proposal exception.
2128
2460
  if (result.status !== 202)
2129
2461
  return false;
2130
2462
  if (statement.op === "SEND" && statement.signal === 300)
2131
2463
  return true;
2132
2464
  return !(statement.op === "SEND" && statement.target === null);
2133
2465
  }
2134
- async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, }) {
2466
+ async #writeLog({ statement, result, workspaceId, workerId, loopId, turnId, sequence, origin, curationPlan, modelCallId, }) {
2135
2467
  const durableStatement = DurableStatement.project(statement);
2136
2468
  const target = this.#extractTarget(durableStatement.target);
2137
2469
  await this.#canonColumns(target, workspaceId); // {§fs-answer-in-canon}
@@ -2141,12 +2473,23 @@ class Dispatcher {
2141
2473
  // A proposal (status 202 from a side-effecting op) is written to the log in
2142
2474
  // state='proposed' until the proposal lifecycle resolves it; attrs holds the
2143
2475
  // scheme-supplied payload (file diff, exec command, etc.) the client renders
2144
- // for review and the scheme consumes on accept. A broadcast SEND[202] is a
2476
+ // for review and the scheme consumes on accept. A broadcast SEND signal 202 is a
2145
2477
  // parked-terminal, not a proposal (#isProposal) → state='resolved'.
2146
2478
  const isProposed = _a.#isProposal(statement, result);
2147
2479
  let attrsObj = (result.attrs !== undefined && result.attrs !== null)
2148
2480
  ? { ...result.attrs }
2149
2481
  : {};
2482
+ if (curationPlan !== null) {
2483
+ if (Object.hasOwn(attrsObj, "__plurnk_curation")) {
2484
+ throw new Error("Dispatcher.#writeLog: result attrs collide with private log curation state");
2485
+ }
2486
+ attrsObj.__plurnk_curation = {
2487
+ ids: curationPlan.targetLogEntryIds,
2488
+ expanded: curationPlan.expanded,
2489
+ add: curationPlan.add,
2490
+ remove: curationPlan.remove,
2491
+ };
2492
+ }
2150
2493
  const seqs = statement.op === "EXEC" || result.problem !== undefined
2151
2494
  ? await this.#db.engine_loop_turn_seqs.get({
2152
2495
  loop_id: loopId,
@@ -2197,6 +2540,7 @@ class Dispatcher {
2197
2540
  sequence: sequence,
2198
2541
  origin,
2199
2542
  source: null, // dispatch entries are self-authored; {§env-delta} deltas set this
2543
+ model_call_id: modelCallId,
2200
2544
  op: durableStatement.op,
2201
2545
  suffix: durableStatement.suffix,
2202
2546
  signal: this.#signalToJson(durableStatement.signal),