@plurnk/plurnk-service 1.3.12 → 1.5.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 (488) hide show
  1. package/.env.defaults +95 -91
  2. package/INSTALL.md +54 -28
  3. package/README.md +25 -10
  4. package/SPEC.md +2426 -942
  5. package/dist/Paths.d.ts +4 -1
  6. package/dist/Paths.d.ts.map +1 -1
  7. package/dist/Paths.js +24 -23
  8. package/dist/Paths.js.map +1 -1
  9. package/dist/build-info.json +1 -1
  10. package/dist/content/edit-receipt.d.ts +13 -28
  11. package/dist/content/edit-receipt.d.ts.map +1 -1
  12. package/dist/content/edit-receipt.js +285 -104
  13. package/dist/content/edit-receipt.js.map +1 -1
  14. package/dist/content/edited-span.d.ts.map +1 -1
  15. package/dist/content/edited-span.js +2 -5
  16. package/dist/content/edited-span.js.map +1 -1
  17. package/dist/content/index.d.ts +4 -3
  18. package/dist/content/index.d.ts.map +1 -1
  19. package/dist/content/index.js +2 -1
  20. package/dist/content/index.js.map +1 -1
  21. package/dist/content/line-marker.d.ts +10 -9
  22. package/dist/content/line-marker.d.ts.map +1 -1
  23. package/dist/content/line-marker.js +8 -11
  24. package/dist/content/line-marker.js.map +1 -1
  25. package/dist/content/matcher.d.ts +15 -26
  26. package/dist/content/matcher.d.ts.map +1 -1
  27. package/dist/content/matcher.js +77 -107
  28. package/dist/content/matcher.js.map +1 -1
  29. package/dist/content/mimetype-binary.d.ts +2 -2
  30. package/dist/content/mimetype-binary.d.ts.map +1 -1
  31. package/dist/content/mimetype-binary.js +10 -10
  32. package/dist/content/mimetype-binary.js.map +1 -1
  33. package/dist/content/path-mimetype.js +3 -3
  34. package/dist/content/path-mimetype.js.map +1 -1
  35. package/dist/content/read-projector.d.ts +14 -0
  36. package/dist/content/read-projector.d.ts.map +1 -0
  37. package/dist/content/read-projector.js +86 -0
  38. package/dist/content/read-projector.js.map +1 -0
  39. package/dist/content/read-resolve.d.ts +6 -7
  40. package/dist/content/read-resolve.d.ts.map +1 -1
  41. package/dist/content/read-resolve.js +36 -59
  42. package/dist/content/read-resolve.js.map +1 -1
  43. package/dist/core/BranchReceipt.d.ts +6 -0
  44. package/dist/core/BranchReceipt.d.ts.map +1 -0
  45. package/dist/core/BranchReceipt.js +24 -0
  46. package/dist/core/BranchReceipt.js.map +1 -0
  47. package/dist/core/BudgetOverflow.d.ts +20 -0
  48. package/dist/core/BudgetOverflow.d.ts.map +1 -0
  49. package/dist/core/BudgetOverflow.js +49 -0
  50. package/dist/core/BudgetOverflow.js.map +1 -0
  51. package/dist/core/BudgetReadout.d.ts +8 -0
  52. package/dist/core/BudgetReadout.d.ts.map +1 -0
  53. package/dist/core/BudgetReadout.js +79 -0
  54. package/dist/core/BudgetReadout.js.map +1 -0
  55. package/dist/core/ChannelWrite.d.ts +31 -9
  56. package/dist/core/ChannelWrite.d.ts.map +1 -1
  57. package/dist/core/ChannelWrite.js +30 -23
  58. package/dist/core/ChannelWrite.js.map +1 -1
  59. package/dist/core/ChannelWrite.sql +14 -12
  60. package/dist/core/CoreSchemeServices.d.ts +18 -4
  61. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  62. package/dist/core/CoreSchemeServices.js +7 -1
  63. package/dist/core/CoreSchemeServices.js.map +1 -1
  64. package/dist/core/Dispatcher.d.ts +26 -21
  65. package/dist/core/Dispatcher.d.ts.map +1 -1
  66. package/dist/core/Dispatcher.js +1626 -671
  67. package/dist/core/Dispatcher.js.map +1 -1
  68. package/dist/core/DurableStatement.d.ts +6 -0
  69. package/dist/core/DurableStatement.d.ts.map +1 -0
  70. package/dist/core/DurableStatement.js +51 -0
  71. package/dist/core/DurableStatement.js.map +1 -0
  72. package/dist/core/Engine.d.ts +64 -29
  73. package/dist/core/Engine.d.ts.map +1 -1
  74. package/dist/core/Engine.js +1348 -835
  75. package/dist/core/Engine.js.map +1 -1
  76. package/dist/core/Engine.sql +329 -234
  77. package/dist/core/EnvFlags.js +1 -1
  78. package/dist/core/EnvFlags.js.map +1 -1
  79. package/dist/core/ErrorDetail.d.ts +6 -0
  80. package/dist/core/ErrorDetail.d.ts.map +1 -0
  81. package/dist/core/ErrorDetail.js +20 -0
  82. package/dist/core/ErrorDetail.js.map +1 -0
  83. package/dist/core/ExecutorRegistry.d.ts +14 -3
  84. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  85. package/dist/core/ExecutorRegistry.js +56 -30
  86. package/dist/core/ExecutorRegistry.js.map +1 -1
  87. package/dist/core/GitBranch.d.ts +20 -0
  88. package/dist/core/GitBranch.d.ts.map +1 -0
  89. package/dist/core/GitBranch.js +110 -0
  90. package/dist/core/GitBranch.js.map +1 -0
  91. package/dist/core/JournalTurn.d.ts +9 -0
  92. package/dist/core/JournalTurn.d.ts.map +1 -0
  93. package/dist/core/JournalTurn.js +14 -0
  94. package/dist/core/JournalTurn.js.map +1 -0
  95. package/dist/core/JournalTurn.sql +10 -0
  96. package/dist/core/LogBody.d.ts +18 -0
  97. package/dist/core/LogBody.d.ts.map +1 -0
  98. package/dist/core/LogBody.js +180 -0
  99. package/dist/core/LogBody.js.map +1 -0
  100. package/dist/core/LoopFlagsReader.d.ts +7 -0
  101. package/dist/core/LoopFlagsReader.d.ts.map +1 -0
  102. package/dist/core/LoopFlagsReader.js +33 -0
  103. package/dist/core/LoopFlagsReader.js.map +1 -0
  104. package/dist/core/LoopLifecycle.d.ts +8 -2
  105. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  106. package/dist/core/LoopLifecycle.js +54 -11
  107. package/dist/core/LoopLifecycle.js.map +1 -1
  108. package/dist/core/LoopLifecycle.sql +10 -7
  109. package/dist/core/NoticeChannel.d.ts +13 -0
  110. package/dist/core/NoticeChannel.d.ts.map +1 -0
  111. package/dist/core/NoticeChannel.js +40 -0
  112. package/dist/core/NoticeChannel.js.map +1 -0
  113. package/dist/core/Owner.d.ts +0 -1
  114. package/dist/core/Owner.d.ts.map +1 -1
  115. package/dist/core/Owner.js +2 -5
  116. package/dist/core/Owner.js.map +1 -1
  117. package/dist/core/PacketBuilder.d.ts +27 -25
  118. package/dist/core/PacketBuilder.d.ts.map +1 -1
  119. package/dist/core/PacketBuilder.js +321 -383
  120. package/dist/core/PacketBuilder.js.map +1 -1
  121. package/dist/core/ProblemLog.d.ts +26 -0
  122. package/dist/core/ProblemLog.d.ts.map +1 -0
  123. package/dist/core/ProblemLog.js +58 -0
  124. package/dist/core/ProblemLog.js.map +1 -0
  125. package/dist/core/ProposalLifecycle.d.ts +15 -21
  126. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  127. package/dist/core/ProposalLifecycle.js +291 -78
  128. package/dist/core/ProposalLifecycle.js.map +1 -1
  129. package/dist/core/ProviderInstantiate.d.ts.map +1 -1
  130. package/dist/core/ProviderInstantiate.js +51 -48
  131. package/dist/core/ProviderInstantiate.js.map +1 -1
  132. package/dist/core/SchemeRegistry.d.ts +14 -8
  133. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  134. package/dist/core/SchemeRegistry.js +189 -114
  135. package/dist/core/SchemeRegistry.js.map +1 -1
  136. package/dist/core/ServiceTeardown.d.ts +11 -0
  137. package/dist/core/ServiceTeardown.d.ts.map +1 -0
  138. package/dist/core/ServiceTeardown.js +70 -0
  139. package/dist/core/ServiceTeardown.js.map +1 -0
  140. package/dist/core/StoredPacket.d.ts +29 -0
  141. package/dist/core/StoredPacket.d.ts.map +1 -0
  142. package/dist/core/StoredPacket.js +147 -0
  143. package/dist/core/StoredPacket.js.map +1 -0
  144. package/dist/core/StrikeRail.d.ts +6 -3
  145. package/dist/core/StrikeRail.d.ts.map +1 -1
  146. package/dist/core/StrikeRail.js +27 -56
  147. package/dist/core/StrikeRail.js.map +1 -1
  148. package/dist/core/TerminalResult.d.ts +16 -0
  149. package/dist/core/TerminalResult.d.ts.map +1 -0
  150. package/dist/core/TerminalResult.js +49 -0
  151. package/dist/core/TerminalResult.js.map +1 -0
  152. package/dist/core/WorkerControlAddress.d.ts +17 -0
  153. package/dist/core/WorkerControlAddress.d.ts.map +1 -0
  154. package/dist/core/WorkerControlAddress.js +44 -0
  155. package/dist/core/WorkerControlAddress.js.map +1 -0
  156. package/dist/core/WorkerName.d.ts +31 -0
  157. package/dist/core/WorkerName.d.ts.map +1 -0
  158. package/dist/core/WorkerName.js +95 -0
  159. package/dist/core/WorkerName.js.map +1 -0
  160. package/dist/core/WorkerName.sql +36 -0
  161. package/dist/core/WorkspaceGate.d.ts +14 -0
  162. package/dist/core/WorkspaceGate.d.ts.map +1 -0
  163. package/dist/core/WorkspaceGate.js +155 -0
  164. package/dist/core/WorkspaceGate.js.map +1 -0
  165. package/dist/core/caps/CapsResolve.d.ts +5 -1
  166. package/dist/core/caps/CapsResolve.d.ts.map +1 -1
  167. package/dist/core/caps/CapsResolve.js +8 -4
  168. package/dist/core/caps/CapsResolve.js.map +1 -1
  169. package/dist/core/caps/DbChannelCaps.d.ts +5 -11
  170. package/dist/core/caps/DbChannelCaps.d.ts.map +1 -1
  171. package/dist/core/caps/DbChannelCaps.js +30 -12
  172. package/dist/core/caps/DbChannelCaps.js.map +1 -1
  173. package/dist/core/caps/DbEntryCaps.d.ts +5 -14
  174. package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
  175. package/dist/core/caps/DbEntryCaps.js +27 -8
  176. package/dist/core/caps/DbEntryCaps.js.map +1 -1
  177. package/dist/core/caps/DbNotifyCaps.d.ts +1 -1
  178. package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
  179. package/dist/core/caps/DbNotifyCaps.js +18 -15
  180. package/dist/core/caps/DbNotifyCaps.js.map +1 -1
  181. package/dist/core/caps/DbProjectionCaps.d.ts +5 -5
  182. package/dist/core/caps/DbProjectionCaps.d.ts.map +1 -1
  183. package/dist/core/caps/DbProjectionCaps.js +21 -4
  184. package/dist/core/caps/DbProjectionCaps.js.map +1 -1
  185. package/dist/core/caps/DbSubscriptionCaps.d.ts +4 -6
  186. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  187. package/dist/core/caps/DbSubscriptionCaps.js +75 -66
  188. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  189. package/dist/core/caps/DbTagCaps.d.ts +5 -12
  190. package/dist/core/caps/DbTagCaps.d.ts.map +1 -1
  191. package/dist/core/caps/DbTagCaps.js +20 -11
  192. package/dist/core/caps/DbTagCaps.js.map +1 -1
  193. package/dist/core/caps/SchemeCtxImpl.d.ts +6 -1
  194. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  195. package/dist/core/caps/SchemeCtxImpl.js +6 -6
  196. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  197. package/dist/core/content-hash.js +1 -1
  198. package/dist/core/content-hash.js.map +1 -1
  199. package/dist/core/env-defaults.d.ts.map +1 -1
  200. package/dist/core/env-defaults.js.map +1 -1
  201. package/dist/core/fork.d.ts.map +1 -1
  202. package/dist/core/fork.js +55 -30
  203. package/dist/core/fork.js.map +1 -1
  204. package/dist/core/fork.sql +43 -29
  205. package/dist/core/git-env.d.ts +2 -0
  206. package/dist/core/git-env.d.ts.map +1 -1
  207. package/dist/core/git-env.js +13 -18
  208. package/dist/core/git-env.js.map +1 -1
  209. package/dist/core/git-iso.d.ts +3 -2
  210. package/dist/core/git-iso.d.ts.map +1 -1
  211. package/dist/core/git-iso.js +36 -27
  212. package/dist/core/git-iso.js.map +1 -1
  213. package/dist/core/git-membership.d.ts +2 -1
  214. package/dist/core/git-membership.d.ts.map +1 -1
  215. package/dist/core/git-membership.js +276 -162
  216. package/dist/core/git-membership.js.map +1 -1
  217. package/dist/core/git-state.d.ts +8 -1
  218. package/dist/core/git-state.d.ts.map +1 -1
  219. package/dist/core/git-state.js +44 -26
  220. package/dist/core/git-state.js.map +1 -1
  221. package/dist/core/namespace.d.ts +1 -0
  222. package/dist/core/namespace.d.ts.map +1 -1
  223. package/dist/core/namespace.js +20 -0
  224. package/dist/core/namespace.js.map +1 -1
  225. package/dist/core/optimistic-settlement.d.ts +2 -0
  226. package/dist/core/optimistic-settlement.d.ts.map +1 -0
  227. package/dist/core/optimistic-settlement.js +14 -0
  228. package/dist/core/optimistic-settlement.js.map +1 -0
  229. package/dist/core/owner.sql +1 -1
  230. package/dist/core/packet-inject.d.ts.map +1 -1
  231. package/dist/core/packet-inject.js +7 -11
  232. package/dist/core/packet-inject.js.map +1 -1
  233. package/dist/core/packet-wire.d.ts +3 -21
  234. package/dist/core/packet-wire.d.ts.map +1 -1
  235. package/dist/core/packet-wire.js +305 -385
  236. package/dist/core/packet-wire.js.map +1 -1
  237. package/dist/core/plurnk-uri.d.ts +12 -1
  238. package/dist/core/plurnk-uri.d.ts.map +1 -1
  239. package/dist/core/plurnk-uri.js +49 -29
  240. package/dist/core/plurnk-uri.js.map +1 -1
  241. package/dist/core/results.d.ts +18 -5
  242. package/dist/core/results.d.ts.map +1 -1
  243. package/dist/core/results.js +33 -18
  244. package/dist/core/results.js.map +1 -1
  245. package/dist/core/ruler_count.d.ts +4 -0
  246. package/dist/core/ruler_count.d.ts.map +1 -0
  247. package/dist/core/ruler_count.js +7 -0
  248. package/dist/core/ruler_count.js.map +1 -0
  249. package/dist/core/scheme-types.d.ts +3 -17
  250. package/dist/core/scheme-types.d.ts.map +1 -1
  251. package/dist/core/scheme-types.js.map +1 -1
  252. package/dist/core/search-gate.d.ts.map +1 -1
  253. package/dist/core/search-gate.js +3 -11
  254. package/dist/core/search-gate.js.map +1 -1
  255. package/dist/core/teaching-corpus.d.ts +6 -0
  256. package/dist/core/teaching-corpus.d.ts.map +1 -0
  257. package/dist/core/teaching-corpus.js +23 -0
  258. package/dist/core/teaching-corpus.js.map +1 -0
  259. package/dist/core/teaching.d.ts +0 -1
  260. package/dist/core/teaching.d.ts.map +1 -1
  261. package/dist/core/teaching.js +2 -8
  262. package/dist/core/teaching.js.map +1 -1
  263. package/dist/core/token-ruler.d.ts.map +1 -1
  264. package/dist/core/token-ruler.js +3 -16
  265. package/dist/core/token-ruler.js.map +1 -1
  266. package/dist/core/turn-scheduler.d.ts +1 -1
  267. package/dist/core/turn-scheduler.d.ts.map +1 -1
  268. package/dist/core/turn-scheduler.js +1 -1
  269. package/dist/core/turn-scheduler.js.map +1 -1
  270. package/dist/core/worker-cap.d.ts +2 -4
  271. package/dist/core/worker-cap.d.ts.map +1 -1
  272. package/dist/core/worker-cap.js +13 -4
  273. package/dist/core/worker-cap.js.map +1 -1
  274. package/dist/core/{run-ops.sql → worker-ops.sql} +14 -14
  275. package/dist/core/workspace-settings.d.ts.map +1 -1
  276. package/dist/core/workspace-settings.js +17 -9
  277. package/dist/core/workspace-settings.js.map +1 -1
  278. package/dist/digest/Digest.d.ts +2 -1
  279. package/dist/digest/Digest.d.ts.map +1 -1
  280. package/dist/digest/Digest.js +510 -149
  281. package/dist/digest/Digest.js.map +1 -1
  282. package/dist/digest/digest.sql +23 -10
  283. package/dist/index.d.ts.map +1 -1
  284. package/dist/index.js +3 -5
  285. package/dist/index.js.map +1 -1
  286. package/dist/observe/api.d.ts +6 -0
  287. package/dist/observe/api.d.ts.map +1 -0
  288. package/dist/observe/api.js +11 -0
  289. package/dist/observe/api.js.map +1 -0
  290. package/dist/observe/init.d.ts +7 -0
  291. package/dist/observe/init.d.ts.map +1 -0
  292. package/dist/observe/init.js +159 -0
  293. package/dist/observe/init.js.map +1 -0
  294. package/dist/observe/metrics.d.ts +6 -0
  295. package/dist/observe/metrics.d.ts.map +1 -0
  296. package/dist/observe/metrics.js +15 -0
  297. package/dist/observe/metrics.js.map +1 -0
  298. package/dist/observe/spans.d.ts +4 -0
  299. package/dist/observe/spans.d.ts.map +1 -0
  300. package/dist/observe/spans.js +51 -0
  301. package/dist/observe/spans.js.map +1 -0
  302. package/dist/schemes/EffectPolicy.d.ts +1 -0
  303. package/dist/schemes/EffectPolicy.d.ts.map +1 -1
  304. package/dist/schemes/EffectPolicy.js +5 -2
  305. package/dist/schemes/EffectPolicy.js.map +1 -1
  306. package/dist/schemes/Exec.d.ts +9 -14
  307. package/dist/schemes/Exec.d.ts.map +1 -1
  308. package/dist/schemes/Exec.js +402 -262
  309. package/dist/schemes/Exec.js.map +1 -1
  310. package/dist/schemes/ExecOutputScheme.d.ts +9 -9
  311. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  312. package/dist/schemes/ExecOutputScheme.js +49 -18
  313. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  314. package/dist/schemes/File.d.ts +7 -13
  315. package/dist/schemes/File.d.ts.map +1 -1
  316. package/dist/schemes/File.js +280 -151
  317. package/dist/schemes/File.js.map +1 -1
  318. package/dist/schemes/Log.d.ts +8 -12
  319. package/dist/schemes/Log.d.ts.map +1 -1
  320. package/dist/schemes/Log.js +402 -233
  321. package/dist/schemes/Log.js.map +1 -1
  322. package/dist/schemes/Log.sql +71 -32
  323. package/dist/schemes/Prompt.d.ts +3 -3
  324. package/dist/schemes/Prompt.d.ts.map +1 -1
  325. package/dist/schemes/Prompt.js +10 -10
  326. package/dist/schemes/Prompt.js.map +1 -1
  327. package/dist/schemes/Skill.d.ts +2 -3
  328. package/dist/schemes/Skill.d.ts.map +1 -1
  329. package/dist/schemes/Skill.js +2 -6
  330. package/dist/schemes/Skill.js.map +1 -1
  331. package/dist/schemes/Worker.d.ts +11 -19
  332. package/dist/schemes/Worker.d.ts.map +1 -1
  333. package/dist/schemes/Worker.js +210 -89
  334. package/dist/schemes/Worker.js.map +1 -1
  335. package/dist/schemes/_entry-chunk.d.ts.map +1 -1
  336. package/dist/schemes/_entry-chunk.js +12 -5
  337. package/dist/schemes/_entry-chunk.js.map +1 -1
  338. package/dist/schemes/_entry-crud.d.ts +11 -9
  339. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  340. package/dist/schemes/_entry-crud.js +72 -13
  341. package/dist/schemes/_entry-crud.js.map +1 -1
  342. package/dist/schemes/_entry-crud.sql +28 -10
  343. package/dist/schemes/_entry-find.d.ts +49 -18
  344. package/dist/schemes/_entry-find.d.ts.map +1 -1
  345. package/dist/schemes/_entry-find.js +348 -195
  346. package/dist/schemes/_entry-find.js.map +1 -1
  347. package/dist/schemes/_entry-find.sql +20 -8
  348. package/dist/schemes/_entry-graph.d.ts +4 -2
  349. package/dist/schemes/_entry-graph.d.ts.map +1 -1
  350. package/dist/schemes/_entry-graph.js +55 -31
  351. package/dist/schemes/_entry-graph.js.map +1 -1
  352. package/dist/schemes/_entry-graph.sql +37 -22
  353. package/dist/schemes/_entry-manifest.d.ts +10 -15
  354. package/dist/schemes/_entry-manifest.d.ts.map +1 -1
  355. package/dist/schemes/_entry-manifest.js +50 -268
  356. package/dist/schemes/_entry-manifest.js.map +1 -1
  357. package/dist/schemes/_entry-ops.d.ts +14 -15
  358. package/dist/schemes/_entry-ops.d.ts.map +1 -1
  359. package/dist/schemes/_entry-ops.js +121 -110
  360. package/dist/schemes/_entry-ops.js.map +1 -1
  361. package/dist/schemes/_entry-ops.sql +2 -3
  362. package/dist/schemes/_entry-semantic.d.ts +22 -9
  363. package/dist/schemes/_entry-semantic.d.ts.map +1 -1
  364. package/dist/schemes/_entry-semantic.js +127 -67
  365. package/dist/schemes/_entry-semantic.js.map +1 -1
  366. package/dist/schemes/_entry-semantic.sql +63 -45
  367. package/dist/schemes/_entry-send.d.ts +3 -4
  368. package/dist/schemes/_entry-send.d.ts.map +1 -1
  369. package/dist/schemes/_entry-send.js +48 -23
  370. package/dist/schemes/_entry-send.js.map +1 -1
  371. package/dist/schemes/_path-scope.d.ts +23 -0
  372. package/dist/schemes/_path-scope.d.ts.map +1 -0
  373. package/dist/schemes/_path-scope.js +61 -0
  374. package/dist/schemes/_path-scope.js.map +1 -0
  375. package/dist/schemes/_search-candidate.d.ts +20 -0
  376. package/dist/schemes/_search-candidate.d.ts.map +1 -0
  377. package/dist/schemes/_search-candidate.js +19 -0
  378. package/dist/schemes/_search-candidate.js.map +1 -0
  379. package/dist/schemes/_search-exclusion.d.ts +7 -0
  380. package/dist/schemes/_search-exclusion.d.ts.map +1 -0
  381. package/dist/schemes/_search-exclusion.js +23 -0
  382. package/dist/schemes/_search-exclusion.js.map +1 -0
  383. package/dist/schemes/_search-index.d.ts +6 -0
  384. package/dist/schemes/_search-index.d.ts.map +1 -0
  385. package/dist/schemes/_search-index.js +328 -0
  386. package/dist/schemes/_search-index.js.map +1 -0
  387. package/dist/schemes/cosine.js +1 -1
  388. package/dist/schemes/cosine.js.map +1 -1
  389. package/dist/schemes/exec-abort.js +3 -3
  390. package/dist/schemes/exec-abort.js.map +1 -1
  391. package/dist/schemes/exec-env.js +3 -3
  392. package/dist/schemes/exec-env.js.map +1 -1
  393. package/dist/server/BranchBatches.d.ts +49 -0
  394. package/dist/server/BranchBatches.d.ts.map +1 -0
  395. package/dist/server/BranchBatches.js +592 -0
  396. package/dist/server/BranchBatches.js.map +1 -0
  397. package/dist/server/Daemon.d.ts +39 -71
  398. package/dist/server/Daemon.d.ts.map +1 -1
  399. package/dist/server/Daemon.js +1133 -491
  400. package/dist/server/Daemon.js.map +1 -1
  401. package/dist/server/DaemonModule.d.ts +31 -0
  402. package/dist/server/DaemonModule.d.ts.map +1 -0
  403. package/dist/server/DaemonModule.js +2 -0
  404. package/dist/server/DaemonModule.js.map +1 -0
  405. package/dist/server/branch-batch.sql +154 -0
  406. package/dist/server/client-input.d.ts +13 -0
  407. package/dist/server/client-input.d.ts.map +1 -1
  408. package/dist/server/client-input.js +268 -57
  409. package/dist/server/client-input.js.map +1 -1
  410. package/dist/server/dispatch-as-plurnk.d.ts +1 -1
  411. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  412. package/dist/server/dispatch-as-plurnk.js +15 -7
  413. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  414. package/dist/server/drain.sql +96 -65
  415. package/dist/server/envelope.d.ts +4 -7
  416. package/dist/server/envelope.d.ts.map +1 -1
  417. package/dist/server/envelope.js +118 -95
  418. package/dist/server/envelope.js.map +1 -1
  419. package/dist/server/envelope.sql +18 -23
  420. package/dist/server/exec-poll-backoff.js +1 -1
  421. package/dist/server/exec-poll-backoff.js.map +1 -1
  422. package/dist/server/lifecycle-recovery.sql +112 -2
  423. package/dist/server/logEntry.d.ts +4 -2
  424. package/dist/server/logEntry.d.ts.map +1 -1
  425. package/dist/server/logEntry.js +6 -3
  426. package/dist/server/logEntry.js.map +1 -1
  427. package/dist/server/logEntry.sql +1 -1
  428. package/dist/server/loop-model.d.ts.map +1 -1
  429. package/dist/server/loop-model.js +22 -9
  430. package/dist/server/loop-model.js.map +1 -1
  431. package/dist/server/loopDocs.d.ts.map +1 -1
  432. package/dist/server/loopDocs.js +5 -4
  433. package/dist/server/loopDocs.js.map +1 -1
  434. package/dist/server/seam-entry-read.sql +15 -10
  435. package/dist/server/seam-log-read.sql +2 -2
  436. package/dist/server/seam-loop.sql +1 -1
  437. package/dist/server/seam-proposal-list.sql +17 -7
  438. package/dist/service.d.ts.map +1 -1
  439. package/dist/service.js +79 -61
  440. package/dist/service.js.map +1 -1
  441. package/migrations/001_schema.sql +1010 -0
  442. package/package.json +65 -32
  443. package/dist/core/PluginLoader.d.ts +0 -20
  444. package/dist/core/PluginLoader.d.ts.map +0 -1
  445. package/dist/core/PluginLoader.js +0 -142
  446. package/dist/core/PluginLoader.js.map +0 -1
  447. package/dist/core/TelemetryChannel.d.ts +0 -37
  448. package/dist/core/TelemetryChannel.d.ts.map +0 -1
  449. package/dist/core/TelemetryChannel.js +0 -80
  450. package/dist/core/TelemetryChannel.js.map +0 -1
  451. package/dist/core/path-decode.d.ts +0 -3
  452. package/dist/core/path-decode.d.ts.map +0 -1
  453. package/dist/core/path-decode.js +0 -9
  454. package/dist/core/path-decode.js.map +0 -1
  455. package/dist/core/plugin-attribution.d.ts +0 -5
  456. package/dist/core/plugin-attribution.d.ts.map +0 -1
  457. package/dist/core/plugin-attribution.js +0 -39
  458. package/dist/core/plugin-attribution.js.map +0 -1
  459. package/dist/core/world-state.d.ts +0 -10
  460. package/dist/core/world-state.d.ts.map +0 -1
  461. package/dist/core/world-state.js +0 -35
  462. package/dist/core/world-state.js.map +0 -1
  463. package/dist/core/world-state.sql +0 -35
  464. package/dist/core/zero-pin.d.ts +0 -3
  465. package/dist/core/zero-pin.d.ts.map +0 -1
  466. package/dist/core/zero-pin.js +0 -16
  467. package/dist/core/zero-pin.js.map +0 -1
  468. package/dist/server/auto.d.ts +0 -6
  469. package/dist/server/auto.d.ts.map +0 -1
  470. package/dist/server/auto.js +0 -63
  471. package/dist/server/auto.js.map +0 -1
  472. package/dist/server/clientTurn.d.ts +0 -6
  473. package/dist/server/clientTurn.d.ts.map +0 -1
  474. package/dist/server/clientTurn.js +0 -22
  475. package/dist/server/clientTurn.js.map +0 -1
  476. package/dist/server/clientTurn.sql +0 -10
  477. package/dist/server/noProposals.d.ts +0 -6
  478. package/dist/server/noProposals.d.ts.map +0 -1
  479. package/dist/server/noProposals.js +0 -37
  480. package/dist/server/noProposals.js.map +0 -1
  481. package/dist/server/version-info.d.ts +0 -14
  482. package/dist/server/version-info.d.ts.map +0 -1
  483. package/dist/server/version-info.js +0 -69
  484. package/dist/server/version-info.js.map +0 -1
  485. package/migrations/0000-00-00.01_schema.sql +0 -536
  486. package/migrations/0004_loop-provider.sql +0 -3
  487. package/migrations/0005_subscription-published-channel.sql +0 -3
  488. package/migrations/0006_loop-max-turns.sql +0 -3
@@ -1,28 +1,31 @@
1
- import { parsePath } from "@plurnk/plurnk-grammar";
1
+ import { parsePath } from "@plurnk/plurnk-contracts";
2
2
  import { Policy } from "@plurnk/plurnk-execs";
3
- import { WebFetcher } from "@plurnk/plurnk-schemes-http";
3
+ import { WebFetcher, WebMaterializationError, } from "@plurnk/plurnk-schemes-http";
4
4
  import WorkspaceSettings from "../core/workspace-settings.js";
5
5
  import EffectPolicy from "./EffectPolicy.js";
6
- import EntryOps from "./_entry-ops.js";
7
6
  import EntryCrud from "./_entry-crud.js";
8
7
  import Owner from "../core/Owner.js";
9
- import { foldAuthorityIntoPath } from "../core/plurnk-uri.js";
10
- import { decodePathParens } from "../core/path-decode.js";
11
8
  import EntryFind from "./_entry-find.js";
12
9
  import ChannelWrite from "../core/ChannelWrite.js";
13
10
  import ExecEnv from "./exec-env.js";
14
11
  import ExecAbort from "./exec-abort.js";
15
- import { renderAddress } from "../core/plurnk-uri.js";
12
+ import { entryPathnameOf, renderAddress } from "../core/plurnk-uri.js";
16
13
  import { writeFile, unlink, stat } from "node:fs/promises";
17
14
  import { tmpdir } from "node:os";
18
15
  import { join, resolve } from "node:path";
19
16
  import { CoreSchemeAdapterBase } from "../core/CoreSchemeServices.js";
17
+ import ErrorDetail from "../core/ErrorDetail.js";
18
+ import Results, { OperationFailureError } from "../core/results.js";
19
+ import { InvalidOperationResultError, NetworkAddress } from "@plurnk/plurnk-schemes";
20
+ import DbProjectionCaps from "../core/caps/DbProjectionCaps.js";
21
+ import WorkerControlAddress from "../core/WorkerControlAddress.js";
22
+ import JournalTurn from "../core/JournalTurn.js";
23
+ import { setTimeout as delay } from "node:timers/promises";
20
24
  // Executors are discovered + probed at boot into ExecutorRegistry and reach
21
- // the scheme through ctx.executors (plurnk-service#181). Each runtime tag
25
+ // the scheme through ctx.executors ({§exec-registry-resolves}). Each runtime tag
22
26
  // resolves to its sibling executor; the scheme itself stays runtime-agnostic.
23
- // The local path a subprocess EXEC's `(target)` slot names — a bare local path or a file:/// URL (both
24
- // decode to a filesystem path). Stat-routed at dispatch (#462): a directory becomes cwd, a file is the
25
- // program/data-source. A plurnk-scheme target is NOT local — schemeTargetOf handles that.
27
+ // Extract the local arm of {§exec-target-routing}; non-file schemes are
28
+ // classified separately by schemeSourceOf.
26
29
  const localPathFromTarget = (target) => {
27
30
  if (target === null)
28
31
  return null;
@@ -33,22 +36,31 @@ const localPathFromTarget = (target) => {
33
36
  }
34
37
  return null;
35
38
  };
36
- // #201 — a plurnk-scheme target (known/exec/log/…), distinct from file/local
37
- // (which localPathFromTarget handles as a path). Its content is resolved at apply-time;
38
- // executors stay scheme-blind (SPEC §5), so the scheme — not the executor — reads it.
39
- const schemeTargetOf = (target) => {
39
+ // A non-file scheme target is distinct from the local path handled above. The
40
+ // consumer resolves its content at apply time; executors stay scheme-blind
41
+ // ({§executor-role}).
42
+ const schemeSourceOf = (target) => {
40
43
  if (target === null || target.kind !== "url")
41
44
  return null;
42
45
  if (target.scheme === null || target.scheme === "file")
43
46
  return null;
44
- return { scheme: target.scheme, pathname: target.pathname, fragment: target.fragment };
47
+ return target.raw;
45
48
  };
49
+ // {§exec-target-routing} — effect classifies the invocation core is admitting,
50
+ // before a scheme-backed source can be materialized. A scheme target with no
51
+ // body becomes the command, so the executor has no target. With a body, the
52
+ // scheme content becomes a target at run time; its authored address is the
53
+ // stable, opaque target-present identity used only for the one effect call.
54
+ const effectTargetOf = (authoredTarget, routedTarget, schemeSource, command) => schemeSource !== null && command.length > 0
55
+ ? authoredTarget?.raw ?? null
56
+ : routedTarget;
46
57
  // EXEC's pathname is <runtime>/<loop_seq>/<turn_seq>/<sequence> (stamped by
47
58
  // Dispatcher.#writeLog). Exec owns this convention, so it — not the client — turns
48
59
  // the pathname into the entry's coordinate, mirrored onto stream payloads so
49
- // waterfall clients read fields instead of parsing the URI (#224). The
60
+ // clients read fields instead of parsing the URI. The
50
61
  // coordinate is the trailing three segments (runtime-agnostic); a pathname that
51
62
  // isn't a numeric triple yields undefined (no coordinate on the wire).
63
+ // {§notifications-stream-event-on-channel-change}, {§notifications-stream-concluded}
52
64
  const coordinateFromPathname = (pathname) => {
53
65
  const seg = pathname.split("/").filter(Boolean);
54
66
  if (seg.length < 3)
@@ -61,7 +73,8 @@ const coordinateFromPathname = (pathname) => {
61
73
  // {§stream-owner-scoped} — resolve a stream statement's authority to the owning worker and hand
62
74
  // back the statement authority-stripped (the storage path is the bare loop coordinate; the owner
63
75
  // rides the owner_id column, never the pathname). Empty authority = the CALLING worker — your own
64
- // streams need no qualifier, so a fan-out sibling's identical coordinate can never be yours (#526).
76
+ // streams need no qualifier, so a fan-out sibling's identical coordinate can never be yours
77
+ // ({§stream-owner-scoped}).
65
78
  // A named authority = that worker's streams, ancestry-gated (reader must be the owner or an
66
79
  // ancestor); an unknown name or unpermitted reader resolves null → the face 404s, no existence leak.
67
80
  export const resolveStreamStatement = async (statement, ctx) => {
@@ -80,38 +93,36 @@ export default class Exec extends CoreSchemeAdapterBase {
80
93
  channels: { stdout: "text/stream", stderr: "text/stream" },
81
94
  defaultChannel: "stdout",
82
95
  category: "data",
83
- scope: "workspace",
84
96
  writableBy: ["model", "client"],
85
97
  volatile: true,
86
98
  modelVisible: true,
87
99
  example: "<<EXEC[sqlite]:SELECT 22.0 / 7.0:EXEC",
88
- documentation: "Runs a command in a runtime — `<<EXEC[runtime](cwd):command:EXEC` — output streams into the worker's `<runtime>:///<loop>/<turn>/<seq>` entry on the runtime's own channels (a subprocess → stdout/stderr; a computational runtime like sqlite/jq → a JSON `results` channel). A host-effecting command proposes for review before it runs; a read-only/pure one runs ungated. Either way you never fetch the output: the engine surfaces each turn's new stream bytes to you automatically — folded while the command runs, opened when it finishes.",
100
+ documentation: "Runs a command in a runtime — `<<EXEC[runtime](target):command:EXEC` — output streams into the worker's `<runtime>:///<loop>/<turn>/<seq>` entry on the runtime's own channels (a subprocess → stdout/stderr; a computational runtime like sqlite/jq → a JSON `results` channel). A host-effecting command proposes for review before it runs; a read-only/pure one runs ungated. Either way you never fetch the output: the engine surfaces each turn's new stream bytes to you automatically — folded while the command runs, opened when it finishes.",
89
101
  flags: {
90
102
  excludedInAsk: true,
91
103
  },
92
104
  };
93
- // The web-fetch the entry sink calls on content:null (§exec-entry-sink / #455). Default = schemes-http's
94
- // guarded WebFetcher over one warm-Chromium pool shared across this handler's
95
- // fallback renders; injectable so tests substitute the network.
105
+ // The web-fetch the entry sink calls on content:null ({§exec-entry-sink}).
106
+ // Default = schemes-http's checked WebFetcher; injectable for tests.
96
107
  #fetchWeb;
97
- #closeWebFetcher;
98
108
  constructor(fetchWeb) {
99
109
  super();
100
- const webFetcher = new WebFetcher();
101
110
  if (fetchWeb === undefined) {
111
+ const webFetcher = new WebFetcher();
102
112
  this.#fetchWeb = (url, opts) => webFetcher.fetch(url, opts);
103
- this.#closeWebFetcher = () => webFetcher.close();
104
113
  }
105
114
  else {
106
115
  this.#fetchWeb = fetchWeb;
107
- this.#closeWebFetcher = async () => { };
108
116
  }
109
117
  }
110
118
  #activeAborts = new Map();
111
119
  #activeSpawns = new Map();
112
120
  async idle() {
113
121
  await Promise.allSettled([...this.#activeSpawns.values()]);
114
- await this.#closeWebFetcher();
122
+ }
123
+ // {§handler-lifecycle} — idle is the streaming drain barrier.
124
+ async close() {
125
+ await this.idle();
115
126
  }
116
127
  // Whether the worker has an in-flight spawn (a background exec). The daemon
117
128
  // reads this only for loop.cancel's cancelled=true/false answer — the
@@ -123,23 +134,39 @@ export default class Exec extends CoreSchemeAdapterBase {
123
134
  return true;
124
135
  return false;
125
136
  }
126
- // §exec-hold-until-concluded — does the worker hold an in-flight spawn whose RUNTIME is in the
127
- // operator's hold set? The turn-hold exception (owner ruling): for streams we know and
128
- // control (the search family — one final JSON digest, seconds-bounded), the engine holds
129
- // the next packet until conclusion instead of giving the model a turn it can only waste.
137
+ // {§worker-optimistic-settlement} — wait on exactly the spawns initiated by
138
+ // this turn, ending immediately when all settle and never extending the
139
+ // opportunity to streams inherited from an earlier turn.
140
+ async settleTurnSpawns(workerId, turnId, timeoutMs, signal) {
141
+ signal?.throwIfAborted();
142
+ const pending = [...this.#activeAborts.entries()]
143
+ .filter(([, active]) => active.workerId === workerId && active.turnId === turnId)
144
+ .map(([subscriptionId]) => {
145
+ const spawn = this.#activeSpawns.get(subscriptionId);
146
+ if (spawn === undefined) {
147
+ throw new Error(`Active EXEC subscription ${subscriptionId} has no spawn settlement promise.`);
148
+ }
149
+ return spawn;
150
+ });
151
+ if (pending.length === 0)
152
+ return true;
153
+ if (timeoutMs === 0)
154
+ return false;
155
+ return await Promise.race([
156
+ Promise.allSettled(pending).then(() => true),
157
+ delay(timeoutMs, false, { signal, ref: false }),
158
+ ]);
159
+ }
160
+ // {§exec-hold-until-concluded} — match active spawns against the
161
+ // operator's runtime or runtime:effect hold selectors.
130
162
  hasActiveHoldSpawns(workerId, holdSet) {
131
163
  for (const { workerId: r, runtime, effect } of this.#activeAborts.values())
132
164
  if (r === workerId && (holdSet.has(runtime) || holdSet.has(`${runtime}:${effect}`)))
133
165
  return true;
134
166
  return false;
135
167
  }
136
- // Process-KILL (plurnk-service#203). A running (host/background) exec is
137
- // addressable by its coordinate pathname; KILL aborts that spawn's controller with
138
- // the model's signal — KILL[code] → exactly that signal once (KILL[9] = SIGKILL), a
139
- // bare KILL → the executor's SIGHUP default. The model owns escalation, so there
140
- // is no auto-escalation here. The full #203 status matrix: 200 killed (in-flight) · 410
141
- // killed-earlier (a prior abort closed the stream 499) · 304 already-exited (closed
142
- // with any other terminal status) · 404 unknown (no subscription for that coordinate).
168
+ // {§stream-control} — active KILL routes through the live controller;
169
+ // terminal and missing outcomes resolve from the durable subscription.
143
170
  async kill(pathname, signal, ctx, scheme = "exec") {
144
171
  const core = this.coreContext(ctx);
145
172
  for (const entry of this.#activeAborts.values()) {
@@ -151,32 +178,44 @@ export default class Exec extends CoreSchemeAdapterBase {
151
178
  // Not running — settle the outcome from the closed subscription's status, scoped to the
152
179
  // caller's own subscription (coordinates duplicate across workers, {§stream-owner-scoped}).
153
180
  // The error answers in the model's OWN runtime-tag address (sh:///…), never the retired
154
- // internal `exec` scheme (#527, {§fs-answer-in-canon}) — the model KILLs what it addressed.
155
- const terminal = await ChannelWrite.execTerminalStatus(core.db, { workspaceId: core.workspaceId, workerId: core.workerId, pathname });
156
- if (terminal === null)
157
- return { status: 404, error: `no active stream at ${scheme}://${pathname}` };
158
- if (terminal === 499)
159
- return { status: 410, error: `${scheme}://${pathname} was killed earlier` };
160
- return { status: 304 };
181
+ // internal `exec` machinery ({§fs-answer-in-canon}) — the model KILLs what it addressed.
182
+ const terminal = await ChannelWrite.execTerminalStatus(core.db, {
183
+ workspaceId: core.workspaceId,
184
+ workerId: core.workerId,
185
+ scheme,
186
+ pathname,
187
+ });
188
+ const target = renderAddress(scheme, pathname);
189
+ if (terminal === null) {
190
+ return Results.failure("scheme:exec", "stream-not-found", 404, `No stream exists at ${target}.`, {}, { target });
191
+ }
192
+ if (terminal === 499) {
193
+ return Results.failure("scheme:exec", "stream-already-killed", 410, `Stream ${target} was already killed.`, {}, {
194
+ target,
195
+ retryable: false,
196
+ });
197
+ }
198
+ return Results.failure("scheme:exec", "stream-already-terminal", 409, `${target} already concluded with status ${terminal}.`, {}, {
199
+ target,
200
+ terminalStatus: terminal,
201
+ retryable: false,
202
+ });
161
203
  }
162
204
  // EXEC op handler — the actual model-facing entry point per plurnk.md.
163
- // `<<EXEC[runtime](cwd):command:EXEC` →
164
- // signal=runtime, target=cwd (ParsedPath local/file or null), body=command.
205
+ // `<<EXEC[runtime](target):command:EXEC` →
206
+ // signal=runtime, target=optional source/program/cwd, body=command.
165
207
  //
166
208
  // Proposes (status=202) with attrs={runtime, cwd, command, pathname}.
167
209
  // applyResolution spawns the subprocess; output streams into the
168
210
  // coordinate-stamped <runtime>:///<pathname> entry's stdout/stderr channels
169
- // (e.g. sh:///1/1/2, §exec/#240). The model READs that entry on a subsequent turn.
211
+ // (e.g. sh:///1/1/2, {§exec}). The model READs that entry on a subsequent turn.
170
212
  async exec(statement, ctx) {
171
213
  const core = this.coreContext(ctx);
172
214
  let command = statement.body ?? "";
173
- // #201 — a plurnk-scheme target carries content the scheme resolves at
174
- // apply-time; an empty body is then legal (the target IS the script).
175
- const schemeTarget = schemeTargetOf(statement.target);
176
- // #462 — stat-route the local (target): a DIRECTORY overrides cwd (run the body IN it); a FILE is
177
- // the program/data-source the executor runs (body = stdin). A stat-miss falls to the file arm so the
178
- // runtime reports its own not-found, never a dispatch 400. cwd otherwise = the workspace workspace
179
- // (project_root) — the directory File writes to, and what a data-source target resolves against.
215
+ // Non-file data sources resolve after acceptance. {§exec-target-routing}
216
+ const schemeSource = schemeSourceOf(statement.target);
217
+ // A local directory overrides cwd; every other local path remains the
218
+ // executor target. {§exec-target-routing}
180
219
  const workspaceRow = await core.db.envelope_get_workspace.get({ id: core.workspaceId });
181
220
  const projectRoot = workspaceRow?.project_root ?? null;
182
221
  const localTarget = localPathFromTarget(statement.target);
@@ -190,66 +229,96 @@ export default class Exec extends CoreSchemeAdapterBase {
190
229
  routedTarget = null;
191
230
  }
192
231
  }
193
- catch { /* stat-miss → file arm; the runtime reports its own not-found */ }
232
+ catch (cause) {
233
+ if (cause?.code !== "ENOENT") {
234
+ console.error(`EXEC target classification failed for '${abs}':`, cause);
235
+ return Results.failure("scheme:exec", "target-classification-failed", 500, `EXEC target '${localTarget}' could not be inspected: ${ErrorDetail.preview(cause)}`, {}, {
236
+ target: localTarget,
237
+ stage: "target-classification",
238
+ });
239
+ }
240
+ }
194
241
  }
195
- // Empty body is legal when the target IS the program: a #201 scheme target (the target is the
196
- // script) or a #462 FILE target (run it, no stdin). Empty body with a directory target (nothing
197
- // to run) or no target at all → 400.
198
- if (command.length === 0 && schemeTarget === null && routedTarget === null) {
199
- return { status: 400, error: "EXEC requires a command body (or a scheme target to run)" };
242
+ // An empty body requires a file/program or scheme command source.
243
+ // {§exec-target-routing}
244
+ if (command.length === 0 && schemeSource === null && routedTarget === null) {
245
+ return Results.failure("scheme:exec", "command-required", 400, "EXEC requires a command body or an executable target.", {}, {
246
+ recovery: "Provide a command body or executable target.",
247
+ retryable: false,
248
+ });
200
249
  }
201
250
  const requested = typeof statement.signal === "string" ? statement.signal : "";
202
251
  const runtime = requested === "" ? "sh" : requested; // empty signal = default shell
203
252
  if (core.executors === undefined)
204
253
  throw new Error("exec dispatched without an executor registry");
205
254
  const workspaceExecs = (await WorkspaceSettings.read(core.db, core.workspaceId)).execs;
206
- // §exec-registry-resolves — a non-empty tag selects exactly one registered executable
255
+ // {§exec-registry-resolves} — a non-empty tag selects exactly one registered executable
207
256
  // tool. Unknown tags are not reinterpreted as shell command words: that would make the
208
257
  // executed command differ from the authored body. Bare EXEC remains the default-shell form.
209
258
  const resolved = core.executors.entry(runtime);
210
259
  if (resolved === undefined) {
211
260
  const available = core.executors.availableRuntimes()
212
261
  .filter((tag) => workspaceExecs === null || Policy.isEnabled(tag, workspaceExecs));
213
- return {
214
- status: 501,
215
- error: `\`${runtime}\` is not a registered executable tool. Use a registered tag, or run complete shell commands with bare EXEC or EXEC[sh]. available to this workspace: ${available.join(", ") || "(none)"}`,
216
- };
262
+ return Results.failure("scheme:exec", "runtime-not-registered", 501, `Executable tool '${runtime}' is not registered in this workspace.`, {}, {
263
+ requestedRuntime: runtime,
264
+ availableRuntimes: available,
265
+ ...(available.length > 0
266
+ ? {
267
+ recovery: available.includes("sh")
268
+ ? `Use bare EXEC for a shell command or select a registered executable tool: ${available.join(", ")}.`
269
+ : `Select a registered executable tool: ${available.join(", ")}.`,
270
+ }
271
+ : {}),
272
+ retryable: false,
273
+ });
217
274
  }
218
- // #328 — per-workspace client policy narrows the boot-registered set (subtractive). A tag the
219
- // workspace's client layer disables is ABSENT for this workspace — refused like an unavailable
220
- // runtime. Bare EXEC resolves to sh before this gate, so disabling sh also disables the default.
275
+ // {§operator-config-workspace-execs} — the workspace layer only narrows
276
+ // the registered set. Bare EXEC resolves to sh before the same gate.
221
277
  if (workspaceExecs !== null && !Policy.isEnabled(runtime, workspaceExecs)) {
222
- return { status: 501, error: `\`${runtime}\` is disabled for this workspace by client policy (PLURNK_EXECS_*)` };
278
+ const available = core.executors.availableRuntimes()
279
+ .filter((tag) => Policy.isEnabled(tag, workspaceExecs));
280
+ return Results.failure("scheme:exec", "runtime-disabled", 501, `Executable tool '${runtime}' is disabled by workspace policy.`, {}, {
281
+ requestedRuntime: runtime,
282
+ availableRuntimes: available,
283
+ recovery: available.length > 0
284
+ ? `Use an enabled executable tool: ${available.join(", ")}.`
285
+ : "Continue without executing a tool in this workspace.",
286
+ retryable: false,
287
+ });
223
288
  }
224
289
  if (!resolved.available) {
225
- const why = resolved.detail === undefined ? "" : `: ${resolved.detail}`;
226
- return { status: 501, error: `\`${runtime}\` is unavailable${why}` };
290
+ const why = resolved.detail === undefined ? "" : `: ${ErrorDetail.preview(resolved.detail)}`;
291
+ return Results.failure("scheme:exec", "runtime-unavailable", 501, `Executable tool '${runtime}' is unavailable${why}.`, {}, {
292
+ requestedRuntime: runtime,
293
+ retryable: false,
294
+ });
227
295
  }
228
- // The (target) slot the executor receives — its DATA SOURCE / program (jq input, sqlite db, or a
229
- // subprocess program run with the body as stdin; #15). A #462 directory target routed to cwd above
230
- // leaves this null (the body is the command, run in that directory).
296
+ // The local (target) realization the executor receives — its data source
297
+ // or program. A directory routes to cwd and a scheme source is resolved
298
+ // only after acceptance, so either is null here.
231
299
  const target = routedTarget;
232
- // Effect classifies by the target only, never by parsing the command (#289): host → propose,
233
- // read/pure → auto-run inline (plurnk-service#182).
234
- const policy = EffectPolicy.decide(resolved.executor.effect(target, command));
235
- // cwd is the workspace WORKSPACE (project_root) — the directory File writes to and a relative
236
- // data-source target resolves against (execs 0.4.26 §2) — UNLESS a #462 directory target overrode
237
- // it above, in which case the body runs in that directory. A file/data-source target never moves cwd.
300
+ // Derive one effect from the canonical logical target before any policy
301
+ // consumes it, then preserve that fact with the invocation (#107,
302
+ // {§executor-effect}). Leaves never receive authored command text.
303
+ const effectTarget = effectTargetOf(statement.target, target, schemeSource, command);
304
+ const effect = resolved.executor.effect(effectTarget);
305
+ // cwd is the workspace project_root unless target routing selected a
306
+ // directory override. {§exec-target-routing}, {§executor-sinks}
238
307
  // Pathname is assigned by Dispatcher.#writeLog as <runtime>/<loop_seq>/
239
308
  // <turn_seq>/<sequence> (executor-domain + coordinate, e.g. sh/1/1/2).
240
309
  // `pathname` is stamped into attrs at log-write time; applyResolution
241
310
  // reads it back here.
242
311
  // EXEC repurposes the `<L>` slot as `<timeout, poll>` (seconds): mark[0] caps the spawn's
243
- // lifetime, mark[1] sets the hibernation poll-wake cadence (§exec-poll). N>0 → deadline (504);
312
+ // lifetime, mark[1] sets the hibernation poll-wake cadence ({§exec-poll}). N>0 → deadline (504);
244
313
  // -1 / absent → unbounded (loop-life bounded); 0 → turn-scoped (reaped at the next pre-turn,
245
314
  // never surviving into the subsequent turn).
246
315
  const marks = statement.lineMarker?.marks;
247
316
  const timeoutSec = typeof marks?.[0] === "number" && marks[0] > 0 ? Math.floor(marks[0]) : undefined;
248
317
  const turnScoped = typeof marks?.[0] === "number" && marks[0] === 0;
249
- const pollSec = typeof marks?.[1] === "number" && marks[1] > 0 ? Math.floor(marks[1]) : undefined;
318
+ const pollSec = typeof marks?.[1] === "number" && marks[1] >= 0 ? Math.floor(marks[1]) : undefined;
250
319
  const attrs = {
251
- runtime, cwd, command, target, pathname: "", inline: policy === "auto",
252
- ...(schemeTarget !== null ? { schemeTarget } : {}),
320
+ runtime, cwd, command, target, pathname: "", effect,
321
+ ...(schemeSource !== null ? { schemeSource } : {}),
253
322
  ...(timeoutSec !== undefined ? { timeoutSec } : {}),
254
323
  ...(turnScoped ? { turnScoped: true } : {}),
255
324
  ...(pollSec !== undefined ? { pollSec } : {}),
@@ -257,7 +326,7 @@ export default class Exec extends CoreSchemeAdapterBase {
257
326
  // Body shown to client during proposal review — `$ command` is the
258
327
  // most-readable summary regardless of runtime.
259
328
  const preview = runtime !== "" ? `[${runtime}] ${command}` : `$ ${command}`;
260
- return { status: 202, body: preview, attrs }; // host runtime proposes with 202 — §exec-host-proposes
329
+ return { status: 202, body: preview, attrs }; // host runtime proposes with 202 — {§exec-host-proposes}
261
330
  }
262
331
  async applyResolution(args, ctx) {
263
332
  const core = this.coreContext(ctx);
@@ -267,25 +336,42 @@ export default class Exec extends CoreSchemeAdapterBase {
267
336
  const runtime = (typeof attrs.runtime === "string" && attrs.runtime !== "") ? attrs.runtime : "sh";
268
337
  const cwd = (typeof attrs.cwd === "string" && attrs.cwd.length > 0) ? attrs.cwd : null;
269
338
  let target = (typeof attrs.target === "string" && attrs.target.length > 0) ? attrs.target : null;
339
+ const effect = attrs.effect;
270
340
  if (typeof pathname !== "string" || pathname.length === 0) {
271
- return { status: 500, outcome: "missing_pathname" };
341
+ throw new InvalidOperationResultError("The accepted EXEC proposal is missing its stream pathname.");
272
342
  }
273
- // #201 — resolve a scheme-URI target to content (executors stay scheme-blind).
274
- // Empty body → the resolved content IS the command (run a stored script).
275
- // Non-empty body → materialize the content to a temp file whose path becomes
276
- // the runtime's data-source TARGET (the input for filters/sqlite/wasm).
343
+ if (!EffectPolicy.isEffect(effect)) {
344
+ throw new InvalidOperationResultError("The accepted EXEC proposal is missing its canonical effect fact.");
345
+ }
346
+ // Resolve a scheme target after acceptance: empty body uses the content
347
+ // as the command; otherwise a temporary file becomes the data target.
348
+ // {§exec-target-routing}
277
349
  let tempPath = null;
278
- if (attrs.schemeTarget !== undefined) {
279
- const { scheme, pathname: tPath, fragment } = attrs.schemeTarget;
280
- const read = await EntryCrud.readEntry(tPath, core, scheme);
281
- if (read.entry === null)
282
- return { status: 404, outcome: "scheme_target_not_found" };
283
- const channels = read.entry.channels;
284
- const channelName = fragment ?? (channels.body !== undefined ? "body" : Object.keys(channels)[0]);
285
- const content = channelName === undefined ? undefined : channels[channelName]?.content;
286
- // §channel-selection-unknown-channel-400 sibling fact — the miss names what exists.
287
- if (content === undefined)
288
- return { status: 404, outcome: "scheme_target_channel_not_found", error: `no channel #${channelName ?? fragment} at ${scheme}:///${tPath.replace(/^\//, "")}; channels: ${Object.keys(channels).join(", ")}` };
350
+ if (attrs.schemeSource !== undefined) {
351
+ const sourceTarget = parsePath(attrs.schemeSource);
352
+ if (sourceTarget?.kind !== "url" || sourceTarget.scheme === null || sourceTarget.scheme === "file") {
353
+ throw new InvalidOperationResultError("The accepted EXEC proposal has an invalid scheme source address.");
354
+ }
355
+ const read = await this.readExecSource({
356
+ op: "READ",
357
+ suffix: "",
358
+ signal: null,
359
+ target: sourceTarget,
360
+ lineMarker: { marks: [1, -1] },
361
+ body: null,
362
+ position: { line: 0, column: 0 },
363
+ }, core);
364
+ if (read.status >= 400) {
365
+ return Results.assert({ ...read, outcome: "scheme_source_read_failed" });
366
+ }
367
+ const content = read.content;
368
+ if (typeof content !== "string") {
369
+ return Results.failure("scheme:exec", "source-content-unavailable", 422, `Scheme '${sourceTarget.scheme}' did not supply content for the EXEC source.`, { outcome: "scheme_source_content_unavailable" }, {
370
+ scheme: sourceTarget.scheme,
371
+ sourceStatus: read.status,
372
+ retryable: false,
373
+ });
374
+ }
289
375
  if (command.length === 0) {
290
376
  command = content;
291
377
  }
@@ -295,25 +381,26 @@ export default class Exec extends CoreSchemeAdapterBase {
295
381
  target = tempPath;
296
382
  }
297
383
  }
298
- // #500 — the accept-half mirrors the propose-half's #462 stat-route: an empty body with a
299
- // LOCAL FILE target is legal (the target IS the program — the executor runs it as its script
300
- // positional, transient exec per the owner ruling). Empty body with no target of any kind
301
- // remains the contradiction.
384
+ // {§exec-target-routing} — a local file target is itself the program, so
385
+ // its body may be empty; no target and no command remains invalid.
302
386
  if (command.length === 0 && target === null) {
303
- return { status: 500, outcome: "missing_command" };
387
+ if (attrs.schemeSource !== undefined) {
388
+ return Results.failure("scheme:exec", "source-command-empty", 422, "The EXEC source resolved to an empty command.", { outcome: "scheme_source_command_empty" }, { retryable: false });
389
+ }
390
+ throw new InvalidOperationResultError("The accepted EXEC proposal has neither a command nor a target.");
304
391
  }
305
392
  // Resolve the runtime's executor from the boot registry, then seed
306
- // channels from its declared topology (Q1(b) in plurnk-service#174 —
307
- // executor declares, scheme honors). Each executor declares its own
393
+ // channels from its declared topology ({§executor-channels}). Each executor declares its own
308
394
  // shape (subprocess → stdout/stderr; search → results; etc.).
309
- if (core.executors === undefined)
310
- return { status: 500, outcome: "no_executor_registry" };
395
+ if (core.executors === undefined) {
396
+ throw new InvalidOperationResultError("An accepted EXEC proposal has no executor registry.");
397
+ }
311
398
  const resolved = core.executors.entry(runtime);
312
- if (resolved === undefined)
313
- return { status: 500, outcome: "no_executor" };
314
- // #485 — the per-tool effect (execs Effect: read/host/pure) rides the hold predicate so a
315
- // suffixed PLURNK_SERVICE_EXEC_HOLD entry (`github:read`) can hold one tool-class and not another.
316
- const effect = resolved.executor.effect(target, command);
399
+ if (resolved === undefined) {
400
+ throw new InvalidOperationResultError(`The '${runtime}' executor disappeared after its EXEC proposal.`);
401
+ }
402
+ // {§executor-effect}, {§exec-hold-until-concluded}, #107: the admitted
403
+ // effect fact rides the hold predicate unchanged through application.
317
404
  const seedChannels = {};
318
405
  for (const [name, decl] of Object.entries(resolved.executor.channels)) {
319
406
  seedChannels[name] = {
@@ -323,16 +410,23 @@ export default class Exec extends CoreSchemeAdapterBase {
323
410
  };
324
411
  }
325
412
  const seed = { channels: seedChannels, tags: [] };
326
- // §exec — the stream entry's scheme IS the runtime tag (sh/node), so it addresses by
413
+ // {§exec} — the stream entry's scheme IS the runtime tag (sh/node), so it addresses by
327
414
  // tag authority (sh:///l/t/s). The engine registers each runtime tag → this handler.
328
415
  const { entryId } = await EntryCrud.writeEntry(pathname, seed, core, runtime, core.workerId);
329
- if (entryId === null)
330
- return { status: 500, outcome: "entry_write_failed" };
416
+ if (entryId === null) {
417
+ return Results.failure("scheme:exec", "stream-entry-write-failed", 500, `The ${runtime} stream entry could not be created.`, {
418
+ outcome: "entry_write_failed",
419
+ }, {
420
+ runtime,
421
+ stage: "stream-creation",
422
+ retryable: false,
423
+ });
424
+ }
331
425
  const subscriptionId = await ChannelWrite.openSubscription(core.db, {
332
426
  workerId: core.workerId, entryId, scheme: runtime,
333
427
  handle: runtime !== "" ? `${runtime}: ${command}` : command,
334
- pollSeconds: typeof attrs.pollSec === "number" ? attrs.pollSec : null, // §exec-poll — hibernation wake cadence
335
- turnScoped: attrs.turnScoped === true, // §exec-poll — `<0>` reaped at the next pre-turn
428
+ pollSeconds: typeof attrs.pollSec === "number" ? attrs.pollSec : null, // {§exec-poll} — hibernation wake cadence
429
+ turnScoped: attrs.turnScoped === true, // {§exec-poll} — `<0>` reaped at the next pre-turn
336
430
  });
337
431
  const controller = new AbortController();
338
432
  let unlink = () => { };
@@ -345,14 +439,14 @@ export default class Exec extends CoreSchemeAdapterBase {
345
439
  // then re-check `aborted`: a listener added to an already-aborted signal never
346
440
  // fires, so a check-then-attach order LOSES an abort that lands in the gap (R1's
347
441
  // TOCTOU leak). Attach-then-check closes it; controller.abort is idempotent, so a
348
- // doubled fire is harmless. §exec-timeout
442
+ // doubled fire is harmless. {§exec-timeout}
349
443
  const onParentAbort = () => controller.abort(ExecAbort.teardownReason());
350
444
  parent.addEventListener("abort", onParentAbort, { once: true });
351
445
  unlink = () => parent.removeEventListener("abort", onParentAbort);
352
446
  if (parent.aborted)
353
447
  controller.abort(ExecAbort.teardownReason());
354
448
  }
355
- this.#activeAborts.set(subscriptionId, { workerId: core.workerId, pathname, runtime, effect, controller, unlink });
449
+ this.#activeAborts.set(subscriptionId, { workerId: core.workerId, turnId: core.turnId, pathname, runtime, effect, controller, unlink });
356
450
  this.liveSubscriptions().register(subscriptionId, {
357
451
  cancel: () => controller.abort(ExecAbort.teardownReason()),
358
452
  });
@@ -362,18 +456,19 @@ export default class Exec extends CoreSchemeAdapterBase {
362
456
  entryId, subscriptionId, signal: controller.signal, controller, tempPath,
363
457
  timeoutSec: typeof attrs.timeoutSec === "number" ? attrs.timeoutSec : null,
364
458
  });
365
- // Every exec backgrounds + streams (§exec-stream): no same-turn receipt — the output
459
+ // Every exec backgrounds + streams ({§exec-stream}): no same-turn receipt — the output
366
460
  // surfaces as the environment-observation injector's delta on the next turn (folded while
367
- // it runs, opened when it finishes). Pure/read commands still auto-accept (attrs.inline =
368
- // no human gate); they just resolve a turn later, uniformly with host streams.
461
+ // it runs, opened when it finishes). Pure/read commands still auto-accept
462
+ // from the preserved effect fact; they resolve a turn later, uniformly
463
+ // with host streams.
369
464
  this.#activeSpawns.set(subscriptionId, tail);
370
465
  return { status: 200, outcome: "started" };
371
466
  }
372
467
  // Bridge the executor's sink-style contract (write/setState/emit)
373
468
  // onto plurnk-service's storage primitives (appendToChannel,
374
- // setChannelState, ctx.pushTelemetry). Per plurnk-service#174 Q3,
375
- // executor TelemetryEvents flow through the same engine path as
376
- // grammar parse_errors — emit → buffer → next packet + live notify.
469
+ // setChannelState, ctx.pushNotice). Under {§executor-results},
470
+ // executor Notices flow through the same engine path as
471
+ // grammar parse advisories: emit, buffer, next packet, and live notify.
377
472
  //
378
473
  // write() and setState() callbacks must run in emission order:
379
474
  // appendToChannel reads channel state AFTER the append commits, so
@@ -383,7 +478,7 @@ export default class Exec extends CoreSchemeAdapterBase {
383
478
  async #runExecutor(opts) {
384
479
  const { executor, runtime, command, cwd, target, ctx, pathname, entryId, subscriptionId, signal, controller, timeoutSec, tempPath } = opts;
385
480
  const db = ctx.db;
386
- const coordinate = coordinateFromPathname(pathname); // #224 — stamped on stream/event + stream/concluded
481
+ const coordinate = coordinateFromPathname(pathname);
387
482
  // grammar 0.74.20 EXEC `<T>` — kill the spawn after T seconds. unref'd so a pending timer never
388
483
  // holds the process open; cleared in finally so a spawn that finishes first leaves no timer.
389
484
  let timedOut = false;
@@ -395,217 +490,262 @@ export default class Exec extends CoreSchemeAdapterBase {
395
490
  const enqueue = (op) => {
396
491
  queue = queue.then(op, op);
397
492
  };
398
- let closeStatus = 500;
399
- let exitLabel = "spawn_failed";
493
+ let result = Results.failure("scheme:exec", "executor-did-not-conclude", 500, `The '${runtime}' executor did not produce a terminal result.`, {}, {
494
+ runtime,
495
+ stage: "execution",
496
+ retryable: false,
497
+ });
498
+ let exitLabel = "did not conclude";
400
499
  let stdoutLength = 0;
401
500
  let stderrLength = 0;
402
- // §exec-entry-sink (#340) — the executor's entry() materialization request. The executor
403
- // owns zero substrate: it hands us (path, content, {tags, mimetype}) and WE create/update
404
- // the entry (writeEntry upsert; tags UNIONED — writeEntry alone replaces), then narrate ONE
405
- // EDIT row in the reserved plurnk worker's log (the fs-fiction pattern, source = the calling
406
- // run) — the existing env-delta ambience folds it into every worker's next packet. The row is a
407
- // FULL fiction: tx carries the statement (body = the written content — the journal records
408
- // the write, replay/fork-complete), rx carries the span (§edit-result-render, the whole
409
- // content numbered — a wholesale write's span IS the content; no diff, which would be a
410
- // pathological cost against a rewritten multi-MB page). Rendered folded by default, so no
411
- // body rides a packet uninvited — the meta line carries the honest OPEN weight.
412
- // Serialized: parallel entry() calls (allSettled) write in order; a rejection prunes that
413
- // survivor executor-side without breaking the chain. Lazy narration context: one plurnk-run
414
- // turn per spawn, not per entry.
501
+ // {§exec-entry-sink} {§env-delta-entry-materialization} — serialize
502
+ // executor entry() materializations and narrate them through the one
503
+ // reserved-worker turn owned by this spawn.
415
504
  let entryChain = Promise.resolve();
416
505
  let narration = null;
506
+ let callerSource;
507
+ const resolveCallerSource = async () => {
508
+ if (callerSource !== undefined)
509
+ return callerSource;
510
+ const caller = await db.worker_name_by_id.get({ worker_id: ctx.workerId });
511
+ if (caller === undefined)
512
+ throw new Error(`entry(): calling worker ${ctx.workerId} does not exist`);
513
+ callerSource = WorkerControlAddress.render(caller.name);
514
+ return callerSource;
515
+ };
417
516
  const entrySink = (path, content, opts) => {
418
517
  const parsed = parsePath(path);
419
- if (parsed === null || parsed.kind !== "url" || parsed.scheme === null)
518
+ if (parsed === null || parsed.kind !== "url")
420
519
  return Promise.reject(new Error(`entry(): '${path.slice(0, 80)}' is not a URL`));
421
520
  if (content !== null && opts.mimetype === undefined)
422
521
  return Promise.reject(new Error("entry(): mimetype is required when content is provided"));
423
- // §exec-entry-sink / #455 — content:null ⇒ core fetches the page through schemes-http's guarded
424
- // primitive. The fetch STARTS HERE, OFF the write-serialization chain, so concurrent entry() calls
425
- // fetch in PARALLEL (owner ruling: search fetches must not freeze the agent); only the entry WRITE
426
- // serializes on entryChain (db-write ordering). A null fetch is dead (guard-refused / unreachable /
427
- // non-2xx / non-textual / empty) and REJECTS, so the executor prunes that survivor. Non-null content
428
- // is the materialize-given-body path — the caller already holds the bytes and states their mimetype.
429
- const materialized = content === null
430
- ? this.#fetchWeb(path, { signal })
431
- : Promise.resolve({ body: content, mimetype: opts.mimetype });
522
+ const address = NetworkAddress.supports(parsed.scheme) ? NetworkAddress.from(parsed) : null;
523
+ if (address?.hasCredentials === true)
524
+ return Promise.reject(new Error("entry(): network URL userinfo is not allowed"));
525
+ const fetchAddress = address !== null && (address.scheme === "http" || address.scheme === "https")
526
+ ? address
527
+ : null;
528
+ const pathname = address?.pathname ?? entryPathnameOf(parsed);
529
+ const scheme = address?.scheme ?? parsed.scheme;
530
+ // {§exec-entry-sink}/{§web-search-retrieval} — start content:null
531
+ // acquisition before the write chain so fetches run in parallel;
532
+ // only durable entry writes serialize. A null result rejects the sink.
533
+ let materialized;
534
+ if (content === null) {
535
+ if (fetchAddress === null)
536
+ return Promise.reject(new Error("entry(): content:null requires an http(s):// URL"));
537
+ materialized = this.#fetchWeb(fetchAddress.url, { signal });
538
+ }
539
+ else {
540
+ materialized = Promise.resolve({
541
+ url: fetchAddress?.url ?? "http://localhost",
542
+ body: content,
543
+ mimetype: opts.mimetype,
544
+ allowTavily: false,
545
+ });
546
+ }
432
547
  const op = async () => {
433
548
  const fetched = await materialized;
434
549
  if (fetched === null)
435
550
  throw new Error(`entry(): '${path.slice(0, 80)}' is dead`);
436
- let { body, mimetype } = fetched;
437
- // External URLs arrive in transport-safe spelling, while entry identity uses
438
- // the grammar's canonical resolved path. Store the decoded identity; renderers
439
- // encode parentheses again when the address returns to the model.
440
- const pathname = decodePathParens(foldAuthorityIntoPath(parsed.hostname, parsed.pathname));
441
- const prior = await EntryCrud.readEntry(pathname, ctx, parsed.scheme);
442
- const tags = [...new Set([...(prior.entry?.tags ?? []), ...opts.tags])];
443
- // The web-fetch entry point: a fetched html page stores the handler's readable
444
- // projection as the decisive `body` (text/markdown — what READ serves, FIND matches,
445
- // FTS indexes, every weight reports) with the raw page under `html` (xpath + archive).
446
- // Scoped HERE, not writeEntry: only auto-fetched web content projects; authored files
447
- // stay verbatim (a `<user email=…>` roster's attribute data must survive a default READ).
448
- let channels = { body: { content: body, mimetype } };
449
- if (fetched.header !== undefined)
450
- channels.header = { content: fetched.header, mimetype: "text/plain" };
451
- let decisive = body;
452
- if (mimetype === "text/html") {
453
- if (ctx.mimetypes === undefined)
454
- throw new Error("entry(): HTML materialization requires the mimetype registry");
455
- let projected = (await ctx.mimetypes.process({ content: body, hint: "text/html" }, { channels: ["content"] })).content;
456
- if ((typeof projected !== "string" || projected.length === 0) && fetched.render !== undefined) {
457
- const rendered = await fetched.render();
458
- if (rendered !== null) {
459
- body = rendered.body;
460
- mimetype = rendered.mimetype;
461
- projected = (await ctx.mimetypes.process({ content: body, hint: "text/html" }, { channels: ["content"] })).content;
462
- }
463
- }
464
- if (typeof projected !== "string" || projected.length === 0) {
465
- throw new Error(`entry(): '${path.slice(0, 80)}' has no readable HTML projection`);
551
+ let web;
552
+ try {
553
+ web = await WebFetcher.materialize(fetched, new DbProjectionCaps(ctx));
554
+ }
555
+ catch (error) {
556
+ if (!signal.aborted && error instanceof WebMaterializationError) {
557
+ console.error("entry() web materialization failed", { path, error });
466
558
  }
467
- channels = {
468
- body: { content: projected, mimetype: "text/markdown" },
469
- html: { content: body, mimetype },
470
- ...(fetched.header === undefined ? {} : { header: { content: fetched.header, mimetype: "text/plain" } }),
471
- };
472
- decisive = projected;
559
+ throw error;
560
+ }
561
+ if (web === null)
562
+ throw new Error(`entry(): '${path.slice(0, 80)}' has no readable projection`);
563
+ if (web.body === undefined) {
564
+ throw new Error(web.bodyOutcome.failure?.detail
565
+ ?? `entry(): '${path.slice(0, 80)}' produced no readable body`);
473
566
  }
474
- const written = await EntryCrud.writeEntry(pathname, { channels, tags }, ctx, parsed.scheme);
567
+ const prior = await EntryCrud.readEntry(pathname, ctx, scheme);
568
+ const tags = [...new Set([...(prior.entry?.tags ?? []), ...opts.tags])];
569
+ // {§exec-entry-sink}/{§html-materialization} The shared
570
+ // materializer owns the decisive projection and its provenance.
571
+ const channels = WebFetcher.materializedChannels(web, content === null && fetchAddress !== null
572
+ ? { url: fetchAddress.url, method: "GET" }
573
+ : undefined);
574
+ const decisive = web.body.content;
575
+ const source = web.html?.content ?? decisive;
576
+ const causalSource = await resolveCallerSource();
577
+ const written = Results.assert(await EntryCrud.writeEntry(pathname, { channels, tags }, ctx, scheme));
475
578
  if (narration === null) {
476
- const run = await db.envelope_get_worker_by_name.get({ workspace_id: ctx.workspaceId, name: "plurnk" })
579
+ const worker = await db.envelope_get_worker_by_name.get({ workspace_id: ctx.workspaceId, name: "plurnk" })
477
580
  ?? await db.envelope_insert_worker.get({ workspace_id: ctx.workspaceId, name: "plurnk", origin: "plurnk" });
478
- if (run === undefined)
581
+ if (worker === undefined)
479
582
  throw new Error("entry(): plurnk worker resolution returned no row");
480
- const loop = await db.envelope_insert_client_loop.get({ worker_id: run.id });
583
+ const loop = await db.envelope_insert_client_loop.get({ worker_id: worker.id });
481
584
  if (loop === undefined)
482
585
  throw new Error("entry(): loop insert returned no row");
483
- const seqRow = await db.client_turn_next_sequence.get({ loop_id: loop.id });
484
- const turn = await db.client_turn_insert.get({ loop_id: loop.id, sequence: seqRow?.next ?? 1, packet: "{}" });
485
- if (turn === undefined)
486
- throw new Error("entry(): turn insert returned no row");
487
- narration = { workerId: run.id, loopId: loop.id, turnId: turn.id, seq: 1 };
586
+ const turn = await JournalTurn.insert(db, loop.id);
587
+ narration = {
588
+ workerId: worker.id,
589
+ loopId: loop.id,
590
+ loopSeq: loop.sequence,
591
+ turnId: turn.id,
592
+ turnSeq: turn.sequence,
593
+ seq: 1,
594
+ };
595
+ }
596
+ const sequence = narration.seq++;
597
+ if (written.problem !== undefined) {
598
+ Results.attachInstance(written, `log:///${narration.loopSeq}/${narration.turnSeq}/${sequence}/EDIT`);
488
599
  }
489
600
  await db.engine_insert_log_entry.get({
490
- worker_id: narration.workerId, loop_id: narration.loopId, turn_id: narration.turnId, sequence: narration.seq++,
601
+ worker_id: narration.workerId, loop_id: narration.loopId, turn_id: narration.turnId, sequence,
491
602
  // signal carries the tags — the SAME slot a model's EDIT[tags] uses, so the
492
603
  // ambient row renders its tags natively everywhere (packet meta line, digest).
493
- origin: "plurnk", source: String(ctx.workerId), op: "EDIT", suffix: "", signal: JSON.stringify(tags),
494
- scheme: parsed.scheme, username: null, password: null, hostname: null, port: null,
495
- pathname, params: null, fragment: null, lineMarker: null,
496
- tx: JSON.stringify({ op: "EDIT", body }), mimetype_tx: "application/json",
497
- rx: JSON.stringify({
498
- status: written.status, entryId: written.entryId, tags,
499
- span: decisive.split("\n").map((l, n) => `${n + 1}:${l}`).join("\n"),
500
- }), mimetype_rx: "application/json",
604
+ origin: "plurnk", source: causalSource, op: "EDIT", suffix: "", signal: JSON.stringify(tags),
605
+ scheme, username: null, password: null, hostname: null, port: null,
606
+ pathname, query: null, fragment: null, lineMarker: null,
607
+ tx: JSON.stringify({ op: "EDIT", body: source }), mimetype_tx: "application/json",
608
+ rx: JSON.stringify(written.problem === undefined
609
+ ? {
610
+ ...written,
611
+ tags,
612
+ span: decisive.split("\n").map((l, n) => `${n + 1}:${l}`).join("\n"),
613
+ }
614
+ : written),
615
+ mimetype_rx: "application/json",
501
616
  status_rx: written.status, tokens: ctx.tokenize?.(decisive) ?? 0, state: "resolved", outcome: null,
502
617
  // Durable provenance for clients/forensics. This is machine
503
618
  // ambience, not a human/model action waterfall item.
504
619
  attrs: JSON.stringify({ tags, kind: "entry_materialized" }),
505
620
  });
506
- return renderAddress(parsed.scheme, pathname);
621
+ if (written.problem !== undefined)
622
+ throw new OperationFailureError(written);
623
+ return renderAddress(scheme, pathname);
507
624
  };
508
625
  const run = entryChain.then(op, op);
509
626
  entryChain = run.then(() => undefined, () => undefined);
510
627
  return run;
511
628
  };
512
629
  try {
513
- let result = null;
514
630
  try {
515
- result = await executor.run({
631
+ const reported = await executor.run({
516
632
  runtime, command, cwd, target, signal,
517
633
  entry: entrySink,
518
- env: ExecEnv.scoped(), // SPEC §exec {§exec-env-scoped} — never plurnk's own secrets
519
- write: (channel, chunk) => enqueue(() => ChannelWrite.appendToChannel(db, {
520
- entryId, channel, chunk, notify: ctx.streamEventNotify, coordinate,
634
+ env: ExecEnv.scoped(), // SPEC {§exec} {§exec-env-scoped} — never plurnk's own secrets
635
+ write: (channel, chunk, mimetype) => enqueue(() => ChannelWrite.appendToChannel(db, {
636
+ entryId, channel, chunk, mimetype, notify: ctx.streamEventNotify, coordinate,
521
637
  })),
522
638
  setState: (channel, state) => enqueue(() => ChannelWrite.setChannelState(db, {
523
639
  entryId, channel, state, notify: ctx.streamEventNotify, coordinate,
524
640
  })),
525
- emit: (event) => {
526
- // The executor plugin's TelemetryEvent predates grammar's required `level`;
527
- // inject a default (forwarding the producer's own severity when it supplies one). #276
528
- const level = event.level ?? "info";
529
- ctx.pushTelemetry?.({ ...event, level });
530
- },
641
+ emit: (event) => ctx.pushNotice?.(event),
531
642
  });
532
643
  // Drain the queue so the subscription doesn't close before
533
644
  // final chunk events / state transitions have committed.
534
645
  await queue;
646
+ try {
647
+ result = Results.assert(reported);
648
+ }
649
+ catch (cause) {
650
+ console.error(`Executor '${runtime}' returned an invalid operation result:`, cause);
651
+ result = Results.failure("scheme:exec", "executor-invalid-result", 500, `The '${runtime}' executor returned an invalid operation result.`, {}, {
652
+ runtime,
653
+ stage: "result-validation",
654
+ retryable: false,
655
+ });
656
+ }
535
657
  }
536
658
  catch (cause) {
537
659
  // A rejecting driver must still CONCLUDE its stream — uncaught, the subscription sat
538
660
  // open forever and the floating spawn promise was an unhandled rejection.
539
- exitLabel = `driver_crashed: ${cause instanceof Error ? cause.message : String(cause)}`;
661
+ console.error(`Executor '${runtime}' threw outside its operation result contract:`, cause);
662
+ result = Results.failure("scheme:exec", "executor-threw", 500, `The '${runtime}' executor failed outside its operation result contract.`, {}, {
663
+ runtime,
664
+ stage: "execution",
665
+ retryable: false,
666
+ });
540
667
  }
541
- if (result !== null) {
542
- const exitCode = result.exitCode ?? -1;
543
- closeStatus = result.status;
544
- // A timeout aborts the spawn → the executor reports 499; restamp it 504 so the model
545
- // sees "ran out of time" distinct from a deliberate kill/cancel (§exec-timeout).
546
- if (timedOut && closeStatus === 499)
547
- closeStatus = 504;
548
- // The service's own abort knowledge outranks a driver's claim: a spawn we reaped
549
- // (teardown/kill/cancel) did not succeed, whatever status it resolved under abort.
550
- if (signal.aborted && !timedOut && closeStatus < 400) {
551
- closeStatus = 499;
552
- exitLabel = "reaped";
553
- }
554
- else
555
- exitLabel = closeStatus === 504 ? `timed out after ${timeoutSec}s`
556
- : closeStatus === 499 ? "aborted"
557
- : closeStatus === 500 && exitCode === -1 ? "spawn_failed"
558
- : `exit ${exitCode}`;
668
+ const exitCode = typeof result.exitCode === "number" ? result.exitCode : null;
669
+ // A timeout aborts the spawn → the executor reports 499; replace the
670
+ // complete result so status and Problem remain one valid truth.
671
+ if (timedOut) {
672
+ result = Results.failure("scheme:exec", "execution-timeout", 504, `Execution of '${runtime}' exceeded its ${timeoutSec}-second deadline.`, exitCode === null ? {} : { exitCode }, {
673
+ runtime,
674
+ timeoutSeconds: timeoutSec,
675
+ stage: "execution",
676
+ retryable: false,
677
+ });
678
+ // The service's own abort knowledge outranks a driver's claim: a
679
+ // spawn we reaped did not succeed, whatever it resolved under abort.
559
680
  }
560
- await ChannelWrite.closeSubscription(db, { subscriptionId, status: closeStatus });
681
+ else if (signal.aborted && result.status < 400) {
682
+ result = Results.failure("scheme:exec", "execution-cancelled", 499, `Execution of '${runtime}' was cancelled by the service.`, exitCode === null ? {} : { exitCode }, {
683
+ runtime,
684
+ stage: "execution",
685
+ retryable: false,
686
+ });
687
+ }
688
+ exitLabel = timedOut
689
+ ? `timed out after ${timeoutSec}s`
690
+ : result.status === 499
691
+ ? "aborted"
692
+ : exitCode !== null
693
+ ? `exit ${exitCode}`
694
+ : result.problem?.title.toLowerCase() ?? "completed";
695
+ await ChannelWrite.closeSubscription(db, { subscriptionId, result });
561
696
  const stdoutMeta = await db.channel_meta.get({ entry_id: entryId, channel: "stdout" });
562
697
  const stderrMeta = await db.channel_meta.get({ entry_id: entryId, channel: "stderr" });
563
698
  stdoutLength = stdoutMeta?.contentLength ?? 0;
564
699
  stderrLength = stderrMeta?.contentLength ?? 0;
565
700
  }
566
701
  finally {
567
- // §exec-entry-sink — drain the entry()/narration writes so the tail (hence idle()) is a
568
- // COMPLETE quiescence barrier: a consumer woken below, or a teardown awaiting idle(), must
569
- // not race an in-flight entry() write into a closing db (#432 teardown race). Each op is
570
- // enqueued as `.then(op, op)`, so entryChain never rejects — awaiting it in finally is safe.
702
+ // {§exec-entry-sink} — the spawn tail owns every serialized entry/narration write.
571
703
  await entryChain;
572
704
  if (timeoutTimer !== null)
573
705
  clearTimeout(timeoutTimer); // a finished spawn leaves no pending timer
574
- // #201 — a materialized data-source temp file outlives the spawn it fed;
575
- // unlink it once the worker settles (open-unlink is safe on Linux).
576
- if (tempPath !== null)
577
- await unlink(tempPath).catch(() => { });
706
+ // {§exec-source-temporary} — cleanup cannot rewrite the settled
707
+ // executor result, but an exceptional failure remains observable.
708
+ if (tempPath !== null) {
709
+ await unlink(tempPath).catch((cause) => {
710
+ console.error(`EXEC source temporary cleanup failed for '${tempPath}':`, cause);
711
+ });
712
+ }
578
713
  this.#activeAborts.get(subscriptionId)?.unlink();
579
714
  this.#activeAborts.delete(subscriptionId);
580
715
  this.liveSubscriptions().unregister(subscriptionId);
581
716
  this.#activeSpawns.delete(subscriptionId);
582
- // Every worker backgrounds now (§exec-stream) — wake a parked loop on completion so the
583
- // run resumes to the turn where the stream's terminal delta surfaces.
717
+ // Every worker backgrounds now ({§exec-stream}) — wake a parked loop on completion so the
718
+ // worker resumes to the turn where the stream's terminal delta surfaces.
584
719
  if (ctx.wakeWorkerNotify !== undefined) {
585
720
  ctx.wakeWorkerNotify({
586
721
  workspaceId: ctx.workspaceId, workerId: ctx.workerId,
587
- entryId, target: `${runtime}://${pathname}`, subscriptionId, closeStatus,
722
+ entryOwnerId: ctx.workerId, entryId, target: `${runtime}://${pathname}`, subscriptionId, result,
588
723
  scheme: runtime,
589
724
  summary: `${runtime}://${pathname} completed (${exitLabel}); stdout=${stdoutLength} bytes, stderr=${stderrLength} bytes`,
590
725
  ...coordinate,
591
726
  });
592
727
  }
593
728
  }
594
- return closeStatus;
729
+ return result;
595
730
  }
596
- async read(statement, ctx) {
597
- const core = this.coreContext(ctx);
598
- const owner = await resolveStreamStatement(statement, core);
599
- if (owner === null)
600
- return { status: 404, content: null, mimetype: null, channel: null };
601
- return EntryOps.readWorkspaceEntry(owner.statement, core, Exec.manifest, owner.ownerId);
731
+ async resolveEntryAddress(target, ctx) {
732
+ if (target.kind !== "url")
733
+ return null;
734
+ const ownerId = await Owner.resolveStreamOwner(target.hostname, this.coreContext(ctx));
735
+ return ownerId === null
736
+ ? Results.failure("scheme:exec", "stream-not-found", 404, "No visible stream exists at the requested address.")
737
+ : { pathname: target.pathname, ownerId };
602
738
  }
603
739
  async find(statement, ctx) {
604
740
  const core = this.coreContext(ctx);
605
741
  const owner = await resolveStreamStatement(statement, core);
606
- if (owner === null)
607
- return { status: 404, content: null, mimetype: null, results: [], itemsTokenTotal: 0, pathnames: [], matches: [] };
608
- return EntryFind.findWorkspaceEntries(owner.statement, core, Exec.manifest, owner.ownerId);
742
+ if (owner === null) {
743
+ return Results.failure("scheme:exec", "stream-not-found", 404, "No visible stream exists at the requested address.", {
744
+ content: null, mimetype: null, results: [], itemsTokenTotal: 0, returnedItemsTokenTotal: 0,
745
+ matchingPathCount: 0, matchLocationCount: 0,
746
+ });
747
+ }
748
+ return EntryFind.findWorkspaceEntries(owner.statement, core, Exec.manifest, { ownerId: owner.ownerId });
609
749
  }
610
750
  async readEntry(pathname, ctx) {
611
751
  const core = this.coreContext(ctx);