@llblab/pi-kit 0.38.3 → 0.39.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 (430) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +108 -20
  4. package/node_modules/@llblab/pi-actors/BACKLOG.md +207 -4
  5. package/node_modules/@llblab/pi-actors/CHANGELOG.md +13 -0
  6. package/node_modules/@llblab/pi-actors/README.md +31 -1
  7. package/node_modules/@llblab/pi-actors/dist/index.js +1 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +37 -0
  9. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +131 -62
  10. package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.d.ts +2 -0
  11. package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.js +3 -2
  12. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.d.ts +37 -0
  13. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +263 -129
  14. package/node_modules/@llblab/pi-actors/dist/lib/execution-files.d.ts +32 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/execution-files.js +128 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/execution-output.js +5 -4
  17. package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.d.ts +19 -1
  18. package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.js +62 -26
  19. package/node_modules/@llblab/pi-actors/dist/lib/execution.d.ts +4 -1
  20. package/node_modules/@llblab/pi-actors/dist/lib/execution.js +6 -1
  21. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +9 -2
  22. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +92 -14
  23. package/node_modules/@llblab/pi-actors/dist/lib/file-state.js +21 -2
  24. package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.js +2 -2
  25. package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +25 -7
  26. package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +562 -262
  27. package/node_modules/@llblab/pi-actors/dist/lib/inspector.d.ts +20 -10
  28. package/node_modules/@llblab/pi-actors/dist/lib/inspector.js +80 -91
  29. package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +1 -0
  30. package/node_modules/@llblab/pi-actors/dist/lib/limits.js +1 -0
  31. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +15 -1
  32. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +216 -99
  33. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +5 -4
  34. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +64 -10
  35. package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.d.ts +18 -0
  36. package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.js +220 -0
  37. package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.d.ts +2 -0
  38. package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.js +30 -6
  39. package/node_modules/@llblab/pi-actors/dist/lib/registry.js +270 -36
  40. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +213 -5
  41. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +1909 -40
  42. package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.d.ts +107 -0
  43. package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.js +412 -0
  44. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +9 -2
  45. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +283 -60
  46. package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.d.ts +26 -1
  47. package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +68 -2
  48. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.d.ts +451 -0
  49. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.js +2315 -0
  50. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.d.ts +97 -0
  51. package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.js +368 -0
  52. package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.js +46 -7
  53. package/node_modules/@llblab/pi-actors/dist/lib/runs-control.d.ts +78 -2
  54. package/node_modules/@llblab/pi-actors/dist/lib/runs-control.js +334 -2
  55. package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.js +4 -1
  56. package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.d.ts +80 -0
  57. package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.js +287 -0
  58. package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.d.ts +110 -0
  59. package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.js +282 -0
  60. package/node_modules/@llblab/pi-actors/dist/lib/runs-index.d.ts +66 -1
  61. package/node_modules/@llblab/pi-actors/dist/lib/runs-index.js +349 -13
  62. package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.d.ts +73 -0
  63. package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.js +227 -0
  64. package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.d.ts +141 -0
  65. package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.js +292 -0
  66. package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.d.ts +10 -1
  67. package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.js +36 -4
  68. package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.d.ts +46 -0
  69. package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.js +114 -0
  70. package/node_modules/@llblab/pi-actors/dist/lib/runs-process.d.ts +34 -1
  71. package/node_modules/@llblab/pi-actors/dist/lib/runs-process.js +158 -26
  72. package/node_modules/@llblab/pi-actors/dist/lib/runs-results.d.ts +116 -0
  73. package/node_modules/@llblab/pi-actors/dist/lib/runs-results.js +518 -0
  74. package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.d.ts +15 -2
  75. package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.js +148 -9
  76. package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.d.ts +95 -0
  77. package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.js +470 -0
  78. package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.d.ts +58 -0
  79. package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.js +92 -0
  80. package/node_modules/@llblab/pi-actors/dist/lib/runs-start.d.ts +2 -0
  81. package/node_modules/@llblab/pi-actors/dist/lib/runs-start.js +44 -3
  82. package/node_modules/@llblab/pi-actors/dist/lib/runs-status.d.ts +15 -1
  83. package/node_modules/@llblab/pi-actors/dist/lib/runs-status.js +337 -13
  84. package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.d.ts +105 -0
  85. package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.js +501 -0
  86. package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +1 -0
  87. package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +14 -3
  88. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +22 -3
  89. package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +37 -0
  90. package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +59 -3
  91. package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +4 -1
  92. package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +14 -8
  93. package/node_modules/@llblab/pi-actors/dist/lib/temp.d.ts +2 -4
  94. package/node_modules/@llblab/pi-actors/dist/lib/temp.js +4 -56
  95. package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +3 -0
  96. package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +13 -0
  97. package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.d.ts +1 -0
  98. package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +110 -43
  99. package/node_modules/@llblab/pi-actors/dist/lib/tools-local.d.ts +3 -0
  100. package/node_modules/@llblab/pi-actors/dist/lib/tools-local.js +18 -6
  101. package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +4 -1
  102. package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +126 -31
  103. package/node_modules/@llblab/pi-actors/dist/lib/tools-response.d.ts +31 -3
  104. package/node_modules/@llblab/pi-actors/dist/lib/tools-response.js +311 -47
  105. package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.d.ts +3 -0
  106. package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.js +29 -7
  107. package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +20 -0
  108. package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +192 -43
  109. package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +272 -194
  110. package/node_modules/@llblab/pi-actors/dist/scripts/conformance.mjs +14 -2
  111. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +46 -2
  112. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/diagnostics.md +40 -0
  113. package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +55 -1
  114. package/node_modules/@llblab/pi-actors/docs/README.md +9 -1
  115. package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +84 -15
  116. package/node_modules/@llblab/pi-actors/docs/agent-experience.md +216 -0
  117. package/node_modules/@llblab/pi-actors/docs/async-runs.md +18 -2
  118. package/node_modules/@llblab/pi-actors/docs/composition-transport.md +103 -0
  119. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +204 -4
  120. package/node_modules/@llblab/pi-actors/docs/inspection.md +38 -2
  121. package/node_modules/@llblab/pi-actors/docs/output-projection-map.md +130 -0
  122. package/node_modules/@llblab/pi-actors/docs/releasing.md +19 -2
  123. package/node_modules/@llblab/pi-actors/docs/run-supervision.md +1622 -0
  124. package/node_modules/@llblab/pi-actors/docs/template-recipes.md +4 -0
  125. package/node_modules/@llblab/pi-actors/docs/windows-process-observation.md +248 -0
  126. package/node_modules/@llblab/pi-actors/index.ts +1 -1
  127. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +164 -73
  128. package/node_modules/@llblab/pi-actors/lib/automatic-review-runtime.ts +4 -2
  129. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +287 -138
  130. package/node_modules/@llblab/pi-actors/lib/execution-files.ts +118 -0
  131. package/node_modules/@llblab/pi-actors/lib/execution-output.ts +4 -4
  132. package/node_modules/@llblab/pi-actors/lib/execution-sessions.ts +57 -27
  133. package/node_modules/@llblab/pi-actors/lib/execution.ts +8 -0
  134. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +102 -12
  135. package/node_modules/@llblab/pi-actors/lib/file-state.ts +16 -2
  136. package/node_modules/@llblab/pi-actors/lib/inspector-command.ts +2 -2
  137. package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +524 -261
  138. package/node_modules/@llblab/pi-actors/lib/inspector.ts +101 -130
  139. package/node_modules/@llblab/pi-actors/lib/limits.ts +1 -0
  140. package/node_modules/@llblab/pi-actors/lib/observability.ts +235 -116
  141. package/node_modules/@llblab/pi-actors/lib/pi.ts +59 -11
  142. package/node_modules/@llblab/pi-actors/lib/recipe-projection.ts +232 -0
  143. package/node_modules/@llblab/pi-actors/lib/recipes-context.ts +27 -6
  144. package/node_modules/@llblab/pi-actors/lib/registry.ts +266 -36
  145. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +1737 -49
  146. package/node_modules/@llblab/pi-actors/lib/run-supervision.ts +411 -0
  147. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +237 -73
  148. package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +73 -1
  149. package/node_modules/@llblab/pi-actors/lib/runs-context-exports.ts +1871 -0
  150. package/node_modules/@llblab/pi-actors/lib/runs-context-index.ts +372 -0
  151. package/node_modules/@llblab/pi-actors/lib/runs-control-delivery.ts +36 -18
  152. package/node_modules/@llblab/pi-actors/lib/runs-control.ts +297 -1
  153. package/node_modules/@llblab/pi-actors/lib/runs-controls.ts +3 -1
  154. package/node_modules/@llblab/pi-actors/lib/runs-descendant-launches.ts +338 -0
  155. package/node_modules/@llblab/pi-actors/lib/runs-execution-evidence.ts +302 -0
  156. package/node_modules/@llblab/pi-actors/lib/runs-index.ts +345 -17
  157. package/node_modules/@llblab/pi-actors/lib/runs-native-launches.ts +270 -0
  158. package/node_modules/@llblab/pi-actors/lib/runs-outcomes.ts +377 -0
  159. package/node_modules/@llblab/pi-actors/lib/runs-ownership.ts +37 -4
  160. package/node_modules/@llblab/pi-actors/lib/runs-process-windows-mi.ts +113 -0
  161. package/node_modules/@llblab/pi-actors/lib/runs-process.ts +210 -34
  162. package/node_modules/@llblab/pi-actors/lib/runs-results.ts +601 -0
  163. package/node_modules/@llblab/pi-actors/lib/runs-retention.ts +134 -10
  164. package/node_modules/@llblab/pi-actors/lib/runs-session-exports.ts +406 -0
  165. package/node_modules/@llblab/pi-actors/lib/runs-snapshot.ts +143 -0
  166. package/node_modules/@llblab/pi-actors/lib/runs-start.ts +44 -2
  167. package/node_modules/@llblab/pi-actors/lib/runs-status.ts +252 -10
  168. package/node_modules/@llblab/pi-actors/lib/runs-supervision.ts +394 -0
  169. package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +14 -3
  170. package/node_modules/@llblab/pi-actors/lib/runtime.ts +22 -3
  171. package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +76 -4
  172. package/node_modules/@llblab/pi-actors/lib/state-readers.ts +16 -7
  173. package/node_modules/@llblab/pi-actors/lib/temp.ts +3 -56
  174. package/node_modules/@llblab/pi-actors/lib/tools-access.ts +13 -0
  175. package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +110 -51
  176. package/node_modules/@llblab/pi-actors/lib/tools-local.ts +37 -24
  177. package/node_modules/@llblab/pi-actors/lib/tools-message.ts +120 -37
  178. package/node_modules/@llblab/pi-actors/lib/tools-response.ts +273 -50
  179. package/node_modules/@llblab/pi-actors/lib/tools-spawn.ts +31 -10
  180. package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +165 -42
  181. package/node_modules/@llblab/pi-actors/package.json +10 -6
  182. package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +272 -194
  183. package/node_modules/@llblab/pi-actors/scripts/conformance.mjs +14 -2
  184. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +46 -2
  185. package/node_modules/@llblab/pi-actors/skills/actors/references/diagnostics.md +40 -0
  186. package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +55 -1
  187. package/node_modules/@llblab/pi-codex-usage/AGENTS.md +2 -2
  188. package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +6 -0
  189. package/node_modules/@llblab/pi-codex-usage/README.md +9 -5
  190. package/node_modules/@llblab/pi-codex-usage/lib/query.ts +229 -50
  191. package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +9 -1
  192. package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
  193. package/node_modules/yaml/LICENSE +13 -0
  194. package/node_modules/yaml/README.md +172 -0
  195. package/node_modules/yaml/bin.mjs +11 -0
  196. package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
  197. package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
  198. package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
  199. package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
  200. package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
  201. package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
  202. package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
  203. package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
  204. package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
  205. package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
  206. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
  207. package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
  208. package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
  209. package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
  210. package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
  211. package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
  212. package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
  213. package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
  214. package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
  215. package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
  216. package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
  217. package/node_modules/yaml/browser/dist/errors.js +57 -0
  218. package/node_modules/yaml/browser/dist/index.js +17 -0
  219. package/node_modules/yaml/browser/dist/log.js +11 -0
  220. package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
  221. package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
  222. package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
  223. package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
  224. package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
  225. package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
  226. package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
  227. package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
  228. package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
  229. package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
  230. package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
  231. package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
  232. package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
  233. package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
  234. package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
  235. package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
  236. package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
  237. package/node_modules/yaml/browser/dist/public-api.js +102 -0
  238. package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
  239. package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
  240. package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
  241. package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
  242. package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
  243. package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
  244. package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
  245. package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
  246. package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
  247. package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
  248. package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
  249. package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
  250. package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
  251. package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
  252. package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
  253. package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
  254. package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
  255. package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
  256. package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
  257. package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
  258. package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
  259. package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
  260. package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
  261. package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
  262. package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
  263. package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
  264. package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
  265. package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
  266. package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
  267. package/node_modules/yaml/browser/dist/util.js +11 -0
  268. package/node_modules/yaml/browser/dist/visit.js +233 -0
  269. package/node_modules/yaml/browser/index.js +5 -0
  270. package/node_modules/yaml/browser/package.json +3 -0
  271. package/node_modules/yaml/dist/cli.d.ts +8 -0
  272. package/node_modules/yaml/dist/cli.mjs +201 -0
  273. package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
  274. package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  275. package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
  276. package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  277. package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
  278. package/node_modules/yaml/dist/compose/compose-node.js +112 -0
  279. package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
  280. package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  281. package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
  282. package/node_modules/yaml/dist/compose/composer.js +224 -0
  283. package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
  284. package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  285. package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
  286. package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  287. package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
  288. package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  289. package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
  290. package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  291. package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
  292. package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  293. package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
  294. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
  295. package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
  296. package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  297. package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
  298. package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  299. package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
  300. package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  301. package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
  302. package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  303. package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
  304. package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  305. package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
  306. package/node_modules/yaml/dist/doc/Document.js +337 -0
  307. package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
  308. package/node_modules/yaml/dist/doc/anchors.js +76 -0
  309. package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
  310. package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  311. package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
  312. package/node_modules/yaml/dist/doc/createNode.js +90 -0
  313. package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
  314. package/node_modules/yaml/dist/doc/directives.js +178 -0
  315. package/node_modules/yaml/dist/errors.d.ts +21 -0
  316. package/node_modules/yaml/dist/errors.js +62 -0
  317. package/node_modules/yaml/dist/index.d.ts +25 -0
  318. package/node_modules/yaml/dist/index.js +50 -0
  319. package/node_modules/yaml/dist/log.d.ts +3 -0
  320. package/node_modules/yaml/dist/log.js +19 -0
  321. package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
  322. package/node_modules/yaml/dist/nodes/Alias.js +118 -0
  323. package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
  324. package/node_modules/yaml/dist/nodes/Collection.js +151 -0
  325. package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
  326. package/node_modules/yaml/dist/nodes/Node.js +40 -0
  327. package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
  328. package/node_modules/yaml/dist/nodes/Pair.js +39 -0
  329. package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
  330. package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  331. package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
  332. package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  333. package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
  334. package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  335. package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
  336. package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  337. package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
  338. package/node_modules/yaml/dist/nodes/identity.js +53 -0
  339. package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
  340. package/node_modules/yaml/dist/nodes/toJS.js +39 -0
  341. package/node_modules/yaml/dist/options.d.ts +350 -0
  342. package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
  343. package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  344. package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
  345. package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  346. package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
  347. package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  348. package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
  349. package/node_modules/yaml/dist/parse/cst.js +112 -0
  350. package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
  351. package/node_modules/yaml/dist/parse/lexer.js +723 -0
  352. package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
  353. package/node_modules/yaml/dist/parse/line-counter.js +41 -0
  354. package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
  355. package/node_modules/yaml/dist/parse/parser.js +980 -0
  356. package/node_modules/yaml/dist/public-api.d.ts +44 -0
  357. package/node_modules/yaml/dist/public-api.js +107 -0
  358. package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
  359. package/node_modules/yaml/dist/schema/Schema.js +39 -0
  360. package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
  361. package/node_modules/yaml/dist/schema/common/map.js +19 -0
  362. package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
  363. package/node_modules/yaml/dist/schema/common/null.js +17 -0
  364. package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
  365. package/node_modules/yaml/dist/schema/common/seq.js +19 -0
  366. package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
  367. package/node_modules/yaml/dist/schema/common/string.js +16 -0
  368. package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
  369. package/node_modules/yaml/dist/schema/core/bool.js +21 -0
  370. package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
  371. package/node_modules/yaml/dist/schema/core/float.js +47 -0
  372. package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
  373. package/node_modules/yaml/dist/schema/core/int.js +42 -0
  374. package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
  375. package/node_modules/yaml/dist/schema/core/schema.js +25 -0
  376. package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
  377. package/node_modules/yaml/dist/schema/json/schema.js +64 -0
  378. package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
  379. package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
  380. package/node_modules/yaml/dist/schema/tags.js +99 -0
  381. package/node_modules/yaml/dist/schema/types.d.ts +92 -0
  382. package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
  383. package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  384. package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
  385. package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  386. package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
  387. package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  388. package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
  389. package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  390. package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
  391. package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
  392. package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
  393. package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  394. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
  395. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  396. package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
  397. package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  398. package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
  399. package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  400. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
  401. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  402. package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
  403. package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  404. package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
  405. package/node_modules/yaml/dist/stringify/stringify.js +132 -0
  406. package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
  407. package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  408. package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
  409. package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  410. package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
  411. package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  412. package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
  413. package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
  414. package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
  415. package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  416. package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
  417. package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  418. package/node_modules/yaml/dist/test-events.d.ts +4 -0
  419. package/node_modules/yaml/dist/test-events.js +134 -0
  420. package/node_modules/yaml/dist/util.d.ts +16 -0
  421. package/node_modules/yaml/dist/util.js +28 -0
  422. package/node_modules/yaml/dist/visit.d.ts +102 -0
  423. package/node_modules/yaml/dist/visit.js +236 -0
  424. package/node_modules/yaml/package.json +97 -0
  425. package/node_modules/yaml/util.js +2 -0
  426. package/package.json +3 -3
  427. package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
  428. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +0 -16
  429. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +0 -57
  430. package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +0 -79
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Native launch evidence persistence.
3
+ * Owns: generation-bound intent/birth/settlement records and unresolved-scope mutation fences.
4
+ * Excludes: runtime-local admission/acquisition, complete membership, termination, and restart execution.
5
+ */
6
+
7
+ import { randomUUID } from "node:crypto";
8
+ import { lstatSync } from "node:fs";
9
+ import { join } from "node:path";
10
+
11
+ import { writeJsonAtomic } from "./file-state.ts";
12
+ import { assertOwnedRunStateDirectory } from "./runs-ownership.ts";
13
+ import { runProcessIdentityDigest, type RunProcessIdentity } from "./runs-process.ts";
14
+ import { readJsonFileResilient } from "./state-readers.ts";
15
+
16
+ export const RUN_NATIVE_LAUNCH_FILE = "native-launches.json";
17
+ const SCHEMA = "run-native-launches-v1";
18
+
19
+ export interface RunNativeLaunchOwner {
20
+ readonly run: string;
21
+ readonly runInstanceId: string;
22
+ readonly ownerId?: string;
23
+ readonly pid: number;
24
+ readonly processIdentityDigest?: string;
25
+ }
26
+
27
+ export interface RunNativeLaunchRequest {
28
+ readonly commandId: string;
29
+ readonly attempt: number;
30
+ }
31
+
32
+ export interface RunNativeLaunchBirth {
33
+ readonly pid: number;
34
+ readonly observed_at: string;
35
+ readonly process_identity: Readonly<RunProcessIdentity>;
36
+ }
37
+
38
+ export interface RunNativeLaunchCloseSettlement {
39
+ readonly basis?: never;
40
+ readonly observed_at: string;
41
+ readonly native_exit_code: number | null;
42
+ readonly native_signal: NodeJS.Signals | null;
43
+ }
44
+
45
+ export interface RunNativeLaunchAbsenceSettlement {
46
+ readonly basis: "verified_absence";
47
+ readonly observed_at: string;
48
+ }
49
+
50
+ export type RunNativeLaunchSettlement = RunNativeLaunchCloseSettlement | RunNativeLaunchAbsenceSettlement;
51
+
52
+ export interface RunNativeLaunchIntent {
53
+ readonly intent_id: string;
54
+ readonly command_id: string;
55
+ readonly attempt: number;
56
+ readonly admitted_at: string;
57
+ readonly birth?: RunNativeLaunchBirth;
58
+ readonly settlement?: RunNativeLaunchSettlement;
59
+ }
60
+
61
+ export interface RunNativeLaunchLedger {
62
+ readonly schema: typeof SCHEMA;
63
+ readonly run: string;
64
+ readonly run_instance_id: string;
65
+ readonly ownerId?: string;
66
+ readonly runner_pid: number;
67
+ readonly runner_identity_digest: string | null;
68
+ readonly intents: readonly RunNativeLaunchIntent[];
69
+ }
70
+
71
+ export type RunNativeLaunchReading =
72
+ | { state: "absent" | "invalid" }
73
+ | { state: "available"; ledger: RunNativeLaunchLedger };
74
+
75
+ function record(value: unknown): value is Record<string, unknown> {
76
+ return value !== null && typeof value === "object" && !Array.isArray(value);
77
+ }
78
+
79
+ function onlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
80
+ return Object.keys(value).every((key) => keys.includes(key));
81
+ }
82
+
83
+ function text(value: unknown): value is string {
84
+ return typeof value === "string" && value.length > 0;
85
+ }
86
+
87
+ function intentId(value: unknown): value is string {
88
+ return typeof value === "string" &&
89
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.exec(value)?.[0] === value;
90
+ }
91
+
92
+ function canonicalTimestamp(value: unknown): value is string {
93
+ if (!text(value)) return false;
94
+ try { return new Date(value).toISOString() === value; } catch { return false; }
95
+ }
96
+
97
+ function validRequest(value: unknown): value is RunNativeLaunchRequest {
98
+ if (!record(value)) return false;
99
+ return typeof value.commandId === "string" &&
100
+ /^command-[0-9]{3,}$/.exec(value.commandId)?.[0] === value.commandId &&
101
+ Number.isSafeInteger(value.attempt) && Number(value.attempt) > 0;
102
+ }
103
+
104
+ function validOwner(owner: RunNativeLaunchOwner): boolean {
105
+ return !!owner && text(owner.run) && text(owner.runInstanceId) &&
106
+ Number.isSafeInteger(owner.pid) && owner.pid > 0 &&
107
+ (owner.ownerId === undefined || text(owner.ownerId)) &&
108
+ (owner.processIdentityDigest === undefined ||
109
+ (typeof owner.processIdentityDigest === "string" && /^[a-f0-9]{64}$/.exec(owner.processIdentityDigest)?.[0] === owner.processIdentityDigest));
110
+ }
111
+
112
+ function decodeBirth(value: unknown, runnerPid: number): RunNativeLaunchBirth | undefined {
113
+ if (!record(value) || !onlyKeys(value, ["pid", "observed_at", "process_identity"]) ||
114
+ !Number.isSafeInteger(value.pid) || Number(value.pid) <= 0 || value.pid === runnerPid ||
115
+ !canonicalTimestamp(value.observed_at) || !record(value.process_identity)) return undefined;
116
+ const identity = value.process_identity;
117
+ if (!onlyKeys(identity, ["platform", "start_time", "command", "cwd"]) || !runProcessIdentityDigest(identity) ||
118
+ (identity.cwd !== undefined && !text(identity.cwd))) return undefined;
119
+ return Object.freeze({
120
+ pid: Number(value.pid), observed_at: value.observed_at,
121
+ process_identity: Object.freeze({
122
+ platform: identity.platform as NodeJS.Platform, start_time: String(identity.start_time), command: String(identity.command),
123
+ ...(identity.cwd === undefined ? {} : { cwd: String(identity.cwd) }),
124
+ }),
125
+ });
126
+ }
127
+
128
+ function decodeSettlement(value: unknown): RunNativeLaunchSettlement | undefined {
129
+ if (!record(value) || !canonicalTimestamp(value.observed_at)) return undefined;
130
+ if (value.basis === "verified_absence") {
131
+ return onlyKeys(value, ["basis", "observed_at"])
132
+ ? Object.freeze({ basis: "verified_absence", observed_at: value.observed_at }) : undefined;
133
+ }
134
+ if (!onlyKeys(value, ["observed_at", "native_exit_code", "native_signal"])) return undefined;
135
+ const code = value.native_exit_code;
136
+ const signal = value.native_signal;
137
+ if (!((Number.isSafeInteger(code) && signal === null) ||
138
+ (code === null && typeof signal === "string" && /^SIG[A-Z0-9]+$/.exec(signal)?.[0] === signal))) return undefined;
139
+ return Object.freeze({
140
+ observed_at: value.observed_at, native_exit_code: code as number | null, native_signal: signal as NodeJS.Signals | null,
141
+ });
142
+ }
143
+
144
+ function decode(value: unknown, owner: RunNativeLaunchOwner): RunNativeLaunchLedger | undefined {
145
+ if (!record(value) || !onlyKeys(value, ["schema", "run", "run_instance_id", "ownerId", "runner_pid", "runner_identity_digest", "intents"]) ||
146
+ value.schema !== SCHEMA || value.run !== owner.run ||
147
+ value.run_instance_id !== owner.runInstanceId || value.ownerId !== owner.ownerId ||
148
+ value.runner_pid !== owner.pid || value.runner_identity_digest !== (owner.processIdentityDigest ?? null) ||
149
+ !Array.isArray(value.intents)) return undefined;
150
+ const ids = new Set<string>();
151
+ const attempts = new Map<string, number>();
152
+ const births = new Set<string>();
153
+ const intents: RunNativeLaunchIntent[] = [];
154
+ for (const item of value.intents) {
155
+ if (!record(item) || !onlyKeys(item, ["intent_id", "command_id", "attempt", "admitted_at", "birth", "settlement"]) ||
156
+ !intentId(item.intent_id) || ids.has(item.intent_id) ||
157
+ !validRequest({ commandId: item.command_id, attempt: item.attempt }) || !canonicalTimestamp(item.admitted_at)) return undefined;
158
+ const command = String(item.command_id);
159
+ const attempt = Number(item.attempt);
160
+ if (attempt !== (attempts.get(command) ?? 0) + 1) return undefined;
161
+ const birth = item.birth === undefined ? undefined : decodeBirth(item.birth, owner.pid);
162
+ if (item.birth !== undefined && !birth) return undefined;
163
+ if (birth) {
164
+ const key = JSON.stringify([birth.process_identity.platform, birth.pid, birth.process_identity.start_time]);
165
+ if (births.has(key)) return undefined;
166
+ births.add(key);
167
+ }
168
+ const settlement = item.settlement === undefined ? undefined : decodeSettlement(item.settlement);
169
+ if (item.settlement !== undefined && (!birth || !settlement)) return undefined;
170
+ ids.add(item.intent_id);
171
+ attempts.set(command, attempt);
172
+ intents.push(Object.freeze({ intent_id: item.intent_id, command_id: command, attempt, admitted_at: item.admitted_at,
173
+ ...(birth ? { birth } : {}), ...(settlement ? { settlement } : {}) }));
174
+ }
175
+ return Object.freeze({
176
+ schema: SCHEMA, run: owner.run, run_instance_id: owner.runInstanceId,
177
+ ...(owner.ownerId === undefined ? {} : { ownerId: owner.ownerId }),
178
+ runner_pid: owner.pid, runner_identity_digest: owner.processIdentityDigest ?? null,
179
+ intents: Object.freeze(intents),
180
+ });
181
+ }
182
+
183
+ /** Even every recorded member's settlement is not complete-scope closure or mutation authority. */
184
+ export function assertNoRetainedNativeLaunchScope(stateDir: string): void {
185
+ try { lstatSync(join(stateDir, RUN_NATIVE_LAUNCH_FILE)); }
186
+ catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return; }
187
+ throw new Error("Run native launch scope is unproven; confirm the complete owned scope before replacing or retiring its state.");
188
+ }
189
+
190
+ /** Absence or an available admission list never proves no child was born or complete scope membership. */
191
+ export function readRunNativeLaunchLedger(stateDir: string, owner: RunNativeLaunchOwner): RunNativeLaunchReading {
192
+ if (!validOwner(owner)) return { state: "invalid" };
193
+ let path: string;
194
+ try { path = join(assertOwnedRunStateDirectory(stateDir, owner.run), RUN_NATIVE_LAUNCH_FILE); }
195
+ catch { return { state: "invalid" }; }
196
+ try { if (!lstatSync(path).isFile()) return { state: "invalid" }; }
197
+ catch (error) { return { state: (error as NodeJS.ErrnoException).code === "ENOENT" ? "absent" : "invalid" }; }
198
+ const read = readJsonFileResilient<unknown>(path, undefined);
199
+ if (read.diagnostics.length) return { state: "invalid" };
200
+ const ledger = decode(read.value, owner);
201
+ return ledger ? { state: "available", ledger } : { state: "invalid" };
202
+ }
203
+
204
+ /** Storage only: caller must hold lifecycle admission and supply a kernel-acquired birth, never replayed runtime authority. */
205
+ export function recordRunNativeLaunchBirthUnderLock(
206
+ stateDir: string,
207
+ owner: RunNativeLaunchOwner,
208
+ intent: Readonly<RunNativeLaunchIntent>,
209
+ birth: RunNativeLaunchBirth,
210
+ ): Readonly<RunNativeLaunchBirth> {
211
+ if (!validOwner(owner)) throw new Error("Run native launch identity or request is invalid.");
212
+ const canonical = assertOwnedRunStateDirectory(stateDir, owner.run);
213
+ const current = readRunNativeLaunchLedger(canonical, owner);
214
+ if (current.state !== "available") throw new Error("Run native launch evidence is unavailable or invalid.");
215
+ const index = current.ledger.intents.findIndex((item) => item.intent_id === intent.intent_id);
216
+ const prior = current.ledger.intents[index];
217
+ if (!prior || prior.birth || prior.command_id !== intent.command_id || prior.attempt !== intent.attempt ||
218
+ prior.admitted_at !== intent.admitted_at) throw new Error("Run native launch birth binding changed or is already retained.");
219
+ const updated = decode({ ...current.ledger, intents: current.ledger.intents.map((item, i) => i === index ? { ...item, birth } : item) }, owner);
220
+ if (!updated) throw new Error("Run native launch birth evidence is unavailable or invalid.");
221
+ writeJsonAtomic(join(canonical, RUN_NATIVE_LAUNCH_FILE), updated);
222
+ return updated.intents[index]!.birth!;
223
+ }
224
+
225
+ /** Storage only: exact retained birth plus admitted native-close or verified-absence evidence, never work authority. */
226
+ export function recordRunNativeLaunchSettlementUnderLock<T extends RunNativeLaunchSettlement>(
227
+ stateDir: string,
228
+ owner: RunNativeLaunchOwner,
229
+ update: { intent: Readonly<RunNativeLaunchIntent>; birth: RunNativeLaunchBirth; settlement: T },
230
+ ): Readonly<T> {
231
+ if (!validOwner(owner)) throw new Error("Run native launch identity or request is invalid.");
232
+ const canonical = assertOwnedRunStateDirectory(stateDir, owner.run);
233
+ const current = readRunNativeLaunchLedger(canonical, owner);
234
+ if (current.state !== "available") throw new Error("Run native launch evidence is unavailable or invalid.");
235
+ const { intent, birth, settlement } = update;
236
+ const index = current.ledger.intents.findIndex((item) => item.intent_id === intent.intent_id);
237
+ const prior = current.ledger.intents[index];
238
+ if (!prior?.birth || prior.settlement || prior.command_id !== intent.command_id || prior.attempt !== intent.attempt ||
239
+ prior.admitted_at !== intent.admitted_at || prior.birth.pid !== birth.pid || prior.birth.observed_at !== birth.observed_at ||
240
+ runProcessIdentityDigest(prior.birth.process_identity) !== runProcessIdentityDigest(birth.process_identity)) {
241
+ throw new Error("Run native launch settlement binding changed or is already retained.");
242
+ }
243
+ const updated = decode({ ...current.ledger, intents: current.ledger.intents.map((item, i) => i === index ? { ...item, settlement } : item) }, owner);
244
+ if (!updated) throw new Error("Run native launch settlement evidence is unavailable or invalid.");
245
+ writeJsonAtomic(join(canonical, RUN_NATIVE_LAUNCH_FILE), updated);
246
+ return updated.intents[index]!.settlement! as Readonly<T>;
247
+ }
248
+
249
+ /** Kernel storage only: caller must hold lifecycle/work admission. No reset or rollback of existing intents. */
250
+ export function recordRunNativeLaunchIntentUnderLock(
251
+ stateDir: string,
252
+ owner: RunNativeLaunchOwner,
253
+ request: RunNativeLaunchRequest,
254
+ ): Readonly<RunNativeLaunchIntent> {
255
+ if (!validOwner(owner) || !validRequest(request)) throw new Error("Run native launch identity or request is invalid.");
256
+ const canonical = assertOwnedRunStateDirectory(stateDir, owner.run);
257
+ const current = readRunNativeLaunchLedger(canonical, owner);
258
+ if (current.state === "invalid") throw new Error("Run native launch evidence is unavailable or invalid.");
259
+ const prior = current.state === "available" ? current.ledger.intents : [];
260
+ const last = prior.filter((item) => item.command_id === request.commandId).at(-1)?.attempt ?? 0;
261
+ if (request.attempt !== last + 1) throw new Error("Run native launch attempt is duplicate or out of order.");
262
+ const intent = Object.freeze({ intent_id: randomUUID(), command_id: request.commandId, attempt: request.attempt, admitted_at: new Date().toISOString() });
263
+ writeJsonAtomic(join(canonical, RUN_NATIVE_LAUNCH_FILE), {
264
+ schema: SCHEMA, run: owner.run, run_instance_id: owner.runInstanceId,
265
+ ...(owner.ownerId === undefined ? {} : { ownerId: owner.ownerId }),
266
+ runner_pid: owner.pid, runner_identity_digest: owner.processIdentityDigest ?? null,
267
+ intents: [...prior, intent],
268
+ });
269
+ return intent;
270
+ }
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Generation-bound outcome protocol and durable stop-request state.
3
+ * Owns: bounded stop records, process binding, lifecycle-locked stop persistence, canonical result classification and primary/artifact binding data schemas.
4
+ * Excludes: lock acquisition, primary body admission/publication, deadline eligibility, signals, exit proof and delivery.
5
+ */
6
+
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { lstatSync } from "node:fs";
9
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
10
+
11
+ import { writeJsonAtomic } from "./file-state.ts";
12
+ import { RUN_COMPLETION_MODEL_MAX_BYTES, RUN_DELIVERY_MODEL_MAX_BYTES } from "./limits.ts";
13
+ import { runProcessIdentityDigest } from "./runs-process.ts";
14
+ import { readJsonFileResilient } from "./state-readers.ts";
15
+
16
+ export const RUN_PRIMARY_TEXT_PROTOCOL = "run-primary-text-v1";
17
+ export const RUN_ARTIFACT_INVENTORY_PROTOCOL = "run-artifact-inventory-v1";
18
+ export const RUN_RESULT_DIAGNOSTICS_PROTOCOL = "run-result-diagnostics-v1";
19
+ export const RUN_RESULT_DIAGNOSTIC_FIELDS = ["command", "error", "failure_reason"] as const;
20
+ export type RunResultDiagnosticField = typeof RUN_RESULT_DIAGNOSTIC_FIELDS[number];
21
+ export type RunResultDiagnosticCandidate = Readonly<Partial<Record<RunResultDiagnosticField, string>>>;
22
+ export const RUN_RESULT_DIAGNOSTIC_EXCERPT_CHARS = 1_000;
23
+ export interface RunResultDiagnosticExcerpt {
24
+ readonly text: string;
25
+ readonly bytes_total: number;
26
+ readonly shown_bytes: Readonly<{ start: number; end: number }>;
27
+ readonly chars_total: number;
28
+ readonly chars_shown: number;
29
+ readonly chars_omitted: number;
30
+ readonly truncated: boolean;
31
+ }
32
+ export type RunResultDiagnosticPacketExcerpt = Readonly<RunResultDiagnosticExcerpt & { field: RunResultDiagnosticField }>;
33
+ export interface RunResultDiagnosticsReference {
34
+ readonly source: Readonly<{ path: string; run_instance_id: string; sha256: string }>;
35
+ readonly bytes_total: number;
36
+ readonly fields: readonly RunResultDiagnosticField[];
37
+ }
38
+ export type RunResultDiagnosticsBinding =
39
+ | { readonly result_diagnostics: Readonly<RunResultDiagnosticsReference> }
40
+ | { readonly result_diagnostics: null; readonly result_diagnostics_unavailable: string };
41
+ export interface RunArtifactInventoryReference {
42
+ readonly source: Readonly<{ path: string; run_instance_id: string; sha256: string }>;
43
+ readonly bytes_total: number;
44
+ readonly declarations_total: number;
45
+ }
46
+ export type RunArtifactInventoryBinding =
47
+ | { readonly artifact_inventory: Readonly<RunArtifactInventoryReference> }
48
+ | { readonly artifact_inventory: null; readonly artifact_inventory_unavailable: string };
49
+
50
+ export interface RunPrimaryTextOutput {
51
+ readonly text_basis?: "actor_response";
52
+ readonly upstream_truncated?: boolean;
53
+ readonly source: { readonly path: string; readonly run_instance_id: string; readonly sha256: string };
54
+ readonly bytes_total: number;
55
+ readonly shown_bytes: { readonly start: number; readonly end: number };
56
+ readonly chars_total: number;
57
+ readonly chars_shown: number;
58
+ readonly chars_omitted: number;
59
+ readonly preview: string;
60
+ readonly truncated: boolean;
61
+ readonly redacted: boolean;
62
+ }
63
+ /** Completion-only admitted source text; this data is not an independent publication or read grant. */
64
+ export interface RunCompletionText {
65
+ readonly text: string;
66
+ readonly shown_bytes: Readonly<{ start: number; end: number }>;
67
+ }
68
+
69
+ /** Prefix presentation only; preserve Unicode scalars and never split an admitted CRLF. */
70
+ export function projectRunCompletionText(text: string, maxBytes: number): Readonly<RunCompletionText> {
71
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0 || maxBytes > RUN_COMPLETION_MODEL_MAX_BYTES ||
72
+ Buffer.from(text).toString("utf8") !== text) throw new Error("Completion text prefix is invalid.");
73
+ const bytes = Buffer.from(text); let end = Math.min(bytes.length, maxBytes);
74
+ while (end > 0 && end < bytes.length && (bytes[end]! & 0xc0) === 0x80) end--;
75
+ if (end < bytes.length && bytes[end - 1] === 13 && bytes[end] === 10) end--;
76
+ return Object.freeze({ text: bytes.subarray(0, end).toString("utf8"), shown_bytes: Object.freeze({ start: 0, end }) });
77
+ }
78
+
79
+ export function decodeRunCompletionText(primary: Readonly<RunPrimaryTextBinding> | undefined, value: unknown): Readonly<RunCompletionText> {
80
+ const data = primaryData(value), range = primaryData(data?.shown_bytes), output = primary?.output;
81
+ if (!data || Object.keys(data).some(key => key !== "text" && key !== "shown_bytes") || !range ||
82
+ Object.keys(range).some(key => key !== "start" && key !== "end") || !output || typeof data.text !== "string" ||
83
+ range.start !== 0 || !Number.isSafeInteger(range.end) || Number(range.end) < 0 ||
84
+ Number(range.end) > Math.min(output.bytes_total, RUN_COMPLETION_MODEL_MAX_BYTES) ||
85
+ Buffer.byteLength(data.text) !== range.end || Buffer.from(data.text).toString("utf8") !== data.text ||
86
+ [...data.text].length > output.chars_total ||
87
+ (range.end === output.bytes_total && (createHash("sha256").update(data.text).digest("hex") !== output.source.sha256 ||
88
+ [...data.text].length !== output.chars_total))) throw new Error("Completion source text is invalid.");
89
+ const end = Math.min(Number(range.end), output.shown_bytes.end), start = output.shown_bytes.start;
90
+ if (end > start && !Buffer.from(data.text).subarray(start, end).equals(Buffer.from(output.preview).subarray(0, end - start))) {
91
+ throw new Error("Completion source text does not match its recorded preview.");
92
+ }
93
+ return Object.freeze({ text: data.text, shown_bytes: Object.freeze({ start: 0, end: Number(range.end) }) });
94
+ }
95
+
96
+ export type RunPrimaryTextBinding =
97
+ | { readonly output: Readonly<RunPrimaryTextOutput> }
98
+ | { readonly output: null; readonly output_unavailable: string };
99
+
100
+ function primaryData(value: unknown): Record<string, unknown> | undefined {
101
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
102
+ const descriptors = Object.getOwnPropertyDescriptors(value);
103
+ return Object.values(descriptors).every(field => Object.hasOwn(field, "value"))
104
+ ? Object.fromEntries(Object.entries(descriptors).map(([key, field]) => [key, field.value])) : undefined;
105
+ }
106
+
107
+ /** Curated JSON-data schema only; neither decoding nor a protocol marker admits a body, producer or work. */
108
+ export function decodeRunPrimaryTextBinding(stateDir: string, generation: string, value: unknown): Readonly<RunPrimaryTextBinding> {
109
+ const input = primaryData(value);
110
+ if (input?.output === null && typeof input.output_unavailable === "string" && /^[a-z_]{1,80}$/.exec(input.output_unavailable)?.[0] === input.output_unavailable) {
111
+ return Object.freeze({ output: null, output_unavailable: input.output_unavailable });
112
+ }
113
+ const output = primaryData(input?.output);
114
+ if (!output || typeof output !== "object") throw new Error("Run primary text binding is invalid.");
115
+ const source = primaryData(output.source); const range = primaryData(output.shown_bytes);
116
+ const { preview, bytes_total: bytes, chars_total: total, chars_shown: shown, chars_omitted: omitted } = output;
117
+ if ((output.text_basis !== undefined && output.text_basis !== "actor_response") ||
118
+ (output.text_basis === "actor_response" ? typeof output.upstream_truncated !== "boolean" : output.upstream_truncated !== undefined) ||
119
+ !source || typeof source.path !== "string" || !isAbsolute(source.path) || resolve(source.path) !== source.path ||
120
+ dirname(source.path) !== join(stateDir, "evidence") || /^primary-[a-f0-9]{64}\.txt$/.exec(basename(source.path))?.[0] !== basename(source.path) || source.run_instance_id !== generation ||
121
+ typeof source.sha256 !== "string" || source.sha256.length !== 64 || !/^[a-f0-9]{64}$/.test(source.sha256) || !range ||
122
+ ![bytes, total, shown, omitted, range.start, range.end].every(number => typeof number === "number" && Number.isSafeInteger(number) && number >= 0) ||
123
+ Number(range.start) > Number(range.end) || Number(range.end) > Number(bytes) || typeof preview !== "string" || Buffer.byteLength(preview) !== Number(range.end) - Number(range.start) ||
124
+ Buffer.from(preview).toString("utf8") !== preview || [...preview].length !== shown || Number(shown) + Number(omitted) !== total || Number(total) > Number(bytes) || typeof output.redacted !== "boolean" || output.truncated !== (Number(omitted) > 0)) {
125
+ throw new Error("Run primary text binding is invalid.");
126
+ }
127
+ const binding = { output: { ...(output.text_basis === "actor_response" ? { text_basis: "actor_response" as const, upstream_truncated: output.upstream_truncated as boolean } : {}),
128
+ source: Object.freeze({ path: source.path, run_instance_id: generation, sha256: source.sha256 }),
129
+ bytes_total: Number(bytes), shown_bytes: Object.freeze({ start: Number(range.start), end: Number(range.end) }), chars_total: Number(total), chars_shown: Number(shown), chars_omitted: Number(omitted),
130
+ preview, truncated: output.truncated, redacted: output.redacted } };
131
+ if (Buffer.byteLength(JSON.stringify(binding)) > RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Run primary text binding is too large.");
132
+ return Object.freeze({ output: Object.freeze(binding.output) });
133
+ }
134
+
135
+ /** Inspect completion shape without invoking getters/custom thenables; this grants no source authority. */
136
+ function synchronousBindingData(value: unknown, label: string): Record<string, unknown> | undefined {
137
+ for (let target = value && typeof value === "object" ? value : null; target; target = Object.getPrototypeOf(target)) {
138
+ const then = Object.getOwnPropertyDescriptor(target, "then");
139
+ if (then && (!Object.hasOwn(then, "value") || typeof then.value === "function")) throw new Error(`${label} binding must complete synchronously.`);
140
+ }
141
+ const input = primaryData(value);
142
+ if (input && Object.hasOwn(input, "then")) throw new Error(`${label} binding must complete synchronously.`);
143
+ return input;
144
+ }
145
+
146
+ /** Data schema only; copied actor fields/markers cannot publish or authorize inventory/body reads. */
147
+ export function decodeRunArtifactInventoryBinding(stateDir: string, generation: string, value: unknown): Readonly<RunArtifactInventoryBinding> {
148
+ const input = synchronousBindingData(value, "Run artifact inventory");
149
+ if (input?.artifact_inventory === null && typeof input.artifact_inventory_unavailable === "string" &&
150
+ /^[a-z_]{1,80}$/u.exec(input.artifact_inventory_unavailable)?.[0] === input.artifact_inventory_unavailable) {
151
+ return Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: input.artifact_inventory_unavailable });
152
+ }
153
+ const inventory = primaryData(input?.artifact_inventory); const source = primaryData(inventory?.source);
154
+ if (!inventory || !source || typeof source.path !== "string" || !isAbsolute(source.path) || resolve(source.path) !== source.path ||
155
+ dirname(source.path) !== join(stateDir, "evidence") || /^artifact-inventory-[a-f0-9]{64}\.json$/u.exec(basename(source.path))?.[0] !== basename(source.path) ||
156
+ source.run_instance_id !== generation || typeof source.sha256 !== "string" || source.sha256.length !== 64 || !/^[a-f0-9]{64}$/u.test(source.sha256) ||
157
+ typeof inventory.bytes_total !== "number" || !Number.isSafeInteger(inventory.bytes_total) || inventory.bytes_total <= 0 ||
158
+ typeof inventory.declarations_total !== "number" || !Number.isSafeInteger(inventory.declarations_total) || inventory.declarations_total <= 0 || inventory.declarations_total > inventory.bytes_total) {
159
+ throw new Error("Run artifact inventory binding is invalid.");
160
+ }
161
+ const binding = Object.freeze({ artifact_inventory: Object.freeze({ source: Object.freeze({ path: source.path, run_instance_id: generation, sha256: source.sha256 }),
162
+ bytes_total: inventory.bytes_total, declarations_total: inventory.declarations_total }) });
163
+ if (Buffer.byteLength(JSON.stringify(binding)) > RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Run artifact inventory binding is too large.");
164
+ return binding;
165
+ }
166
+
167
+ /** Curated diagnostic reference data only, never candidate/producer/publication/body permission. */
168
+ export function decodeRunResultDiagnosticsBinding(stateDir: string, generation: string, value: unknown): Readonly<RunResultDiagnosticsBinding> {
169
+ const input = synchronousBindingData(value, "Run result diagnostics");
170
+ if (input?.result_diagnostics === null && typeof input.result_diagnostics_unavailable === "string" &&
171
+ /^[a-z_]{1,80}$/u.exec(input.result_diagnostics_unavailable)?.[0] === input.result_diagnostics_unavailable) {
172
+ return Object.freeze({ result_diagnostics: null, result_diagnostics_unavailable: input.result_diagnostics_unavailable });
173
+ }
174
+ const reference = primaryData(input?.result_diagnostics); const source = primaryData(reference?.source);
175
+ const list = Array.isArray(reference?.fields) ? Object.getOwnPropertyDescriptors(reference.fields as object) : undefined;
176
+ const fields = list && Object.values(list).every(field => Object.hasOwn(field, "value")) &&
177
+ Number.isSafeInteger(list.length?.value) && list.length!.value > 0 && list.length!.value <= RUN_RESULT_DIAGNOSTIC_FIELDS.length
178
+ ? Array.from({ length: list.length!.value as number }, (_, index) => list[String(index)]?.value) : undefined;
179
+ if (!reference || !source || typeof source.path !== "string" || !isAbsolute(source.path) || resolve(source.path) !== source.path ||
180
+ dirname(source.path) !== join(stateDir, "evidence") || /^result-diagnostics-[a-f0-9]{64}\.json$/u.exec(basename(source.path))?.[0] !== basename(source.path) ||
181
+ source.run_instance_id !== generation || typeof source.sha256 !== "string" || source.sha256.length !== 64 || !/^[a-f0-9]{64}$/u.test(source.sha256) ||
182
+ typeof reference.bytes_total !== "number" || !Number.isSafeInteger(reference.bytes_total) || reference.bytes_total <= 0 || !fields ||
183
+ fields.some((field, index) => !RUN_RESULT_DIAGNOSTIC_FIELDS.includes(field as RunResultDiagnosticField) || fields.indexOf(field) !== index) ||
184
+ JSON.stringify(fields) !== JSON.stringify(RUN_RESULT_DIAGNOSTIC_FIELDS.filter(field => fields.includes(field)))) throw new Error("Run result diagnostics binding is invalid.");
185
+ const binding = Object.freeze({ result_diagnostics: Object.freeze({ source: Object.freeze({ path: source.path, run_instance_id: generation, sha256: source.sha256 }),
186
+ bytes_total: reference.bytes_total, fields: Object.freeze(fields as RunResultDiagnosticField[]) }) });
187
+ if (Buffer.byteLength(JSON.stringify(binding)) > RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Run result diagnostics binding is too large.");
188
+ return binding;
189
+ }
190
+
191
+ /** Frozen literal prefix metadata only; a valid excerpt/reference DTO does not authorize source publication. */
192
+ export function decodeRunResultDiagnosticExcerpt(reference: Readonly<RunResultDiagnosticsReference>, value: unknown): RunResultDiagnosticPacketExcerpt {
193
+ const input = primaryData(value); const range = primaryData(input?.shown_bytes);
194
+ if (!input || !range || !reference.fields.includes(input.field as RunResultDiagnosticField) || typeof input.text !== "string" ||
195
+ Buffer.from(input.text, "utf8").toString("utf8") !== input.text || ![input.bytes_total, input.chars_total, input.chars_shown, input.chars_omitted, range.start, range.end]
196
+ .every(number => typeof number === "number" && Number.isSafeInteger(number) && number >= 0) ||
197
+ range.start !== 0 || Number(range.end) > Number(input.bytes_total) || Buffer.byteLength(input.text) !== range.end ||
198
+ Array.from(input.text).length !== input.chars_shown || Number(input.chars_shown) > RUN_RESULT_DIAGNOSTIC_EXCERPT_CHARS ||
199
+ Number(input.chars_shown) + Number(input.chars_omitted) !== input.chars_total || Number(input.chars_total) > Number(input.bytes_total) ||
200
+ input.truncated !== (Number(input.chars_omitted) > 0)) throw new Error("Run result diagnostic excerpt is invalid.");
201
+ const excerpt = Object.freeze({ field: input.field as RunResultDiagnosticField, text: input.text, bytes_total: Number(input.bytes_total),
202
+ shown_bytes: Object.freeze({ start: 0, end: Number(range.end) }), chars_total: Number(input.chars_total), chars_shown: Number(input.chars_shown),
203
+ chars_omitted: Number(input.chars_omitted), truncated: input.truncated as boolean });
204
+ if (Buffer.byteLength(JSON.stringify(excerpt)) > RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Run result diagnostic excerpt is too large.");
205
+ return excerpt;
206
+ }
207
+
208
+ export const RUN_OUTCOME_PROTOCOL = "run-outcome-v1" as const;
209
+ export const RUN_STOP_CLAIM_FILE = "stop-claim.json";
210
+ const MAX_STOP_CLAIM_BYTES = 16_384;
211
+
212
+ export type RunStopReason = "manual_kill" | "max_runtime";
213
+
214
+ export interface RunStopClaim {
215
+ version: 1;
216
+ claim_id: string;
217
+ run: string;
218
+ run_instance_id: string;
219
+ ownerId?: string;
220
+ pid: number;
221
+ process_identity_digest: string;
222
+ reason: RunStopReason;
223
+ requested_at: string;
224
+ }
225
+
226
+ export type RunStopClaimReading =
227
+ | { state: "absent" | "stale" | "invalid" }
228
+ | { state: "pending"; claim: Readonly<RunStopClaim> };
229
+
230
+
231
+ function isCanonicalTimestamp(value: unknown): value is string {
232
+ if (typeof value !== "string") return false;
233
+ const time = Date.parse(value);
234
+ return Number.isSafeInteger(time) && new Date(time).toISOString() === value;
235
+ }
236
+
237
+ function decodeStopClaim(value: unknown): RunStopClaim | undefined {
238
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
239
+ const record = value as Record<string, unknown>;
240
+ if (record.version !== 1 ||
241
+ typeof record.claim_id !== "string" || !/^[0-9a-f-]{36}$/.test(record.claim_id) ||
242
+ typeof record.run !== "string" || !record.run ||
243
+ typeof record.run_instance_id !== "string" || !record.run_instance_id ||
244
+ (record.ownerId !== undefined && (typeof record.ownerId !== "string" || !record.ownerId)) ||
245
+ !Number.isSafeInteger(record.pid) || Number(record.pid) <= 0 ||
246
+ typeof record.process_identity_digest !== "string" || !/^[0-9a-f]{64}$/.test(record.process_identity_digest) ||
247
+ (record.reason !== "manual_kill" && record.reason !== "max_runtime") ||
248
+ !isCanonicalTimestamp(record.requested_at)) return undefined;
249
+ return {
250
+ version: 1,
251
+ claim_id: record.claim_id,
252
+ run: record.run,
253
+ run_instance_id: record.run_instance_id,
254
+ ...(typeof record.ownerId === "string" ? { ownerId: record.ownerId } : {}),
255
+ pid: Number(record.pid),
256
+ process_identity_digest: record.process_identity_digest,
257
+ reason: record.reason,
258
+ requested_at: record.requested_at,
259
+ };
260
+ }
261
+
262
+ export function readRunStopClaim(
263
+ stateDir: string,
264
+ meta: Record<string, unknown>,
265
+ ): RunStopClaimReading {
266
+ const path = join(stateDir, RUN_STOP_CLAIM_FILE);
267
+ try {
268
+ if (!lstatSync(path).isFile()) return { state: "invalid" };
269
+ } catch (error) {
270
+ return { state: (error as NodeJS.ErrnoException).code === "ENOENT" ? "absent" : "invalid" };
271
+ }
272
+ const read = readJsonFileResilient<unknown>(path, undefined, { maxBytes: MAX_STOP_CLAIM_BYTES });
273
+ if (read.diagnostics.length) return { state: "invalid" };
274
+ if (read.value && typeof read.value === "object" && !Array.isArray(read.value)) {
275
+ const generation = (read.value as Record<string, unknown>).run_instance_id;
276
+ if (typeof generation === "string" && generation && typeof meta.run_instance_id === "string" &&
277
+ generation !== meta.run_instance_id) return { state: "stale" };
278
+ }
279
+ const claim = decodeStopClaim(read.value);
280
+ if (!claim || meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL ||
281
+ claim.run !== meta.run || claim.run_instance_id !== meta.run_instance_id ||
282
+ claim.ownerId !== meta.ownerId || claim.pid !== meta.pid ||
283
+ claim.process_identity_digest !== runProcessIdentityDigest(meta.process_identity)) {
284
+ return { state: "invalid" };
285
+ }
286
+ return { state: "pending", claim: Object.freeze(claim) };
287
+ }
288
+
289
+ /** Only the control owner may call this after admission under the canonical lifecycle lock. */
290
+ export function writeRunStopClaimUnderLock(
291
+ stateDir: string,
292
+ meta: Record<string, unknown>,
293
+ reason: RunStopReason,
294
+ ): Readonly<RunStopClaim> {
295
+ if (readRunStopClaim(stateDir, meta).state !== "absent") {
296
+ throw new Error("Run stop claim cannot replace existing evidence.");
297
+ }
298
+ const claim = {
299
+ version: 1,
300
+ claim_id: randomUUID(),
301
+ run: meta.run,
302
+ run_instance_id: meta.run_instance_id,
303
+ ...(meta.ownerId !== undefined ? { ownerId: meta.ownerId } : {}),
304
+ pid: meta.pid,
305
+ process_identity_digest: runProcessIdentityDigest(meta.process_identity),
306
+ reason,
307
+ requested_at: new Date().toISOString(),
308
+ };
309
+ const decoded = decodeStopClaim(claim);
310
+ if (!decoded || meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL ||
311
+ Buffer.byteLength(JSON.stringify(claim, null, 2), "utf8") > MAX_STOP_CLAIM_BYTES) {
312
+ throw new Error("Run stop claim identity is unavailable or invalid.");
313
+ }
314
+ writeJsonAtomic(join(stateDir, RUN_STOP_CLAIM_FILE), decoded);
315
+ return Object.freeze(decoded);
316
+ }
317
+
318
+ /** Capture markers never imply terminal acceptance, even when their claim is unavailable. */
319
+ export function isRunStopResultCapture(result: Record<string, unknown> | undefined): boolean {
320
+ return result?.terminal_acceptance === "after_stop_claim" || result?.stop_claim_id !== undefined;
321
+ }
322
+
323
+ export type RunNaturalOutcomeReading =
324
+ | { state: "absent" | "invalid" | "captured" }
325
+ | { state: "accepted"; status: "done" | "failed" };
326
+
327
+ export interface RunNaturalOutcomeSnapshot {
328
+ readonly result: Record<string, unknown> | undefined;
329
+ readonly outcome: RunNaturalOutcomeReading;
330
+ }
331
+
332
+ function classifyNaturalOutcome(result: Record<string, unknown>, meta: Record<string, unknown>): RunNaturalOutcomeReading {
333
+ if (typeof meta.run_instance_id !== "string" || !meta.run_instance_id || !Object.hasOwn(result, "code") ||
334
+ result.run_instance_id !== meta.run_instance_id || !Number.isSafeInteger(result.code) ||
335
+ !isCanonicalTimestamp(result.completedAt)) return { state: "invalid" };
336
+ if (result.terminal_acceptance === "after_stop_claim" && typeof result.stop_claim_id === "string") {
337
+ return { state: "captured" };
338
+ }
339
+ if (result.terminal_acceptance !== "natural" || Object.hasOwn(result, "stop_claim_id")) {
340
+ return { state: "invalid" };
341
+ }
342
+ return { state: "accepted", status: result.code === 0 ? "done" : "failed" };
343
+ }
344
+
345
+ function readResultJson(path: string): unknown {
346
+ const read = readJsonFileResilient<unknown>(path, undefined);
347
+ return read.diagnostics.length ? undefined : read.value;
348
+ }
349
+
350
+ /** Read one regular-file result snapshot for both precedence and rendering; data never grants work/stop authority. */
351
+ export function readRunNaturalOutcomeSnapshot(
352
+ stateDir: string,
353
+ meta: Record<string, unknown>,
354
+ readResult: (path: string) => unknown = readResultJson,
355
+ ): Readonly<RunNaturalOutcomeSnapshot> {
356
+ const path = join(stateDir, "result.json");
357
+ try {
358
+ if (!lstatSync(path).isFile()) return Object.freeze<RunNaturalOutcomeSnapshot>({ result: undefined, outcome: { state: "invalid" } });
359
+ } catch (error) {
360
+ return Object.freeze<RunNaturalOutcomeSnapshot>({ result: undefined,
361
+ outcome: { state: (error as NodeJS.ErrnoException).code === "ENOENT" ? "absent" : "invalid" } });
362
+ }
363
+ const value = readResult(path);
364
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
365
+ return Object.freeze<RunNaturalOutcomeSnapshot>({ result: undefined, outcome: { state: "invalid" } });
366
+ }
367
+ const result = value as Record<string, unknown>;
368
+ return Object.freeze({ result, outcome: classifyNaturalOutcome(result, meta) });
369
+ }
370
+
371
+ /** Result bodies remain in their existing canonical file; this is admission, not a second store. */
372
+ export function readRunNaturalOutcome(
373
+ stateDir: string,
374
+ meta: Record<string, unknown>,
375
+ ): RunNaturalOutcomeReading {
376
+ return readRunNaturalOutcomeSnapshot(stateDir, meta).outcome;
377
+ }