@plurnk/plurnk-service 4.2.1 → 4.3.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 (438) hide show
  1. package/INSTALL.md +7 -2
  2. package/README.md +3 -1
  3. package/SPEC.md +1977 -1397
  4. package/dist/build-info.d.ts.map +1 -1
  5. package/dist/build-info.js +25 -3
  6. package/dist/build-info.js.map +1 -1
  7. package/dist/build-info.json +1 -1
  8. package/dist/completions/_plurnk-service +1 -1
  9. package/dist/completions/plurnk-service.bash +1 -1
  10. package/dist/completions/plurnk-service.fish +1 -0
  11. package/dist/content/body-preview.js.map +1 -1
  12. package/dist/content/byte-view.d.ts.map +1 -1
  13. package/dist/content/byte-view.js +3 -2
  14. package/dist/content/byte-view.js.map +1 -1
  15. package/dist/content/edit-collision.d.ts +2 -1
  16. package/dist/content/edit-collision.d.ts.map +1 -1
  17. package/dist/content/edit-collision.js.map +1 -1
  18. package/dist/content/edit-receipt.d.ts.map +1 -1
  19. package/dist/content/edit-receipt.js +28 -23
  20. package/dist/content/edit-receipt.js.map +1 -1
  21. package/dist/content/edited-span.js +1 -1
  22. package/dist/content/edited-span.js.map +1 -1
  23. package/dist/content/line-anchors.d.ts +3 -2
  24. package/dist/content/line-anchors.d.ts.map +1 -1
  25. package/dist/content/line-anchors.js +33 -19
  26. package/dist/content/line-anchors.js.map +1 -1
  27. package/dist/content/line-selection.js.map +1 -1
  28. package/dist/content/pattern-edits.js +1 -1
  29. package/dist/content/pattern-edits.js.map +1 -1
  30. package/dist/content/read-projector.d.ts +1 -1
  31. package/dist/content/read-projector.d.ts.map +1 -1
  32. package/dist/content/read-projector.js +10 -8
  33. package/dist/content/read-projector.js.map +1 -1
  34. package/dist/content/read-resolve.d.ts.map +1 -1
  35. package/dist/content/read-resolve.js +6 -5
  36. package/dist/content/read-resolve.js.map +1 -1
  37. package/dist/core/AdmittedTurnExecutor.d.ts +4 -4
  38. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  39. package/dist/core/AdmittedTurnExecutor.js +20 -18
  40. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  41. package/dist/core/AmbientMembers.d.ts +1 -1
  42. package/dist/core/AmbientMembers.d.ts.map +1 -1
  43. package/dist/core/AmbientMembers.js +5 -2
  44. package/dist/core/AmbientMembers.js.map +1 -1
  45. package/dist/core/BareBatchRunner.d.ts +1 -1
  46. package/dist/core/BareBatchRunner.d.ts.map +1 -1
  47. package/dist/core/BareBatchRunner.js +2 -2
  48. package/dist/core/BareBatchRunner.js.map +1 -1
  49. package/dist/core/CapabilityPolicies.js.map +1 -1
  50. package/dist/core/CapabilityResolver.d.ts +1 -2
  51. package/dist/core/CapabilityResolver.d.ts.map +1 -1
  52. package/dist/core/CapabilityResolver.js +11 -7
  53. package/dist/core/CapabilityResolver.js.map +1 -1
  54. package/dist/core/ChannelWrite.d.ts +2 -2
  55. package/dist/core/ChannelWrite.d.ts.map +1 -1
  56. package/dist/core/ChannelWrite.js.map +1 -1
  57. package/dist/core/ChannelWrite.sql +27 -0
  58. package/dist/core/ClientInteractions.js.map +1 -1
  59. package/dist/core/ContextFit.js +1 -1
  60. package/dist/core/ContextFit.js.map +1 -1
  61. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  62. package/dist/core/CoreSchemeServices.js +4 -4
  63. package/dist/core/CoreSchemeServices.js.map +1 -1
  64. package/dist/core/DataStatementRunner.d.ts.map +1 -1
  65. package/dist/core/DataStatementRunner.js +15 -15
  66. package/dist/core/DataStatementRunner.js.map +1 -1
  67. package/dist/core/Dispatcher.d.ts +29 -5
  68. package/dist/core/Dispatcher.d.ts.map +1 -1
  69. package/dist/core/Dispatcher.js +210 -130
  70. package/dist/core/Dispatcher.js.map +1 -1
  71. package/dist/core/EditMutations.d.ts.map +1 -1
  72. package/dist/core/EditMutations.js +12 -6
  73. package/dist/core/EditMutations.js.map +1 -1
  74. package/dist/core/EditSequence.js.map +1 -1
  75. package/dist/core/Engine.d.ts +12 -6
  76. package/dist/core/Engine.d.ts.map +1 -1
  77. package/dist/core/Engine.js +29 -7
  78. package/dist/core/Engine.js.map +1 -1
  79. package/dist/core/ExecutionOutputs.js.map +1 -1
  80. package/dist/core/ExecutorRegistry.d.ts +1 -2
  81. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  82. package/dist/core/ExecutorRegistry.js +2 -2
  83. package/dist/core/ExecutorRegistry.js.map +1 -1
  84. package/dist/core/FileAddress.js.map +1 -1
  85. package/dist/core/FilePublication.d.ts.map +1 -1
  86. package/dist/core/FilePublication.js +6 -3
  87. package/dist/core/FilePublication.js.map +1 -1
  88. package/dist/core/FileSnapshot.d.ts.map +1 -1
  89. package/dist/core/FileSnapshot.js +8 -5
  90. package/dist/core/FileSnapshot.js.map +1 -1
  91. package/dist/core/KillHandler.d.ts.map +1 -1
  92. package/dist/core/KillHandler.js +7 -0
  93. package/dist/core/KillHandler.js.map +1 -1
  94. package/dist/core/LiveSubscription.d.ts +1 -0
  95. package/dist/core/LiveSubscription.d.ts.map +1 -1
  96. package/dist/core/LiveSubscription.js +3 -2
  97. package/dist/core/LiveSubscription.js.map +1 -1
  98. package/dist/core/LiveSubscriptions.d.ts +4 -1
  99. package/dist/core/LiveSubscriptions.d.ts.map +1 -1
  100. package/dist/core/LiveSubscriptions.js +42 -1
  101. package/dist/core/LiveSubscriptions.js.map +1 -1
  102. package/dist/core/LogBody.d.ts.map +1 -1
  103. package/dist/core/LogBody.js +22 -19
  104. package/dist/core/LogBody.js.map +1 -1
  105. package/dist/core/LogVisibility.d.ts +1 -1
  106. package/dist/core/LogVisibility.d.ts.map +1 -1
  107. package/dist/core/LogVisibility.js +4 -6
  108. package/dist/core/LogVisibility.js.map +1 -1
  109. package/dist/core/LogWriter.d.ts.map +1 -1
  110. package/dist/core/LogWriter.js +6 -9
  111. package/dist/core/LogWriter.js.map +1 -1
  112. package/dist/core/LoopDriver.d.ts +4 -4
  113. package/dist/core/LoopDriver.d.ts.map +1 -1
  114. package/dist/core/LoopDriver.js +4 -5
  115. package/dist/core/LoopDriver.js.map +1 -1
  116. package/dist/core/LoopLifecycle.d.ts +2 -0
  117. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  118. package/dist/core/LoopLifecycle.js +13 -3
  119. package/dist/core/LoopLifecycle.js.map +1 -1
  120. package/dist/core/LoopLifecycle.sql +4 -4
  121. package/dist/core/LoopOutcome.d.ts.map +1 -1
  122. package/dist/core/LoopOutcome.js +2 -2
  123. package/dist/core/LoopOutcome.js.map +1 -1
  124. package/dist/core/MembershipMaterialization.d.ts.map +1 -1
  125. package/dist/core/MembershipMaterialization.js +29 -29
  126. package/dist/core/MembershipMaterialization.js.map +1 -1
  127. package/dist/core/MessageResources.d.ts +5 -1
  128. package/dist/core/MessageResources.d.ts.map +1 -1
  129. package/dist/core/MessageResources.js +12 -0
  130. package/dist/core/MessageResources.js.map +1 -1
  131. package/dist/core/MessageResources.sql +8 -0
  132. package/dist/core/MutationEffects.d.ts +1 -0
  133. package/dist/core/MutationEffects.d.ts.map +1 -1
  134. package/dist/core/MutationEffects.js +14 -10
  135. package/dist/core/MutationEffects.js.map +1 -1
  136. package/dist/core/OperationHistory.d.ts.map +1 -1
  137. package/dist/core/OperationHistory.js +4 -3
  138. package/dist/core/OperationHistory.js.map +1 -1
  139. package/dist/core/OperatorConfig.d.ts.map +1 -1
  140. package/dist/core/OperatorConfig.js +3 -2
  141. package/dist/core/OperatorConfig.js.map +1 -1
  142. package/dist/core/PacketBuilder.d.ts +1 -1
  143. package/dist/core/PacketBuilder.d.ts.map +1 -1
  144. package/dist/core/PacketBuilder.js +7 -5
  145. package/dist/core/PacketBuilder.js.map +1 -1
  146. package/dist/core/ProblemLog.d.ts +2 -5
  147. package/dist/core/ProblemLog.d.ts.map +1 -1
  148. package/dist/core/ProblemLog.js +2 -3
  149. package/dist/core/ProblemLog.js.map +1 -1
  150. package/dist/core/ProposalLifecycle.d.ts +12 -11
  151. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  152. package/dist/core/ProposalLifecycle.js +92 -84
  153. package/dist/core/ProposalLifecycle.js.map +1 -1
  154. package/dist/core/ReasoningEvent.d.ts +2 -0
  155. package/dist/core/ReasoningEvent.d.ts.map +1 -1
  156. package/dist/core/ReplyMetadata.d.ts.map +1 -1
  157. package/dist/core/ReplyMetadata.js +4 -3
  158. package/dist/core/ReplyMetadata.js.map +1 -1
  159. package/dist/core/ResourceBindings.d.ts +2 -2
  160. package/dist/core/ResourceBindings.d.ts.map +1 -1
  161. package/dist/core/ResourceBindings.js.map +1 -1
  162. package/dist/core/ResourceMutations.d.ts +1 -1
  163. package/dist/core/ResourceMutations.d.ts.map +1 -1
  164. package/dist/core/ResourceMutations.js +1 -2
  165. package/dist/core/ResourceMutations.js.map +1 -1
  166. package/dist/core/ResourceSelector.js +2 -2
  167. package/dist/core/ResourceSelector.js.map +1 -1
  168. package/dist/core/ResourceTransfers.d.ts.map +1 -1
  169. package/dist/core/ResourceTransfers.js +162 -120
  170. package/dist/core/ResourceTransfers.js.map +1 -1
  171. package/dist/core/SchemeRegistry.d.ts +6 -5
  172. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  173. package/dist/core/SchemeRegistry.js +32 -26
  174. package/dist/core/SchemeRegistry.js.map +1 -1
  175. package/dist/core/ServiceHelp.d.ts.map +1 -1
  176. package/dist/core/ServiceHelp.js +2 -1
  177. package/dist/core/ServiceHelp.js.map +1 -1
  178. package/dist/core/StoredPacket.d.ts.map +1 -1
  179. package/dist/core/StoredPacket.js +26 -19
  180. package/dist/core/StoredPacket.js.map +1 -1
  181. package/dist/core/StrikeRail.d.ts +1 -2
  182. package/dist/core/StrikeRail.d.ts.map +1 -1
  183. package/dist/core/StrikeRail.js +4 -4
  184. package/dist/core/StrikeRail.js.map +1 -1
  185. package/dist/core/TerminalResult.d.ts +0 -2
  186. package/dist/core/TerminalResult.d.ts.map +1 -1
  187. package/dist/core/TerminalResult.js +0 -18
  188. package/dist/core/TerminalResult.js.map +1 -1
  189. package/dist/core/ToolInputSchema.d.ts.map +1 -1
  190. package/dist/core/ToolInputSchema.js +2 -2
  191. package/dist/core/ToolInputSchema.js.map +1 -1
  192. package/dist/core/ToolResources.d.ts +1 -0
  193. package/dist/core/ToolResources.d.ts.map +1 -1
  194. package/dist/core/ToolResources.js +7 -6
  195. package/dist/core/ToolResources.js.map +1 -1
  196. package/dist/core/TurnDispositionHandler.d.ts +1 -1
  197. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  198. package/dist/core/TurnDispositionHandler.js +0 -1
  199. package/dist/core/TurnDispositionHandler.js.map +1 -1
  200. package/dist/core/TurnMaterialization.d.ts.map +1 -1
  201. package/dist/core/TurnMaterialization.js +3 -2
  202. package/dist/core/TurnMaterialization.js.map +1 -1
  203. package/dist/core/TurnRunner.d.ts +13 -12
  204. package/dist/core/TurnRunner.d.ts.map +1 -1
  205. package/dist/core/TurnRunner.js +174 -164
  206. package/dist/core/TurnRunner.js.map +1 -1
  207. package/dist/core/TurnRunner.sql +3 -3
  208. package/dist/core/WorkerOwners.js.map +1 -1
  209. package/dist/core/caps/DbEntryCaps.js.map +1 -1
  210. package/dist/core/caps/DbMessageCaps.d.ts +3 -3
  211. package/dist/core/caps/DbMessageCaps.d.ts.map +1 -1
  212. package/dist/core/caps/DbProjectionCaps.js +1 -1
  213. package/dist/core/caps/DbProjectionCaps.js.map +1 -1
  214. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  215. package/dist/core/caps/DbSubscriptionCaps.js +1 -0
  216. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  217. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  218. package/dist/core/caps/SchemeCtxImpl.js +4 -1
  219. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  220. package/dist/core/env-catalog.js.map +1 -1
  221. package/dist/core/env-defaults.d.ts.map +1 -1
  222. package/dist/core/env-defaults.js +5 -3
  223. package/dist/core/env-defaults.js.map +1 -1
  224. package/dist/core/file-creation-policy.d.ts.map +1 -1
  225. package/dist/core/file-creation-policy.js +2 -1
  226. package/dist/core/file-creation-policy.js.map +1 -1
  227. package/dist/core/file-materialization.d.ts.map +1 -1
  228. package/dist/core/file-materialization.js +9 -11
  229. package/dist/core/file-materialization.js.map +1 -1
  230. package/dist/core/git-env.d.ts +1 -0
  231. package/dist/core/git-env.d.ts.map +1 -1
  232. package/dist/core/git-env.js +34 -3
  233. package/dist/core/git-env.js.map +1 -1
  234. package/dist/core/git-membership.d.ts +5 -0
  235. package/dist/core/git-membership.d.ts.map +1 -1
  236. package/dist/core/git-membership.js +55 -21
  237. package/dist/core/git-membership.js.map +1 -1
  238. package/dist/core/git-state.d.ts.map +1 -1
  239. package/dist/core/git-state.js +1 -0
  240. package/dist/core/git-state.js.map +1 -1
  241. package/dist/core/packet-wire.d.ts +26 -21
  242. package/dist/core/packet-wire.d.ts.map +1 -1
  243. package/dist/core/packet-wire.js +150 -112
  244. package/dist/core/packet-wire.js.map +1 -1
  245. package/dist/core/results.d.ts +9 -4
  246. package/dist/core/results.d.ts.map +1 -1
  247. package/dist/core/results.js +14 -1
  248. package/dist/core/results.js.map +1 -1
  249. package/dist/core/runtime-floor.d.ts +8 -0
  250. package/dist/core/runtime-floor.d.ts.map +1 -0
  251. package/dist/core/runtime-floor.js +40 -0
  252. package/dist/core/runtime-floor.js.map +1 -0
  253. package/dist/core/runtime-guard.d.ts +2 -0
  254. package/dist/core/runtime-guard.d.ts.map +1 -0
  255. package/dist/core/runtime-guard.js +5 -0
  256. package/dist/core/runtime-guard.js.map +1 -0
  257. package/dist/core/sqlite-synchronous.d.ts +1 -1
  258. package/dist/core/sqlite-synchronous.d.ts.map +1 -1
  259. package/dist/core/sqlite-synchronous.js.map +1 -1
  260. package/dist/core/statement-primary.d.ts +2 -2
  261. package/dist/core/statement-primary.d.ts.map +1 -1
  262. package/dist/core/statement-primary.js.map +1 -1
  263. package/dist/core/workspace-settings.js +1 -1
  264. package/dist/core/workspace-settings.js.map +1 -1
  265. package/dist/evidence/EvidenceReader.d.ts.map +1 -1
  266. package/dist/evidence/EvidenceReader.js +20 -6
  267. package/dist/evidence/EvidenceReader.js.map +1 -1
  268. package/dist/launch/Launch.d.ts +1 -0
  269. package/dist/launch/Launch.d.ts.map +1 -1
  270. package/dist/launch/Launch.js +28 -12
  271. package/dist/launch/Launch.js.map +1 -1
  272. package/dist/man/plurnk-service.1 +6 -2
  273. package/dist/schemes/EffectPolicy.js +1 -1
  274. package/dist/schemes/EffectPolicy.js.map +1 -1
  275. package/dist/schemes/Env.d.ts +1 -1
  276. package/dist/schemes/Env.d.ts.map +1 -1
  277. package/dist/schemes/Env.js +7 -1
  278. package/dist/schemes/Env.js.map +1 -1
  279. package/dist/schemes/Exec.d.ts +10 -12
  280. package/dist/schemes/Exec.d.ts.map +1 -1
  281. package/dist/schemes/Exec.js +337 -437
  282. package/dist/schemes/Exec.js.map +1 -1
  283. package/dist/schemes/ExecOutputScheme.d.ts +4 -6
  284. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  285. package/dist/schemes/ExecOutputScheme.js +17 -17
  286. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  287. package/dist/schemes/ExecScheduler.d.ts.map +1 -1
  288. package/dist/schemes/ExecScheduler.js +1 -2
  289. package/dist/schemes/ExecScheduler.js.map +1 -1
  290. package/dist/schemes/ExecutionInput.js +3 -3
  291. package/dist/schemes/ExecutionInput.js.map +1 -1
  292. package/dist/schemes/File.d.ts +3 -18
  293. package/dist/schemes/File.d.ts.map +1 -1
  294. package/dist/schemes/File.js +136 -79
  295. package/dist/schemes/File.js.map +1 -1
  296. package/dist/schemes/Log.d.ts.map +1 -1
  297. package/dist/schemes/Log.js +193 -161
  298. package/dist/schemes/Log.js.map +1 -1
  299. package/dist/schemes/QuestionTool.d.ts.map +1 -1
  300. package/dist/schemes/QuestionTool.js +6 -8
  301. package/dist/schemes/QuestionTool.js.map +1 -1
  302. package/dist/schemes/ResourceTreeScheme.d.ts.map +1 -1
  303. package/dist/schemes/ResourceTreeScheme.js +2 -2
  304. package/dist/schemes/ResourceTreeScheme.js.map +1 -1
  305. package/dist/schemes/TurnSource.js +3 -3
  306. package/dist/schemes/TurnSource.js.map +1 -1
  307. package/dist/schemes/Worker.js.map +1 -1
  308. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  309. package/dist/schemes/_entry-crud.js +4 -2
  310. package/dist/schemes/_entry-crud.js.map +1 -1
  311. package/dist/schemes/_entry-find.d.ts.map +1 -1
  312. package/dist/schemes/_entry-find.js +187 -139
  313. package/dist/schemes/_entry-find.js.map +1 -1
  314. package/dist/schemes/_entry-manifest.js +1 -1
  315. package/dist/schemes/_entry-manifest.js.map +1 -1
  316. package/dist/schemes/_entry-ops.js.map +1 -1
  317. package/dist/schemes/_search-index.js.map +1 -1
  318. package/dist/schemes/exec-entry-sink.d.ts +30 -0
  319. package/dist/schemes/exec-entry-sink.d.ts.map +1 -0
  320. package/dist/schemes/exec-entry-sink.js +238 -0
  321. package/dist/schemes/exec-entry-sink.js.map +1 -0
  322. package/dist/schemes/exec-lifetime.d.ts.map +1 -1
  323. package/dist/schemes/exec-lifetime.js +5 -2
  324. package/dist/schemes/exec-lifetime.js.map +1 -1
  325. package/dist/server/ClientOperations.d.ts +45 -0
  326. package/dist/server/ClientOperations.d.ts.map +1 -0
  327. package/dist/server/ClientOperations.js +192 -0
  328. package/dist/server/ClientOperations.js.map +1 -0
  329. package/dist/server/ClientReads.d.ts +13 -5
  330. package/dist/server/ClientReads.d.ts.map +1 -1
  331. package/dist/server/ClientReads.js +108 -21
  332. package/dist/server/ClientReads.js.map +1 -1
  333. package/dist/server/Credentials.js +2 -2
  334. package/dist/server/Credentials.js.map +1 -1
  335. package/dist/server/Daemon.d.ts +40 -28
  336. package/dist/server/Daemon.d.ts.map +1 -1
  337. package/dist/server/Daemon.js +250 -839
  338. package/dist/server/Daemon.js.map +1 -1
  339. package/dist/server/DaemonLock.d.ts +2 -0
  340. package/dist/server/DaemonLock.d.ts.map +1 -1
  341. package/dist/server/DaemonLock.js +10 -1
  342. package/dist/server/DaemonLock.js.map +1 -1
  343. package/dist/server/DaemonModules.d.ts +39 -0
  344. package/dist/server/DaemonModules.d.ts.map +1 -0
  345. package/dist/server/DaemonModules.js +219 -0
  346. package/dist/server/DaemonModules.js.map +1 -0
  347. package/dist/server/DrainSupervisor.d.ts +4 -2
  348. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  349. package/dist/server/DrainSupervisor.js +318 -290
  350. package/dist/server/DrainSupervisor.js.map +1 -1
  351. package/dist/server/EnvFunctionality.d.ts +10 -7
  352. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  353. package/dist/server/EnvFunctionality.js +6 -5
  354. package/dist/server/EnvFunctionality.js.map +1 -1
  355. package/dist/server/Functionality.d.ts +4 -5
  356. package/dist/server/Functionality.d.ts.map +1 -1
  357. package/dist/server/Functionality.js +19 -15
  358. package/dist/server/Functionality.js.map +1 -1
  359. package/dist/server/HttpListener.d.ts.map +1 -1
  360. package/dist/server/HttpListener.js +28 -8
  361. package/dist/server/HttpListener.js.map +1 -1
  362. package/dist/server/MembersFunctionality.d.ts +19 -7
  363. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  364. package/dist/server/MembersFunctionality.js +30 -16
  365. package/dist/server/MembersFunctionality.js.map +1 -1
  366. package/dist/server/MessageDelivery.d.ts +43 -0
  367. package/dist/server/MessageDelivery.d.ts.map +1 -0
  368. package/dist/server/MessageDelivery.js +186 -0
  369. package/dist/server/MessageDelivery.js.map +1 -0
  370. package/dist/server/ModuleHost.d.ts +3 -3
  371. package/dist/server/ModuleHost.d.ts.map +1 -1
  372. package/dist/server/Places.d.ts +19 -0
  373. package/dist/server/Places.d.ts.map +1 -0
  374. package/dist/server/Places.js +76 -0
  375. package/dist/server/Places.js.map +1 -0
  376. package/dist/server/PluginSources.d.ts.map +1 -1
  377. package/dist/server/PluginSources.js +2 -1
  378. package/dist/server/PluginSources.js.map +1 -1
  379. package/dist/server/PlurnkSkill.d.ts.map +1 -1
  380. package/dist/server/PlurnkSkill.js +5 -4
  381. package/dist/server/PlurnkSkill.js.map +1 -1
  382. package/dist/server/Rollback.d.ts.map +1 -1
  383. package/dist/server/Rollback.js +2 -1
  384. package/dist/server/Rollback.js.map +1 -1
  385. package/dist/server/WorkerModels.d.ts +83 -0
  386. package/dist/server/WorkerModels.d.ts.map +1 -0
  387. package/dist/server/WorkerModels.js +500 -0
  388. package/dist/server/WorkerModels.js.map +1 -0
  389. package/dist/server/WorkerOverview.d.ts +22 -0
  390. package/dist/server/WorkerOverview.d.ts.map +1 -0
  391. package/dist/server/WorkerOverview.js +42 -0
  392. package/dist/server/WorkerOverview.js.map +1 -0
  393. package/dist/server/WorkspacePlugins.d.ts +1 -1
  394. package/dist/server/WorkspacePlugins.d.ts.map +1 -1
  395. package/dist/server/WorkspacePlugins.js +0 -1
  396. package/dist/server/WorkspacePlugins.js.map +1 -1
  397. package/dist/server/WorkspaceResidency.d.ts +3 -3
  398. package/dist/server/WorkspaceResidency.d.ts.map +1 -1
  399. package/dist/server/WorkspaceResidency.js.map +1 -1
  400. package/dist/server/client-input.d.ts +2 -2
  401. package/dist/server/client-input.d.ts.map +1 -1
  402. package/dist/server/client-input.js +19 -12
  403. package/dist/server/client-input.js.map +1 -1
  404. package/dist/server/envelope.d.ts +4 -1
  405. package/dist/server/envelope.d.ts.map +1 -1
  406. package/dist/server/envelope.js +7 -3
  407. package/dist/server/envelope.js.map +1 -1
  408. package/dist/server/envelope.sql +10 -6
  409. package/dist/server/logEntry.d.ts +38 -1
  410. package/dist/server/logEntry.d.ts.map +1 -1
  411. package/dist/server/logEntry.js +9 -9
  412. package/dist/server/logEntry.js.map +1 -1
  413. package/dist/server/logEntry.sql +4 -2
  414. package/dist/server/loopDocs.d.ts.map +1 -1
  415. package/dist/server/loopDocs.js +11 -2
  416. package/dist/server/loopDocs.js.map +1 -1
  417. package/dist/server/module-discovery.d.ts +2 -3
  418. package/dist/server/module-discovery.d.ts.map +1 -1
  419. package/dist/server/module-discovery.js +6 -6
  420. package/dist/server/module-discovery.js.map +1 -1
  421. package/dist/server/places.sql +83 -0
  422. package/dist/server/seam-loop.sql +8 -0
  423. package/dist/server/worker-overview.sql +50 -0
  424. package/dist/service.d.ts +1 -0
  425. package/dist/service.d.ts.map +1 -1
  426. package/dist/service.js +35 -9
  427. package/dist/service.js.map +1 -1
  428. package/docs/members.md +11 -21
  429. package/migrations/033_hidden_places.sql +10 -0
  430. package/package.json +48 -57
  431. package/dist/observe/init.d.ts +0 -8
  432. package/dist/observe/init.d.ts.map +0 -1
  433. package/dist/observe/init.js +0 -163
  434. package/dist/observe/init.js.map +0 -1
  435. package/dist/server/WorkerModelResolver.d.ts +0 -20
  436. package/dist/server/WorkerModelResolver.d.ts.map +0 -1
  437. package/dist/server/WorkerModelResolver.js +0 -200
  438. package/dist/server/WorkerModelResolver.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { parsePath } from "@plurnk/plurnk-parser";
2
- import { PathSyntax } from "@plurnk/plurnk-contracts";
3
- import { WebMaterializationError, } from "@plurnk/plurnk-schemes";
2
+ import { errnoCode, isExecutionOp, PathSyntax } from "@plurnk/plurnk-contracts";
3
+ import {} from "@plurnk/plurnk-contracts";
4
+ import { InvalidOperationResultError, MetadataOptions, } from "@plurnk/plurnk-schemes";
4
5
  import FunctionalityManager from "../server/FunctionalityManager.js";
5
6
  import EffectPolicy from "./EffectPolicy.js";
6
7
  import EntryCrud from "./_entry-crud.js";
7
8
  import EntryFind from "./_entry-find.js";
8
- import { MetadataOptions } from "@plurnk/plurnk-schemes";
9
9
  import ChannelWrite, {} from "../core/ChannelWrite.js";
10
10
  import LiveSubscription from "../core/LiveSubscription.js";
11
11
  import EnvFunctionality, {} from "../server/EnvFunctionality.js";
@@ -17,22 +17,13 @@ import { extname, isAbsolute, resolve } from "node:path";
17
17
  import { CoreSchemeAdapterBase } from "../core/CoreSchemeServices.js";
18
18
  import ErrorDetail from "../core/ErrorDetail.js";
19
19
  import Results, { OperationFailureError } from "../core/results.js";
20
- import { InvalidOperationResultError, NetworkAddress, ResourceNames, } from "@plurnk/plurnk-schemes";
21
- import DbProjectionCaps from "../core/caps/DbProjectionCaps.js";
22
- import WorkerControlAddress from "../core/WorkerControlAddress.js";
23
- import Turn from "../core/Turn.js";
24
- import AdministrativeLoop from "../core/AdministrativeLoop.js";
25
- import RuntimeWorker from "../core/RuntimeWorker.js";
26
- import LoopLifecycle from "../core/LoopLifecycle.js";
27
- import LogEntryProjection from "../core/LogEntryProjection.js";
28
- import LogBody from "../core/LogBody.js";
29
20
  import ToolResources from "../core/ToolResources.js";
30
21
  import { setTimeout as delay } from "node:timers/promises";
31
22
  import ExecScheduler, { readExecConcurrency } from "./ExecScheduler.js";
32
23
  import ExecScratch from "./ExecScratch.js";
33
24
  import ExecutionInput from "./ExecutionInput.js";
34
25
  import { execRouteOf } from "./exec-runtime.js";
35
- import {} from "@plurnk/plurnk-contracts";
26
+ import ExecEntrySink, {} from "./exec-entry-sink.js";
36
27
  // Executors are discovered + probed at boot into ExecutorRegistry and reach
37
28
  // the scheme through ctx.executors ({§exec-registry-resolves}). Each runtime tag
38
29
  // resolves to its sibling executor; the scheme itself stays runtime-agnostic.
@@ -216,22 +207,90 @@ export default class Exec extends CoreSchemeAdapterBase {
216
207
  // applyResolution spawns the subprocess; output streams into the
217
208
  // coordinate-stamped <runtime>:///<pathname> entry's stdout/stderr channels
218
209
  // (e.g. sh:///1/1/2/sh, {§exec}). The model READs that entry on a subsequent turn.
210
+ //
211
+ // Admission runs in the order its refusals are owed — the executor, its tool, the fence's shape,
212
+ // the target's routing, the service's reserved metadata, the executor's preparation, a script
213
+ // target's existence — and each step returns either the refusal or what the next step needs.
219
214
  async exec(statement, ctx) {
220
215
  const core = this.coreContext(ctx);
221
216
  const body = statement.body ?? "";
222
- if (core.executors === undefined)
217
+ const { executors } = core;
218
+ if (executors === undefined)
223
219
  throw new Error("exec dispatched without an executor registry");
224
220
  // {§exec-executor-slot}: the fence names the executor; the path is its program.
225
221
  const route = execRouteOf(statement);
226
222
  const runtime = route.runtime;
223
+ const resolved = Exec.#admitRuntime(executors, core.workspaceId, runtime);
224
+ if ("status" in resolved)
225
+ return resolved;
226
+ this.#prepareExecution();
227
+ const invocation = Exec.#admitTool(executors, core.workspaceId, resolved, runtime, route.target === null ? null : route.target.raw);
228
+ if ("status" in invocation)
229
+ return invocation;
230
+ const hasBody = body.length > 0;
231
+ const execTarget = route.target;
232
+ const hasTarget = execTarget !== null;
233
+ const shape = Exec.#checkInvocationShape(invocation, runtime, execTarget?.raw, hasBody, hasTarget);
234
+ if (shape !== null)
235
+ return shape;
236
+ const routed = await this.#routeTarget(core, { runtime, invocation, execTarget, hasBody });
237
+ if ("status" in routed)
238
+ return routed;
239
+ const { defaultCwd, resourceSource } = routed;
240
+ let { target } = routed;
241
+ let cwd = defaultCwd;
242
+ let inputDeclared = false;
243
+ const lifetime = Exec.#readReservedMetadata(statement, runtime);
244
+ if ("status" in lifetime)
245
+ return lifetime;
246
+ // {§executor-metadata} Options belong to the invoked executor for every source kind.
247
+ const prepared = await Exec.#prepareInvocation(resolved.executor, statement, { runtime, body, target: resourceSource ?? target, cwd });
248
+ if ("status" in prepared)
249
+ return prepared;
250
+ ({ cwd, inputDeclared } = prepared);
251
+ // A `script` target must be an existing file; other kinds pass through as declared.
252
+ if (target !== null && invocation.target?.kind === "script") {
253
+ const inspected = await Exec.#inspectScriptTarget(core, { runtime, target, cwd, defaultCwd, hasBody });
254
+ if ("status" in inspected)
255
+ return inspected;
256
+ ({ target, cwd } = inspected);
257
+ }
258
+ if (!hasBody && target === null && resourceSource === null) {
259
+ return Exec.#refuse(runtime, "input-required", `Executable tool '${runtime}' has no executable body or realized target.`, `Provide a ${invocation.body.role} body or a non-directory target.`);
260
+ }
261
+ // One logical target derives one effect fact before policy consumes it.
262
+ // A non-file resource keeps its exact authored address until materialization.
263
+ const effectTarget = resourceSource ?? target;
264
+ const effect = resolved.executor.effect(effectTarget);
265
+ // cwd is the workspace project_root unless target routing selected a
266
+ // directory override. {§exec-target-routing}, {§executor-sinks}
267
+ // The log writer claims the workspace output address before proposal application.
268
+ // {§exec-lifetime} — how long the spawn may live is the metadata's one field; the scope slot
269
+ // is text coordinates, which an execution has none of.
270
+ const attrs = {
271
+ runtime, cwd, body, target, pathname: "", effect,
272
+ ...(inputDeclared ? { input: "open" } : {}),
273
+ ...(resourceSource !== null ? { resourceSource } : {}),
274
+ ...lifetime,
275
+ };
276
+ const previewInput = body !== "" ? body : execTarget?.raw ?? "";
277
+ const preview = runtime !== "" ? `[${runtime}] ${previewInput}` : `$ ${previewInput}`;
278
+ return { status: 202, body: preview, attrs }; // host runtime proposes with 202 — {§exec-host-proposes}
279
+ }
280
+ // A refusal of the fence as written: 400, the runtime named, and the working form when there is one.
281
+ static #refuse(runtime, code, detail, recovery, extensions = {}) {
282
+ return Results.failure("scheme:exec", code, 400, detail, {}, { runtime, ...(recovery === undefined ? {} : { recovery }), retryable: false, ...extensions });
283
+ }
284
+ // The executor the fence names, or the refusal: unregistered, unavailable, or unconfigured.
285
+ static #admitRuntime(executors, workspaceId, runtime) {
227
286
  // {§exec-registry-resolves} — a non-empty tag selects exactly one registered executable
228
287
  // tool. Unknown tags are not reinterpreted as shell command words: that would make the
229
288
  // executed command differ from the authored body.
230
- const resolved = core.executors.entry(runtime, core.workspaceId);
289
+ const resolved = executors.entry(runtime, workspaceId);
231
290
  if (resolved === undefined) {
232
291
  return Results.failure("scheme:exec", "executor-not-registered", 400, `No executor or MCP service named \`${runtime}\` is registered for this worker.`, {}, {
233
292
  requestedRuntime: runtime,
234
- executors: core.executors.availableRuntimes(core.workspaceId),
293
+ executors: executors.availableRuntimes(workspaceId),
235
294
  retryable: false,
236
295
  });
237
296
  }
@@ -244,50 +303,60 @@ export default class Exec extends CoreSchemeAdapterBase {
244
303
  retryable: false,
245
304
  });
246
305
  }
247
- this.#prepareExecution();
248
- const registry = core.executors.toolRegistry(runtime, core.workspaceId);
249
- const exactTarget = route.target === null ? null : route.target.raw;
306
+ return resolved;
307
+ }
308
+ // The invocation contract the fence runs under: a family runtime's registered tool, named exactly, or
309
+ // the executor's own declaration.
310
+ static #admitTool(executors, workspaceId, resolved, runtime, exactTarget) {
311
+ const registry = executors.toolRegistry(runtime, workspaceId);
250
312
  const registeredTool = registry?.tools.find((tool) => tool.target === exactTarget);
251
313
  if (registry !== null && registeredTool === undefined) {
252
314
  const availableTargets = registry.tools.map((tool) => tool.target);
315
+ // {§executor-blank-invocation} No target: every enabled target's fence. An unknown one: the document listing them.
316
+ const recovery = availableTargets.length === 0 ? {}
317
+ : exactTarget === null ? { recovery: registry.tools.map((tool) => ToolResources.fence(runtime, tool.invocation, tool.target)).join("\n\n") }
318
+ : { recovery: `Select a target from worker://${ToolResources.documentPath(runtime, resolved.resourcesPath)}.` };
253
319
  return Results.failure("scheme:exec", exactTarget === null ? "target-required" : "target-not-registered", exactTarget === null ? 400 : 404, exactTarget === null
254
320
  ? `Runtime '${runtime}' requires one registered tool target.`
255
321
  : `Runtime '${runtime}' has no registered target '${exactTarget}'.`, {}, {
256
322
  runtime,
257
323
  availableTargetCount: availableTargets.length,
258
- ...(availableTargets.length === 0 ? {} : {
259
- recovery: `Select a target from worker://${ToolResources.documentPath(runtime, resolved.resourcesPath)}.`,
260
- }),
324
+ ...recovery,
261
325
  retryable: false,
262
326
  });
263
327
  }
264
- const invocation = registeredTool?.invocation ?? resolved.invocation;
265
- const hasBody = body.length > 0;
266
- const execTarget = route.target;
267
- const hasTarget = execTarget !== null;
268
- const refuse = (code, detail, recovery, extensions = {}) => Results.failure("scheme:exec", code, 400, detail, {}, { runtime, ...(recovery === undefined ? {} : { recovery }), retryable: false, ...extensions });
328
+ return registeredTool?.invocation ?? resolved.invocation;
329
+ }
330
+ // The fence's body and target against the invocation's declaration, refused by name. {§executor-blank-invocation} An
331
+ // absent input is answered with the invocation's own fence.
332
+ static #checkInvocationShape(invocation, runtime, exactTarget, hasBody, hasTarget) {
333
+ const fence = () => ToolResources.fence(runtime, invocation, exactTarget);
269
334
  if (invocation.body.required && !hasBody) {
270
- return refuse("body-required", `Executable tool '${runtime}' requires a ${invocation.body.role} body.`, `Provide the ${invocation.body.role} in the ${runtime} body.`);
335
+ return Exec.#refuse(runtime, "body-required", `Executable tool '${runtime}' requires a ${invocation.body.role} body.`, fence());
271
336
  }
272
337
  if (invocation.target === undefined && hasTarget) {
273
- return refuse("target-not-supported", `Executable tool '${runtime}' does not accept a target.`, `Remove the target and provide the ${invocation.body.role} in the body.`);
338
+ return Exec.#refuse(runtime, "target-not-supported", `Executable tool '${runtime}' does not accept a target.`, `Remove the target and provide the ${invocation.body.role} in the body.`);
274
339
  }
275
340
  if (invocation.target?.required === true && !hasTarget) {
276
- return refuse("target-required", `Executable tool '${runtime}' requires a ${invocation.target.role} target.`, `Provide the ${invocation.target.role} in (target).`);
341
+ return Exec.#refuse(runtime, "target-required", `Executable tool '${runtime}' requires a ${invocation.target.role} target.`, fence());
277
342
  }
278
343
  if (!hasBody && !hasTarget) {
279
- return refuse("input-required", `Executable tool '${runtime}' requires a body or target.`, `Provide ${invocation.target === undefined ? "a body" : "a body or target"}.`);
344
+ return Exec.#refuse(runtime, "input-required", `Executable tool '${runtime}' requires a body or target.`, fence());
280
345
  }
281
346
  if (invocation.exclusive === true && hasBody && hasTarget) {
282
- return refuse("input-conflict", `Executable tool '${runtime}' accepts its ${invocation.body.role} body and ${invocation.target?.role ?? "target"} target only as alternatives.`, "Provide either the body or target, not both.");
347
+ return Exec.#refuse(runtime, "input-conflict", `Executable tool '${runtime}' accepts its ${invocation.body.role} body and ${invocation.target?.role ?? "target"} target only as alternatives.`, "Provide either the body or target, not both.");
283
348
  }
349
+ return null;
350
+ }
351
+ // {§exec-target-routing} The working directory the run can always name, and the target as the executor
352
+ // will receive it: a literal, a local path, or a non-file resource realized at apply time.
353
+ async #routeTarget(core, args) {
354
+ const { runtime, invocation, execTarget, hasBody } = args;
284
355
  const workspaceRow = await core.db.envelope_get_workspace.get({ id: core.workspaceId });
285
356
  const projectRoot = workspaceRow?.project_root ?? null;
286
357
  // The working directory is always concrete — the project root, or the shell's own
287
358
  // cwd when the workspace has none — so every receipt can name it ({§exec-target-routing}).
288
359
  const defaultCwd = projectRoot ?? process.cwd();
289
- let cwd = defaultCwd;
290
- let inputDeclared = false;
291
360
  let target = null;
292
361
  let resourceSource = null;
293
362
  const targetDecl = invocation.target;
@@ -307,7 +376,7 @@ export default class Exec extends CoreSchemeAdapterBase {
307
376
  }
308
377
  }
309
378
  else {
310
- return refuse("target-kind-invalid", `Executable tool '${runtime}' accepts only a local or file:// ${targetDecl.role} target.`, `Use a local or file:// ${targetDecl.role} target.`, { target: execTarget.raw, targetKind: targetDecl.kind });
379
+ return Exec.#refuse(runtime, "target-kind-invalid", `Executable tool '${runtime}' accepts only a local or file:// ${targetDecl.role} target.`, `Use a local or file:// ${targetDecl.role} target.`, { target: execTarget.raw, targetKind: targetDecl.kind });
311
380
  }
312
381
  }
313
382
  }
@@ -323,7 +392,7 @@ export default class Exec extends CoreSchemeAdapterBase {
323
392
  // cannot see the host's temporary directory, and runs markdown where it can.
324
393
  if (resourceSource !== null && execTarget?.kind === "url" && execTarget.scheme === "worker"
325
394
  && isGeneratedPathname(PathSyntax.decodeParens(execTarget.pathname ?? ""))) {
326
- return refuse("target-is-documentation", `\`${execTarget.raw}\` is reference documentation the harness generated, not a program; ${runtime} cannot run it.`, undefined, { target: execTarget.raw });
395
+ return Exec.#refuse(runtime, "target-is-documentation", `\`${execTarget.raw}\` is reference documentation the harness generated, not a program; ${runtime} cannot run it.`, undefined, { target: execTarget.raw });
327
396
  }
328
397
  // {§exec-scratch-directory} — a standalone source is realized under the scratch directory,
329
398
  // where its executor must be able to read it; a directory the daemon cannot create or
@@ -332,12 +401,16 @@ export default class Exec extends CoreSchemeAdapterBase {
332
401
  const scratch = this.#sourceScratch();
333
402
  const unavailable = await scratch.unavailable();
334
403
  if (unavailable !== null) {
335
- return refuse("scratch-unavailable", `The execution scratch directory \`${scratch.directory}\` named by ${ExecScratch.KNOB} cannot be created or written (${unavailable}), so the ${runtime} source \`${execTarget.raw}\` cannot be realized.`, `The operator points ${ExecScratch.KNOB} at a writable absolute directory the ${runtime} executor can also read; meanwhile, target a program file the executor can reach, or put the command beneath a ${runtime} heading with no target.`, { target: execTarget.raw, scratch: scratch.directory, configuration: ExecScratch.KNOB });
404
+ return Exec.#refuse(runtime, "scratch-unavailable", `The execution scratch directory \`${scratch.directory}\` named by ${ExecScratch.KNOB} cannot be created or written (${unavailable}), so the ${runtime} source \`${execTarget.raw}\` cannot be realized.`, `The operator points ${ExecScratch.KNOB} at a writable absolute directory the ${runtime} executor can also read; meanwhile, target a program file the executor can reach, or put the command beneath a ${runtime} heading with no target.`, { target: execTarget.raw, scratch: scratch.directory, configuration: ExecScratch.KNOB });
336
405
  }
337
406
  }
407
+ return { defaultCwd, target, resourceSource };
408
+ }
409
+ // {§exec-lifetime} {§env-option} The service's reserved keys, read before the executor sees its options.
410
+ static #readReservedMetadata(statement, runtime) {
338
411
  // {§exec-lifetime} — the scope slot is text coordinates, and an execution has none.
339
412
  if (statement.lineMarker !== null)
340
- return refuse("scope-unsupported", "An execution takes no scope.", undefined);
413
+ return Exec.#refuse(runtime, "scope-unsupported", "An execution takes no scope.", undefined);
341
414
  // {§env-option} — the fence's own environment is the service's key: refused here by name,
342
415
  // so the model learns why, never dropped at the spawn. The executor never sees it.
343
416
  try {
@@ -359,84 +432,70 @@ export default class Exec extends CoreSchemeAdapterBase {
359
432
  runtime, recovery: `State one lifetime: ${LIFETIME_SYNTAX}.`, retryable: false,
360
433
  });
361
434
  }
362
- // {§executor-metadata} Options belong to the invoked executor for every source kind.
363
- const executor = resolved.executor;
435
+ return lifetime;
436
+ }
437
+ // {§executor-metadata} The executor's preparation of its own options: the working directory it chose
438
+ // and whether the invocation registers an input receiver; metadata without a preparer is refused.
439
+ static async #prepareInvocation(executor, statement, args) {
440
+ const { runtime, body, target, cwd } = args;
364
441
  if (executor.prepare !== undefined) {
365
- const prepared = Results.assert(await executor.prepare({
366
- runtime, body, target: resourceSource ?? target, cwd, metadata: statement.metadata,
367
- }));
442
+ const prepared = Results.assert(await executor.prepare({ runtime, body, target, cwd, metadata: statement.metadata }));
368
443
  if (prepared.status >= 400)
369
444
  return prepared;
370
445
  if (prepared.status !== 200 || (prepared.cwd !== null && (typeof prepared.cwd !== "string" || !isAbsolute(prepared.cwd)))) {
371
446
  throw new InvalidOperationResultError(`Executable tool '${runtime}' returned invalid invocation preparation.`);
372
447
  }
373
- cwd = prepared.cwd;
374
- inputDeclared = prepared.input === "open";
448
+ return { cwd: prepared.cwd, inputDeclared: prepared.input === "open" };
375
449
  }
376
- else if ((statement.metadata?.length ?? 0) > 0) {
450
+ if ((statement.metadata?.length ?? 0) > 0) {
377
451
  return Results.failure("scheme:exec", "metadata-unsupported", 400, `Executable tool '${runtime}' does not accept metadata.`, {}, { runtime, retryable: false });
378
452
  }
379
- // A `script` target must be an existing file; other kinds pass through as declared.
380
- if (target !== null && targetDecl?.kind === "script") {
381
- const inspected = isAbsolute(target) ? target : resolve(cwd ?? process.cwd(), target);
382
- let kind = "missing";
383
- try {
384
- kind = (await stat(inspected)).isDirectory() ? "directory" : "file";
385
- }
386
- catch (cause) {
387
- if (cause?.code !== "ENOENT") {
388
- console.error(`Execution target classification failed for '${inspected}':`, cause);
389
- return Results.failure("scheme:exec", "target-classification-failed", 500, `${runtime} target '${target}' could not be inspected: ${ErrorDetail.preview(cause)}`, {}, { target, stage: "target-classification" });
390
- }
391
- }
392
- // {§exec-target-near-miss} — a directory is never a program: with a body and no explicit
393
- // cwd, it is where the body runs. Never taught.
394
- if (kind === "directory" && hasBody && cwd === defaultCwd) {
395
- cwd = inspected;
396
- target = null;
397
- }
398
- else if (kind === "directory") {
399
- return refuse("target-not-a-program", `${runtime} target '${target}' is a directory, not a program.`, undefined, { target });
400
- }
401
- if (kind === "missing") {
402
- // The target is not a script here, but it may be a registered tool of another executor or an
403
- // executor itself: that is what exists, said as found (#388, {§diagnostic-observation}).
404
- const executors = core.executors;
405
- const ownerRuntimes = executors === undefined
406
- ? []
407
- : executors.availableRuntimes(core.workspaceId)
408
- .filter((tag) => executors.toolRegistry(tag, core.workspaceId)?.tools.some((tool) => tool.target === target) === true);
409
- const runtimeTarget = executors !== undefined && target !== null && target !== runtime
410
- && executors.availableRuntimes(core.workspaceId).includes(target);
411
- const recovery = ownerRuntimes.length > 0
412
- ? `The tool \`${target}\` is registered under executor \`${ownerRuntimes[0]}\`.`
413
- : runtimeTarget
414
- ? `\`${target}\` is its own executor.`
415
- : "The target must name an existing program resource.";
416
- return refuse("target-not-found", `The ${runtime} program does not resolve as a script or a registered tool for this executor.`, recovery, { target, ...(ownerRuntimes.length === 0 ? {} : { toolRuntimes: ownerRuntimes }) });
453
+ return { cwd, inputDeclared: false };
454
+ }
455
+ // A script target as it exists: a file runs, a directory with a body is where the body runs
456
+ // ({§exec-target-near-miss}), and a missing one is refused as what it is ({§diagnostic-observation}).
457
+ static async #inspectScriptTarget(core, args) {
458
+ const { runtime, defaultCwd, hasBody } = args;
459
+ let { cwd } = args;
460
+ let target = args.target;
461
+ const inspected = isAbsolute(target) ? target : resolve(cwd ?? process.cwd(), target);
462
+ let kind = "missing";
463
+ try {
464
+ kind = (await stat(inspected)).isDirectory() ? "directory" : "file";
465
+ }
466
+ catch (cause) {
467
+ if (errnoCode(cause) !== "ENOENT") {
468
+ console.error(`Execution target classification failed for '${inspected}':`, cause);
469
+ return Results.failure("scheme:exec", "target-classification-failed", 500, `${runtime} target '${target}' could not be inspected: ${ErrorDetail.preview(cause)}`, {}, { target, stage: "target-classification" });
417
470
  }
418
471
  }
419
- if (!hasBody && target === null && resourceSource === null) {
420
- return refuse("input-required", `Executable tool '${runtime}' has no executable body or realized target.`, `Provide a ${invocation.body.role} body or a non-directory target.`);
472
+ // {§exec-target-near-miss} — a directory is never a program: with a body and no explicit
473
+ // cwd, it is where the body runs. Never taught.
474
+ if (kind === "directory" && hasBody && cwd === defaultCwd) {
475
+ cwd = inspected;
476
+ target = null;
421
477
  }
422
- // One logical target derives one effect fact before policy consumes it.
423
- // A non-file resource keeps its exact authored address until materialization.
424
- const effectTarget = resourceSource ?? target;
425
- const effect = resolved.executor.effect(effectTarget);
426
- // cwd is the workspace project_root unless target routing selected a
427
- // directory override. {§exec-target-routing}, {§executor-sinks}
428
- // The log writer claims the workspace output address before proposal application.
429
- // {§exec-lifetime} — how long the spawn may live is the metadata's one field; the scope slot
430
- // is text coordinates, which an execution has none of.
431
- const attrs = {
432
- runtime, cwd, body, target, pathname: "", effect,
433
- ...(inputDeclared ? { input: "open" } : {}),
434
- ...(resourceSource !== null ? { resourceSource } : {}),
435
- ...lifetime,
436
- };
437
- const previewInput = body !== "" ? body : execTarget?.raw ?? "";
438
- const preview = runtime !== "" ? `[${runtime}] ${previewInput}` : `$ ${previewInput}`;
439
- return { status: 202, body: preview, attrs }; // host runtime proposes with 202 — {§exec-host-proposes}
478
+ else if (kind === "directory") {
479
+ return Exec.#refuse(runtime, "target-not-a-program", `${runtime} target '${target}' is a directory, not a program.`, undefined, { target });
480
+ }
481
+ if (kind === "missing") {
482
+ // The target is not a script here, but it may be a registered tool of another executor or an
483
+ // executor itself: that is what exists, said as found (#388, {§diagnostic-observation}).
484
+ const executors = core.executors;
485
+ const ownerRuntimes = executors === undefined
486
+ ? []
487
+ : executors.availableRuntimes(core.workspaceId)
488
+ .filter((tag) => executors.toolRegistry(tag, core.workspaceId)?.tools.some((tool) => tool.target === target) === true);
489
+ const runtimeTarget = executors !== undefined && target !== null && target !== runtime
490
+ && executors.availableRuntimes(core.workspaceId).includes(target);
491
+ const recovery = ownerRuntimes.length > 0
492
+ ? `The tool \`${target}\` is registered under executor \`${ownerRuntimes[0]}\`.`
493
+ : runtimeTarget
494
+ ? `\`${target}\` is its own executor.`
495
+ : "The target must name an existing program resource.";
496
+ return Exec.#refuse(runtime, "target-not-found", `The ${runtime} program does not resolve as a script or a registered tool for this executor.`, recovery, { target, ...(ownerRuntimes.length === 0 ? {} : { toolRuntimes: ownerRuntimes }) });
497
+ }
498
+ return { target, cwd };
440
499
  }
441
500
  async #inputRecipient(statement, ctx, runtime) {
442
501
  const core = this.coreContext(ctx);
@@ -521,8 +580,11 @@ export default class Exec extends CoreSchemeAdapterBase {
521
580
  if (!EffectPolicy.isEffect(effect)) {
522
581
  throw new InvalidOperationResultError("The accepted execution proposal is missing its canonical effect fact.");
523
582
  }
583
+ if (!isExecutionOp(runtime)) {
584
+ throw new InvalidOperationResultError(`The accepted execution proposal names runtime '${runtime}', which is not a runtime tag.`);
585
+ }
524
586
  const invocation = {
525
- runtime: runtime, aside: null, metadata: null,
587
+ runtime, aside: null, metadata: null,
526
588
  target: attrs.resourceSource ? parsePath(attrs.resourceSource) : target === null ? null : parsePath(target),
527
589
  lineMarker: null, body, position: { line: 1, column: 1 },
528
590
  };
@@ -530,54 +592,112 @@ export default class Exec extends CoreSchemeAdapterBase {
530
592
  // files retain their environment; only standalone sources need a copy.
531
593
  let tempPath = null;
532
594
  if (attrs.resourceSource !== undefined) {
533
- const sourceTarget = parsePath(attrs.resourceSource);
534
- if (sourceTarget?.kind !== "url" || sourceTarget.scheme === null || sourceTarget.scheme === "file") {
535
- throw new InvalidOperationResultError("The accepted execution proposal has an invalid scheme source address.");
536
- }
537
- const source = await this.readExecSource({
538
- op: "READ",
539
- aside: null,
540
- target: sourceTarget,
541
- metadata: null,
542
- lineMarker: { marks: [1, -1] },
543
- matcher: null,
544
- body: null,
545
- position: { line: 0, column: 0 },
546
- }, core);
547
- const read = source.result;
548
- if (read.status >= 400) {
549
- // {§exec-target-routing} — the failure stays the owning READ's (#163); the execution
550
- // contract rides its recovery, because a stream address in the target slot is
551
- // almost always a command that belonged beneath a targetless heading (#425 F4).
552
- const problem = read.problem === undefined ? undefined : {
553
- ...read.problem,
554
- recovery: Exec.sourceRecovery(attrs.resourceSource, read.problem.recovery),
555
- };
556
- return Results.assert({ ...read, outcome: "scheme_source_read_failed", ...(problem === undefined ? {} : { problem }) });
557
- }
558
- const content = read.content;
559
- if (typeof content !== "string") {
560
- return Results.failure("scheme:exec", "source-content-unavailable", 422, `Scheme '${sourceTarget.scheme}' did not supply content for the ${runtime} source.`, { outcome: "scheme_source_content_unavailable" }, {
561
- scheme: sourceTarget.scheme,
562
- sourceStatus: read.status,
563
- retryable: false,
564
- });
595
+ const realized = await this.#realizeSource(attrs.resourceSource, runtime, core);
596
+ if ("status" in realized)
597
+ return realized;
598
+ ({ target, tempPath } = realized);
599
+ }
600
+ if (body.length === 0 && target === null) {
601
+ throw new InvalidOperationResultError("The accepted execution proposal has neither a body nor a realized target.");
602
+ }
603
+ const stream = await Exec.#openStreamEntry(core, runtime, pathname);
604
+ if ("status" in stream)
605
+ return stream;
606
+ const { resolved, entryId } = stream;
607
+ const { subscriptionId, controller, input, lifecycle } = await this.#bindSpawnLifecycle({
608
+ core, attrs, resolved, runtime, body, target, pathname, entryId, effect, invocation,
609
+ inputTimeoutMs: execution.inputTimeoutMs,
610
+ });
611
+ const run = async () => {
612
+ input.started();
613
+ return this.#runExecutor({
614
+ executor: resolved.executor,
615
+ runtime, body, cwd, target, metadata: args.metadata, ctx: core, pathname, coordinate: attrs.coordinate,
616
+ entryId, lifecycle, signal: controller.signal, controller, tempPath, input,
617
+ timeoutSec: typeof attrs.timeoutSec === "number" ? attrs.timeoutSec : null,
618
+ });
619
+ };
620
+ // {§functionality-model-mutation} Configuration is an ordered core operation,
621
+ // not a background job competing for an external-execution slot.
622
+ if (resolved.executor instanceof FunctionalityManager) {
623
+ const tail = run();
624
+ this.#activeSpawns.set(subscriptionId, tail);
625
+ return await tail;
626
+ }
627
+ const admission = execution.scheduler.admit(core.workspaceId, controller.signal);
628
+ const tail = admission.ready.then(async (release) => {
629
+ try {
630
+ return await run();
565
631
  }
566
- if (source.nativePath !== null) {
567
- target = source.nativePath;
632
+ finally {
633
+ release();
568
634
  }
569
- else {
570
- tempPath = this.#sourceScratch().path(extname(sourceTarget.pathname)); // {§exec-scratch-directory}
571
- await writeFile(tempPath, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
572
- target = tempPath;
635
+ });
636
+ // External executions background + stream ({§exec-stream}): the output
637
+ // surfaces as the environment-observation injector's terminal delta (nothing while it runs,
638
+ // one visible READ when it finishes). Pure/read commands still auto-accept
639
+ // from the preserved effect fact; they resolve a turn later, uniformly
640
+ // with host streams.
641
+ this.#activeSpawns.set(subscriptionId, tail);
642
+ void tail.catch((cause) => { console.error(`Execution '${runtime}' failed to settle:`, cause); });
643
+ return admission.queued
644
+ ? {
645
+ status: 202,
646
+ outcome: "queued",
647
+ result: {
648
+ executionsAhead: admission.executionsAhead,
649
+ concurrency: admission.concurrency,
650
+ },
573
651
  }
652
+ : { status: 200, outcome: "started" };
653
+ }
654
+ // {§exec-source-temporary} A standalone source read through its own scheme and realized where the executor can
655
+ // run it: a native file is its own path; anything else is written once under the scratch directory
656
+ // ({§exec-scratch-directory}). A failed read stays the owning READ's failure.
657
+ async #realizeSource(resourceSource, runtime, core) {
658
+ const sourceTarget = parsePath(resourceSource);
659
+ if (sourceTarget?.kind !== "url" || sourceTarget.scheme === null || sourceTarget.scheme === "file") {
660
+ throw new InvalidOperationResultError("The accepted execution proposal has an invalid scheme source address.");
574
661
  }
575
- if (body.length === 0 && target === null) {
576
- throw new InvalidOperationResultError("The accepted execution proposal has neither a body nor a realized target.");
662
+ const source = await this.readExecSource({
663
+ op: "READ",
664
+ aside: null,
665
+ target: sourceTarget,
666
+ metadata: null,
667
+ lineMarker: { marks: [1, -1] },
668
+ matcher: null,
669
+ body: null,
670
+ position: { line: 0, column: 0 },
671
+ }, core);
672
+ const read = source.result;
673
+ if (read.status >= 400) {
674
+ // {§exec-target-routing} — the failure stays the owning READ's (#163); the execution
675
+ // contract rides its recovery, because a stream address in the target slot is
676
+ // almost always a command that belonged beneath a targetless heading (#425 F4).
677
+ const problem = read.problem === undefined ? undefined : {
678
+ ...read.problem,
679
+ recovery: Exec.sourceRecovery(resourceSource, read.problem.recovery),
680
+ };
681
+ return Results.assert({ ...read, outcome: "scheme_source_read_failed", ...(problem === undefined ? {} : { problem }) });
682
+ }
683
+ const { content } = read;
684
+ if (typeof content !== "string") {
685
+ return Results.failure("scheme:exec", "source-content-unavailable", 422, `Scheme '${sourceTarget.scheme}' did not supply content for the ${runtime} source.`, { outcome: "scheme_source_content_unavailable" }, {
686
+ scheme: sourceTarget.scheme,
687
+ sourceStatus: read.status,
688
+ retryable: false,
689
+ });
577
690
  }
578
- // Resolve the runtime's executor from the boot registry, then seed
579
- // channels from its declared topology ({§executor-channels}). Each executor declares its own
580
- // shape (subprocess → stdout/stderr; search → results; etc.).
691
+ if (source.nativePath !== null)
692
+ return { target: source.nativePath, tempPath: null };
693
+ const tempPath = this.#sourceScratch().path(extname(sourceTarget.pathname)); // {§exec-scratch-directory}
694
+ await writeFile(tempPath, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
695
+ return { target: tempPath, tempPath };
696
+ }
697
+ // Resolve the runtime's executor from the boot registry, then seed the stream entry's channels from its
698
+ // declared topology ({§executor-channels}): each executor declares its own shape (subprocess → stdout/stderr;
699
+ // search → results; etc.).
700
+ static async #openStreamEntry(core, runtime, pathname) {
581
701
  if (core.executors === undefined) {
582
702
  throw new InvalidOperationResultError("An accepted execution proposal has no executor registry.");
583
703
  }
@@ -610,6 +730,13 @@ export default class Exec extends CoreSchemeAdapterBase {
610
730
  retryable: false,
611
731
  });
612
732
  }
733
+ return { resolved, entryId };
734
+ }
735
+ // The spawn's live subscription and cancellation scope: the stream subscription opened, the controller bound
736
+ // to the loop's teardown unless detached ({§exec-lifetime}), the input receiver, the abort registry entry, and
737
+ // the LiveSubscription whose release unbinds them all.
738
+ async #bindSpawnLifecycle(args) {
739
+ const { core, attrs, resolved, runtime, body, target, pathname, entryId, effect, invocation, inputTimeoutMs } = args;
613
740
  const subscriptionId = await ChannelWrite.openSubscription(core.db, {
614
741
  workerId: core.workerId, entryId, scheme: runtime,
615
742
  handle: runtime !== "" ? `${runtime}: ${body !== "" ? body : target ?? ""}` : body,
@@ -620,7 +747,7 @@ export default class Exec extends CoreSchemeAdapterBase {
620
747
  : `log:///${attrs.coordinate.loop_seq}/${attrs.coordinate.turn_seq}/${attrs.coordinate.sequence}/${runtime}`,
621
748
  });
622
749
  const controller = new AbortController();
623
- let unlink = () => { };
750
+ let detachParent = () => { };
624
751
  // {§exec-lifetime} — a detached spawn outlives the loop: it never binds to the loop's teardown;
625
752
  // only KILL, the worker's total reap, and daemon shutdown end it.
626
753
  if (core.signal !== undefined && attrs.detached !== true) {
@@ -636,11 +763,11 @@ export default class Exec extends CoreSchemeAdapterBase {
636
763
  // doubled fire is harmless. {§exec-lifetime}
637
764
  const onParentAbort = () => controller.abort(ExecAbort.teardownReason());
638
765
  parent.addEventListener("abort", onParentAbort, { once: true });
639
- unlink = () => parent.removeEventListener("abort", onParentAbort);
766
+ detachParent = () => parent.removeEventListener("abort", onParentAbort);
640
767
  if (parent.aborted)
641
768
  controller.abort(ExecAbort.teardownReason());
642
769
  }
643
- const input = new ExecutionInput(controller.signal, execution.inputTimeoutMs, attrs.input === "open");
770
+ const input = new ExecutionInput(controller.signal, inputTimeoutMs, attrs.input === "open");
644
771
  this.#activeAborts.set(subscriptionId, { workspaceId: core.workspaceId, workerId: core.workerId, turnId: core.turnId, pathname, runtime, effect, controller, detached: attrs.detached === true, input, invocation, executor: resolved.executor });
645
772
  const lifecycle = new LiveSubscription({
646
773
  db: core.db, registry: this.liveSubscriptions(),
@@ -649,55 +776,15 @@ export default class Exec extends CoreSchemeAdapterBase {
649
776
  target: `${runtime}://${pathname}`, subscriptionId, scheme: runtime, ...attrs.coordinate,
650
777
  },
651
778
  handle: { cancel: () => controller.abort(ExecAbort.teardownReason()) },
779
+ reaperLoopId: core.signal !== undefined && attrs.detached !== true ? core.loopId : null,
652
780
  release: () => {
653
- unlink();
781
+ detachParent();
654
782
  this.#activeAborts.delete(subscriptionId);
655
783
  this.#activeSpawns.delete(subscriptionId);
656
784
  },
657
785
  notify: core.streamEventNotify, wake: core.wakeWorkerNotify,
658
786
  });
659
- const run = async () => {
660
- input.started();
661
- return this.#runExecutor({
662
- executor: resolved.executor,
663
- runtime, body, cwd, target, metadata: args.metadata, ctx: core, pathname, coordinate: attrs.coordinate,
664
- entryId, lifecycle, signal: controller.signal, controller, tempPath, input,
665
- timeoutSec: typeof attrs.timeoutSec === "number" ? attrs.timeoutSec : null,
666
- });
667
- };
668
- // {§functionality-model-mutation} Configuration is an ordered core operation,
669
- // not a background job competing for an external-execution slot.
670
- if (resolved.executor instanceof FunctionalityManager) {
671
- const tail = run();
672
- this.#activeSpawns.set(subscriptionId, tail);
673
- return await tail;
674
- }
675
- const admission = execution.scheduler.admit(core.workspaceId, controller.signal);
676
- const tail = admission.ready.then(async (release) => {
677
- try {
678
- return await run();
679
- }
680
- finally {
681
- release();
682
- }
683
- });
684
- // External executions background + stream ({§exec-stream}): the output
685
- // surfaces as the environment-observation injector's terminal delta (nothing while it runs,
686
- // one visible READ when it finishes). Pure/read commands still auto-accept
687
- // from the preserved effect fact; they resolve a turn later, uniformly
688
- // with host streams.
689
- this.#activeSpawns.set(subscriptionId, tail);
690
- void tail.catch((cause) => { console.error(`Execution '${runtime}' failed to settle:`, cause); });
691
- return admission.queued
692
- ? {
693
- status: 202,
694
- outcome: "queued",
695
- result: {
696
- executionsAhead: admission.executionsAhead,
697
- concurrency: admission.concurrency,
698
- },
699
- }
700
- : { status: 200, outcome: "started" };
787
+ return { subscriptionId, controller, input, lifecycle };
701
788
  }
702
789
  // Bridge the executor's sink-style contract (write/setState/emit)
703
790
  // onto plurnk-service's storage primitives (appendToChannel,
@@ -762,190 +849,15 @@ export default class Exec extends CoreSchemeAdapterBase {
762
849
  stage: "execution",
763
850
  retryable: false,
764
851
  });
765
- let exitLabel = "did not conclude";
766
- let stdoutLength = 0;
767
- let stderrLength = 0;
768
- // {§exec-entry-sink} {§env-delta-entry-materialization} — serialize
769
- // executor entry() materializations and narrate them through the one
770
- // reserved-worker turn owned by this spawn.
771
- let entryChain = Promise.resolve();
772
- let narration = null;
773
- let narrationResult = { status: 200 };
774
- let entryChainFailed = false;
775
- let callerSource;
776
- const resolveCallerSource = async () => {
777
- if (callerSource !== undefined)
778
- return callerSource;
779
- const caller = await db.worker_name_by_id.get({ worker_id: ctx.workerId });
780
- if (caller === undefined)
781
- throw new Error(`entry(): calling worker ${ctx.workerId} does not exist`);
782
- callerSource = WorkerControlAddress.render(caller.name);
783
- return callerSource;
784
- };
785
- const publishedNames = new ResourceNames();
786
- const entrySink = (requestedPath, content, opts) => {
787
- let path = requestedPath;
788
- if (path === null) {
789
- if (content === null)
790
- return Promise.reject(new Error("entry(): an invocation resource requires supplied content"));
791
- path = renderAddress({ scheme: runtime, authority: "", pathname: `${pathname}/resources/${publishedNames.allocate(opts.name)}` });
792
- }
793
- const parsed = parsePath(path);
794
- if (parsed === null || parsed.kind !== "url")
795
- return Promise.reject(new Error(`entry(): '${path.slice(0, 80)}' is not a URL`));
796
- if (content !== null && opts.mimetype === undefined)
797
- return Promise.reject(new Error("entry(): mimetype is required when content is provided"));
798
- const address = NetworkAddress.supports(parsed.scheme) ? NetworkAddress.from(parsed) : null;
799
- if (address?.hasCredentials === true)
800
- return Promise.reject(new Error("entry(): network URL userinfo is not allowed"));
801
- const fetchAddress = address !== null && (address.scheme === "http" || address.scheme === "https")
802
- ? address
803
- : null;
804
- const binding = requestedPath === null
805
- ? Promise.resolve({ address: { scheme: runtime, authority: "", pathname: parsed.pathname }, result: null })
806
- : this.bindEntryAddress(parsed, ctx);
807
- // {§exec-entry-sink}/{§web-search-retrieval} — start content:null
808
- // acquisition before the write chain so fetches run in parallel;
809
- // only durable entry writes serialize. A null result rejects the sink.
810
- let materialized;
811
- if (content === null) {
812
- if (fetchAddress === null)
813
- return Promise.reject(new Error("entry(): content:null requires an http(s):// URL"));
814
- materialized = this.#fetchWeb(fetchAddress.url, { signal });
815
- }
816
- else if (typeof content === "string" && requestedPath !== null) {
817
- materialized = Promise.resolve({
818
- url: fetchAddress?.url ?? "http://localhost",
819
- body: content,
820
- mimetype: opts.mimetype,
821
- allowConfiguredMaterializer: false,
822
- });
823
- }
824
- const op = async () => {
825
- const resolved = await binding;
826
- if (resolved === null) {
827
- throw new Error(`entry(): no entry-bearing scheme is registered for '${path.slice(0, 80)}'`);
828
- }
829
- if (resolved.result !== null)
830
- throw new OperationFailureError(resolved.result);
831
- if (resolved.address === null) {
832
- throw new Error(`entry(): '${path.slice(0, 80)}' did not resolve to an entry address`);
833
- }
834
- const { authority, pathname, scheme } = resolved.address;
835
- const coordinate = { authority, pathname };
836
- let channels;
837
- let decisive;
838
- let source;
839
- if (materialized === undefined) {
840
- if (content === null)
841
- throw new Error("entry(): missing supplied content");
842
- const body = typeof content === "string" ? { content, mimetype: opts.mimetype }
843
- : { content: "", bytes: content, mimetype: opts.mimetype };
844
- const channel = requestedPath === null ? executor.defaultChannel : ctx.defaultChannelFor?.(scheme);
845
- if (channel === undefined)
846
- throw new Error(`entry(): no default channel for '${scheme}'`);
847
- channels = { [channel]: body };
848
- decisive = typeof content === "string" ? content : `${opts.mimetype}; ${content.byteLength} bytes`;
849
- source = decisive;
850
- }
851
- else {
852
- const fetched = await materialized;
853
- if (fetched === null)
854
- throw new Error(`entry(): '${path.slice(0, 80)}' is dead`);
855
- let web;
856
- try {
857
- web = await this.#materializer().materialize(fetched, new DbProjectionCaps(ctx));
858
- }
859
- catch (error) {
860
- if (!signal.aborted && error instanceof WebMaterializationError) {
861
- console.error("entry() web materialization failed", { path, error });
862
- }
863
- throw error;
864
- }
865
- channels = this.#materializer().materializedChannels(web, content === null && fetchAddress !== null ? { url: fetchAddress.url, method: "GET" } : undefined);
866
- decisive = web.readable?.content ?? (web.body.bytes === undefined
867
- ? web.body.content : `${web.body.mimetype}; ${web.body.bytes.byteLength} bytes`);
868
- source = web.body.bytes === undefined ? web.body.content : decisive;
869
- }
870
- const causalSource = await resolveCallerSource();
871
- const written = Results.assert(await EntryCrud.writeEntry(coordinate, { channels }, ctx, scheme, {
872
- defaultChannel: requestedPath === null ? executor.defaultChannel : ctx.defaultChannelFor?.(scheme),
873
- output: requestedPath === null,
874
- }));
875
- if (narration === null) {
876
- const workerId = await RuntimeWorker.ensure(db, ctx.workspaceId);
877
- const loop = await AdministrativeLoop.open(db, workerId);
878
- const turn = await Turn.open(db, {
879
- loopId: loop.id,
880
- producer: "_plurnk",
881
- kind: "operation",
882
- });
883
- narration = {
884
- workerId,
885
- loopId: loop.id,
886
- loopSeq: loop.sequence,
887
- turnId: turn.id,
888
- turnSeq: turn.sequence,
889
- seq: 1,
890
- };
891
- }
892
- if (narrationResult.status < 400 || written.status >= 400)
893
- narrationResult = written;
894
- const sequence = narration.seq++;
895
- const narrationAttrs = { kind: "entry_materialized" };
896
- if (written.problem !== undefined) {
897
- const coordinate = LogEntryProjection.coordinate(`${narration.loopSeq}/${narration.turnSeq}/${sequence}`, { origin: "_plurnk", op: "EDIT", attrs: narrationAttrs });
898
- Results.attachInstance(written, `log:///${coordinate}`);
899
- }
900
- const tx = JSON.stringify({ op: "EDIT", body: source });
901
- const rx = JSON.stringify(written.problem === undefined
902
- ? {
903
- ...written,
904
- span: decisive.split("\n").map((l, n) => `${n + 1}:${l}`).join("\n"),
905
- }
906
- : written);
907
- const attrs = JSON.stringify(narrationAttrs);
908
- if (ctx.weigh === undefined) {
909
- throw new Error("entry(): ctx.weigh is required for curation-weight accounting");
910
- }
911
- const logRow = await db.engine_insert_log_entry.get({
912
- worker_id: narration.workerId, loop_id: narration.loopId, turn_id: narration.turnId, sequence,
913
- origin: "_plurnk", source: causalSource, model_call_id: null,
914
- op: "EDIT", signal: null,
915
- scheme, username: null, password: null,
916
- hostname: address === null ? null : parsed.hostname,
917
- port: address === null ? null : parsed.port,
918
- pathname: address === null ? pathname : PathSyntax.decodeParens(parsed.pathname),
919
- query: address === null ? null : parsed.query,
920
- fragment: null, lineMarker: null,
921
- tx, mimetype_tx: "application/json",
922
- rx,
923
- mimetype_rx: "application/json",
924
- status_rx: written.status,
925
- weight: LogBody.weight({
926
- op: "EDIT",
927
- attrs,
928
- tx,
929
- rx,
930
- mimetypeTx: "application/json",
931
- mimetypeRx: "application/json",
932
- }, ctx.weigh),
933
- state: "resolved", outcome: null,
934
- // Durable provenance for clients/forensics. This is machine
935
- // ambience, not a human/model action waterfall item.
936
- attrs,
937
- initial_folded: "[]",
938
- });
939
- if (logRow === undefined)
940
- throw new Error("entry(): log insert returned no row");
941
- if (written.problem !== undefined)
942
- throw new OperationFailureError(written);
943
- return renderAddress({ scheme, authority, pathname });
944
- };
945
- const run = entryChain.then(op, op);
946
- entryChain = run.then(() => undefined, () => { entryChainFailed = true; });
947
- return run;
948
- };
852
+ let channelSizes = [];
853
+ // {§exec-entry-sink} — the executor's entry() sink and the narration turn it owns live in their own owner;
854
+ // this spawn's tail settles them.
855
+ const sink = new ExecEntrySink({
856
+ ctx, runtime, pathname, signal, executor,
857
+ bindEntryAddress: (parsed, context) => this.bindEntryAddress(parsed, context),
858
+ fetchWeb: this.#fetchWeb,
859
+ materializer: this.#materializer,
860
+ });
949
861
  const executionFailures = [];
950
862
  const cancelled = (exitCode) => Results.failure("scheme:exec", "execution-cancelled", 499, `Execution of '${runtime}' was cancelled by the service.`, exitCode === undefined ? {} : { exitCode }, { runtime, stage: "execution", retryable: false });
951
863
  try {
@@ -963,7 +875,7 @@ export default class Exec extends CoreSchemeAdapterBase {
963
875
  const reported = await executor.run({
964
876
  registerInput: (receiver) => input.register(receiver),
965
877
  runtime, body, cwd, target, metadata, signal,
966
- entry: entrySink,
878
+ entry: sink.entry,
967
879
  // {§executor-interaction-sink} — the executor's signal narrows the execution's, never replaces it.
968
880
  interact: (request, interactionSignal) => {
969
881
  if (ctx.requestInteraction === undefined) {
@@ -1014,67 +926,19 @@ export default class Exec extends CoreSchemeAdapterBase {
1014
926
  });
1015
927
  }
1016
928
  }
1017
- const exitCode = typeof result.exitCode === "number" ? result.exitCode : null;
1018
- // A timeout aborts the spawn → the executor reports 499; replace the
1019
- // complete result so status and Problem remain one valid truth.
1020
- if (timedOut) {
1021
- const lifetime = formatLifetime(timeoutSec ?? 0);
1022
- result = Results.failure("scheme:exec", "execution-timeout", 504, `Execution of '${runtime}' outlived its ${lifetime} lifetime.`, exitCode === null ? {} : { exitCode }, {
1023
- runtime,
1024
- lifetime,
1025
- stage: "execution",
1026
- retryable: false,
1027
- });
1028
- // The service's own abort knowledge outranks a driver's claim: a
1029
- // spawn we reaped did not succeed, whatever it resolved under abort.
1030
- }
1031
- else if (signal.aborted && result.status < 400) {
1032
- result = cancelled(exitCode ?? undefined);
1033
- }
1034
- else if (result.status < 400 && exitCode === 127 && target === null) {
1035
- result = this.#namedToolFallThrough(result, runtime, body, ctx);
1036
- }
1037
- exitLabel = timedOut
1038
- ? `timed out after ${(timeoutSec ?? 0) / 60}m`
1039
- : result.status === 499
1040
- ? "aborted"
1041
- : exitCode !== null
1042
- ? `exit ${exitCode}`
1043
- : result.problem?.title.toLowerCase() ?? "completed";
1044
- const stdoutMeta = await db.channel_meta.get({ entry_id: entryId, channel: "stdout" });
1045
- const stderrMeta = await db.channel_meta.get({ entry_id: entryId, channel: "stderr" });
1046
- stdoutLength = stdoutMeta?.contentLength ?? 0;
1047
- stderrLength = stderrMeta?.contentLength ?? 0;
929
+ result = this.#settleExecutionResult({ result, timedOut, timeoutSec, signal, target, runtime, body, ctx });
930
+ // {§exec-conclusion-summary} — the summary sizes the channels this executor declares, in its order.
931
+ channelSizes = await Promise.all(Object.keys(executor.channels).map(async (channel) => [
932
+ channel,
933
+ (await db.channel_meta.get({ entry_id: entryId, channel }))?.contentLength ?? 0,
934
+ ]));
1048
935
  }
1049
936
  catch (cause) {
1050
937
  executionFailures.push(cause);
1051
938
  }
1052
939
  {
1053
- // {§exec-entry-sink} — the spawn tail owns every serialized entry/narration write.
1054
- const finalizationErrors = [...executionFailures];
1055
- if (entryChainFailed && narrationResult.status < 400) {
1056
- narrationResult = Results.failure("scheme:exec", "entry-narration-failed", 500, "An executor entry could not be materialized or narrated.", {}, { stage: "entry-materialization", retryable: false });
1057
- }
1058
- try {
1059
- await entryChain;
1060
- }
1061
- catch (cause) {
1062
- narrationResult = Results.failure("scheme:exec", "entry-narration-failed", 500, "An executor entry could not be materialized or narrated.", {}, { stage: "entry-materialization", retryable: false });
1063
- finalizationErrors.push(cause);
1064
- }
1065
- const completedNarration = narration;
1066
- if (completedNarration !== null) {
1067
- try {
1068
- await Turn.complete(db, completedNarration.turnId, narrationResult.status);
1069
- const closed = await new LoopLifecycle(db).finish(completedNarration.loopId, narrationResult);
1070
- if (closed === null) {
1071
- throw new Error(`entry(): narration loop ${completedNarration.loopId} was not open at completion`);
1072
- }
1073
- }
1074
- catch (cause) {
1075
- finalizationErrors.push(cause);
1076
- }
1077
- }
940
+ // {§exec-entry-sink} — the spawn tail settles the entry chain and its narration turn before anything else.
941
+ const finalizationErrors = [...executionFailures, ...await sink.settle()];
1078
942
  if (timeoutTimer !== null)
1079
943
  clearTimeout(timeoutTimer); // a finished spawn leaves no pending timer
1080
944
  // {§exec-source-temporary} — cleanup cannot rewrite the settled
@@ -1085,7 +949,7 @@ export default class Exec extends CoreSchemeAdapterBase {
1085
949
  });
1086
950
  }
1087
951
  try {
1088
- await lifecycle.close(result, `${runtime}://${pathname} completed (${exitLabel}); stdout=${stdoutLength} bytes, stderr=${stderrLength} bytes`);
952
+ await lifecycle.close(result, Exec.#closeSummary(`${runtime}://${pathname}`, result, channelSizes));
1089
953
  }
1090
954
  catch (cause) {
1091
955
  finalizationErrors.push(cause);
@@ -1093,11 +957,47 @@ export default class Exec extends CoreSchemeAdapterBase {
1093
957
  if (finalizationErrors.length === 1)
1094
958
  throw finalizationErrors[0];
1095
959
  if (finalizationErrors.length > 1) {
1096
- throw new AggregateError(finalizationErrors, `Execution narration ${completedNarration?.turnId ?? "unknown"} failed to settle`);
960
+ throw new AggregateError(finalizationErrors, `Execution narration ${sink.narrationTurnId ?? "unknown"} failed to settle`);
1097
961
  }
1098
962
  }
1099
963
  return result;
1100
964
  }
965
+ // One valid truth for the settled execution: a timeout outranks the executor's report, the service's own abort
966
+ // knowledge outranks a driver's claim, and a shell's 127 on a tool's name is that tool's refusal
967
+ // ({§exec-tool-fall-through}).
968
+ #settleExecutionResult(args) {
969
+ const { timedOut, timeoutSec, signal, target, runtime, body, ctx } = args;
970
+ let { result } = args;
971
+ const cancelled = (exitCode) => Results.failure("scheme:exec", "execution-cancelled", 499, `Execution of '${runtime}' was cancelled by the service.`, exitCode === undefined ? {} : { exitCode }, { runtime, stage: "execution", retryable: false });
972
+ const exitCode = typeof result.exitCode === "number" ? result.exitCode : null;
973
+ // A timeout aborts the spawn → the executor reports 499; replace the
974
+ // complete result so status and Problem remain one valid truth.
975
+ if (timedOut) {
976
+ const lifetime = formatLifetime(timeoutSec ?? 0);
977
+ result = Results.failure("scheme:exec", "execution-timeout", 504, `Execution of '${runtime}' outlived its ${lifetime} lifetime.`, exitCode === null ? {} : { exitCode }, {
978
+ runtime,
979
+ lifetime,
980
+ stage: "execution",
981
+ retryable: false,
982
+ });
983
+ // The service's own abort knowledge outranks a driver's claim: a
984
+ // spawn we reaped did not succeed, whatever it resolved under abort.
985
+ }
986
+ else if (signal.aborted && result.status < 400) {
987
+ result = cancelled(exitCode ?? undefined);
988
+ }
989
+ else if (result.status < 400 && exitCode === 127 && target === null) {
990
+ result = this.#namedToolFallThrough(result, runtime, body, ctx);
991
+ }
992
+ return result;
993
+ }
994
+ // {§exec-conclusion-summary} — the stream's address, how its settled result ended, and each declared channel's size.
995
+ static #closeSummary(address, result, channels) {
996
+ const ended = Results.isFailed(result) ? result.problem.title.toLowerCase()
997
+ : typeof result.exitCode === "number" ? `completed (exit ${result.exitCode})` : "completed";
998
+ const sizes = channels.map(([channel, size]) => `${channel}=${size} bytes`).join(", ");
999
+ return `${address} ${ended}${sizes === "" ? "" : `; ${sizes}`}`;
1000
+ }
1101
1001
  async find(statement, ctx) {
1102
1002
  const core = this.coreContext(ctx);
1103
1003
  return EntryFind.findWorkspaceEntries(statement, core, Exec.manifest, {});