@llblab/pi-kit 0.38.4 → 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 (424) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +1 -1
  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/yaml/LICENSE +13 -0
  188. package/node_modules/yaml/README.md +172 -0
  189. package/node_modules/yaml/bin.mjs +11 -0
  190. package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
  191. package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
  192. package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
  193. package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
  194. package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
  195. package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
  196. package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
  197. package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
  198. package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
  199. package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
  200. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
  201. package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
  202. package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
  203. package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
  204. package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
  205. package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
  206. package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
  207. package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
  208. package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
  209. package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
  210. package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
  211. package/node_modules/yaml/browser/dist/errors.js +57 -0
  212. package/node_modules/yaml/browser/dist/index.js +17 -0
  213. package/node_modules/yaml/browser/dist/log.js +11 -0
  214. package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
  215. package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
  216. package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
  217. package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
  218. package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
  219. package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
  220. package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
  221. package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
  222. package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
  223. package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
  224. package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
  225. package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
  226. package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
  227. package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
  228. package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
  229. package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
  230. package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
  231. package/node_modules/yaml/browser/dist/public-api.js +102 -0
  232. package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
  233. package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
  234. package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
  235. package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
  236. package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
  237. package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
  238. package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
  239. package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
  240. package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
  241. package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
  242. package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
  243. package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
  244. package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
  245. package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
  246. package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
  247. package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
  248. package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
  249. package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
  250. package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
  251. package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
  252. package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
  253. package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
  254. package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
  255. package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
  256. package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
  257. package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
  258. package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
  259. package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
  260. package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
  261. package/node_modules/yaml/browser/dist/util.js +11 -0
  262. package/node_modules/yaml/browser/dist/visit.js +233 -0
  263. package/node_modules/yaml/browser/index.js +5 -0
  264. package/node_modules/yaml/browser/package.json +3 -0
  265. package/node_modules/yaml/dist/cli.d.ts +8 -0
  266. package/node_modules/yaml/dist/cli.mjs +201 -0
  267. package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
  268. package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  269. package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
  270. package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  271. package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
  272. package/node_modules/yaml/dist/compose/compose-node.js +112 -0
  273. package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
  274. package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  275. package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
  276. package/node_modules/yaml/dist/compose/composer.js +224 -0
  277. package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
  278. package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  279. package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
  280. package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  281. package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
  282. package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  283. package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
  284. package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  285. package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
  286. package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  287. package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
  288. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
  289. package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
  290. package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  291. package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
  292. package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  293. package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
  294. package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  295. package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
  296. package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  297. package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
  298. package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  299. package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
  300. package/node_modules/yaml/dist/doc/Document.js +337 -0
  301. package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
  302. package/node_modules/yaml/dist/doc/anchors.js +76 -0
  303. package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
  304. package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  305. package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
  306. package/node_modules/yaml/dist/doc/createNode.js +90 -0
  307. package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
  308. package/node_modules/yaml/dist/doc/directives.js +178 -0
  309. package/node_modules/yaml/dist/errors.d.ts +21 -0
  310. package/node_modules/yaml/dist/errors.js +62 -0
  311. package/node_modules/yaml/dist/index.d.ts +25 -0
  312. package/node_modules/yaml/dist/index.js +50 -0
  313. package/node_modules/yaml/dist/log.d.ts +3 -0
  314. package/node_modules/yaml/dist/log.js +19 -0
  315. package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
  316. package/node_modules/yaml/dist/nodes/Alias.js +118 -0
  317. package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
  318. package/node_modules/yaml/dist/nodes/Collection.js +151 -0
  319. package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
  320. package/node_modules/yaml/dist/nodes/Node.js +40 -0
  321. package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
  322. package/node_modules/yaml/dist/nodes/Pair.js +39 -0
  323. package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
  324. package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  325. package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
  326. package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  327. package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
  328. package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  329. package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
  330. package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  331. package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
  332. package/node_modules/yaml/dist/nodes/identity.js +53 -0
  333. package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
  334. package/node_modules/yaml/dist/nodes/toJS.js +39 -0
  335. package/node_modules/yaml/dist/options.d.ts +350 -0
  336. package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
  337. package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  338. package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
  339. package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  340. package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
  341. package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  342. package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
  343. package/node_modules/yaml/dist/parse/cst.js +112 -0
  344. package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
  345. package/node_modules/yaml/dist/parse/lexer.js +723 -0
  346. package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
  347. package/node_modules/yaml/dist/parse/line-counter.js +41 -0
  348. package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
  349. package/node_modules/yaml/dist/parse/parser.js +980 -0
  350. package/node_modules/yaml/dist/public-api.d.ts +44 -0
  351. package/node_modules/yaml/dist/public-api.js +107 -0
  352. package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
  353. package/node_modules/yaml/dist/schema/Schema.js +39 -0
  354. package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
  355. package/node_modules/yaml/dist/schema/common/map.js +19 -0
  356. package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
  357. package/node_modules/yaml/dist/schema/common/null.js +17 -0
  358. package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
  359. package/node_modules/yaml/dist/schema/common/seq.js +19 -0
  360. package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
  361. package/node_modules/yaml/dist/schema/common/string.js +16 -0
  362. package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
  363. package/node_modules/yaml/dist/schema/core/bool.js +21 -0
  364. package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
  365. package/node_modules/yaml/dist/schema/core/float.js +47 -0
  366. package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
  367. package/node_modules/yaml/dist/schema/core/int.js +42 -0
  368. package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
  369. package/node_modules/yaml/dist/schema/core/schema.js +25 -0
  370. package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
  371. package/node_modules/yaml/dist/schema/json/schema.js +64 -0
  372. package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
  373. package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
  374. package/node_modules/yaml/dist/schema/tags.js +99 -0
  375. package/node_modules/yaml/dist/schema/types.d.ts +92 -0
  376. package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
  377. package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  378. package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
  379. package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  380. package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
  381. package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  382. package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
  383. package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  384. package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
  385. package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
  386. package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
  387. package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  388. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
  389. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  390. package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
  391. package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  392. package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
  393. package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  394. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
  395. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  396. package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
  397. package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  398. package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
  399. package/node_modules/yaml/dist/stringify/stringify.js +132 -0
  400. package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
  401. package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  402. package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
  403. package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  404. package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
  405. package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  406. package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
  407. package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
  408. package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
  409. package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  410. package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
  411. package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  412. package/node_modules/yaml/dist/test-events.d.ts +4 -0
  413. package/node_modules/yaml/dist/test-events.js +134 -0
  414. package/node_modules/yaml/dist/util.d.ts +16 -0
  415. package/node_modules/yaml/dist/util.js +28 -0
  416. package/node_modules/yaml/dist/visit.d.ts +102 -0
  417. package/node_modules/yaml/dist/visit.js +236 -0
  418. package/node_modules/yaml/package.json +97 -0
  419. package/node_modules/yaml/util.js +2 -0
  420. package/package.json +2 -2
  421. package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
  422. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +0 -16
  423. package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +0 -57
  424. package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +0 -79
@@ -0,0 +1,1871 @@
1
+ /**
2
+ * Producer-bound Run context, primary text and index orchestration.
3
+ * Owns: admitted privacy capture, atomic derived views, producer-backed saved Recipe/Trace/Control/Execution/Result correspondence, immutable primary source publication/readonly admission and index assembly.
4
+ * Excludes: public routing, session/composed/failure primary publication, live clocks and native work/control authority.
5
+ */
6
+
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { closeSync, constants, fstatSync, linkSync, lstatSync, mkdirSync, openSync, readSync, unlinkSync, writeFileSync, type BigIntStats } from "node:fs";
9
+ import type { CommandTemplateAttemptReceipt, CommandTemplateExecResult } from "./command-templates.ts";
10
+ import { readExecutionCaptureText, RUN_STREAM_SOURCE_MAX_BYTES } from "./execution-files.ts";
11
+ import { isAbsolute, join, sep } from "node:path";
12
+ import { isPromise } from "node:util/types";
13
+ import { isDeepStrictEqual } from "node:util";
14
+
15
+ import { normalizeControlAction, normalizeControlInput } from "./control.ts";
16
+ import { projectRunControl, type ProjectedRunControl } from "./control-projection.ts";
17
+ import { projectRunExecutionManifest, type RunExecutionView } from "./runs-execution-evidence.ts";
18
+ import { writeTextAtomic } from "./file-state.ts";
19
+ import { COMPACT_PREVIEW_CHARS, DEFAULT_INSPECT_LINES, RUN_COMPLETION_MODEL_MAX_BYTES, RUN_DELIVERY_JOURNAL_MAX_BYTES, RUN_DELIVERY_MODEL_MAX_BYTES, RUN_CONTROL_JOURNAL_MAX_BYTES, TRACE_JOURNAL_MAX_BYTES } from "./limits.ts";
20
+ import { createRunRecipeBodyRedactor, createRunRecipeTextRedactor, projectRunRecipe, capturedSecretMask, createContextIdentityPrivacyCheck } from "./recipe-projection.ts";
21
+ import { computeRunControlCapacity } from "./run-evidence-policy.ts";
22
+ import { readRunSupervisionClock, createRunSupervisionTimebase } from "./run-supervision.ts";
23
+ import { classifyRuntimeControl } from "./runtime-triage.ts";
24
+ import { runContextExportPath, writeRunContextIndexUnderLock, readRunContextIdentity, readRunContextIndex, RUN_CONTEXT_INDEX_FILE, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES,
25
+ type RunContextEntry, type RunContextIndexPublication, type RunContextIndexIdentity } from "./runs-context-index.ts";
26
+ import { readRunControlJournalFromStateDir, runControlsFile, type RunControlRecord } from "./runs-controls.ts";
27
+ import { assertOwnedRunStateDirectory, RUN_STATE_OWNERSHIP_FILE } from "./runs-ownership.ts";
28
+ import { describeRunArtifactReferences, observeRunArtifactReference } from "./runs-artifacts.ts";
29
+ import { decodeRunPrimaryTextBinding, readRunNaturalOutcomeSnapshot, RUN_OUTCOME_PROTOCOL, RUN_PRIMARY_TEXT_PROTOCOL, RUN_ARTIFACT_INVENTORY_PROTOCOL, decodeRunArtifactInventoryBinding, RUN_RESULT_DIAGNOSTIC_FIELDS as RESULT_DIAGNOSTIC_FIELDS, RUN_RESULT_DIAGNOSTICS_PROTOCOL, decodeRunResultDiagnosticsBinding, type RunResultDiagnosticsBinding, type RunResultDiagnosticCandidate, type RunResultDiagnosticExcerpt, type RunResultDiagnosticPacketExcerpt, RUN_RESULT_DIAGNOSTIC_EXCERPT_CHARS, decodeRunResultDiagnosticExcerpt, type RunArtifactInventoryBinding, type RunArtifactInventoryReference, type RunPrimaryTextBinding, type RunPrimaryTextOutput } from "./runs-outcomes.ts";
30
+ import { withRunProducerEvidence, readRunProducerCapturedMetadata, readRunProducerCleanupFailure, withRunResultDiagnosticsAdmission, type RunResultDiagnosticsAdmission, type RunResultProducer } from "./runs-results.ts";
31
+ import { readRunTraceJournal, runTraceFile, summarizeRunTraceJournal, type RunTraceSummary } from "./runs-trace.ts";
32
+ import { readJsonFileResilient, readJsonlFileResilient } from "./state-readers.ts";
33
+ import { projectRunTraceJournal, type TraceItem } from "./trace-projection.ts";
34
+ import { redactSessionEvidenceValue } from "./session-evidence.ts";
35
+
36
+ export { readRunSessionContextPreview, writeRunSessionContextExport, writeRunSessionContextIndex, RUN_CONTEXT_SESSIONS_MAX_BYTES } from "./runs-session-exports.ts";
37
+ export type { RunSessionContextExportResult, RunSessionContextPreviewReading } from "./runs-session-exports.ts";
38
+
39
+ export const RUN_CONTEXT_CONTROL_MAX_BYTES = 64 * 1024;
40
+ const CONTROL_VIEW_SCHEMA = "run-context-control-v1";
41
+ const CONTROL_VIEW_COVERAGE = Object.freeze({ truncated: true, history_complete: false, redacted: true });
42
+ export const RUN_CONTEXT_RECIPE_MAX_BYTES = 64 * 1024;
43
+ const RECIPE_VIEW_COVERAGE = Object.freeze({ truncated: true, redacted: true });
44
+ export const RUN_CONTEXT_TRACE_MAX_BYTES = 64 * 1024;
45
+ const TRACE_VIEW_COVERAGE = Object.freeze({ truncated: true, history_complete: false, redacted: true });
46
+ export const RUN_CONTEXT_RESULT_MAX_BYTES = 64 * 1024;
47
+ export const RUN_RESULT_DIAGNOSTICS_MAX_BYTES = 4 * 1024 * 1024;
48
+ const RESULT_DIAGNOSTIC_PREVIEW_CHARS = 4_000;
49
+ const RESULT_VIEW_COVERAGE = Object.freeze({ truncated: true, redacted: true });
50
+ export const RUN_CONTEXT_ARTIFACT_MAX_BYTES = 64 * 1024;
51
+ export const RUN_ARTIFACT_INVENTORY_MAX_BYTES = 4 * 1024 * 1024;
52
+ const ARTIFACT_VIEW_COVERAGE = Object.freeze({ truncated: false, redacted: true });
53
+ export const RUN_CONTEXT_EXECUTION_MAX_BYTES = 64 * 1024;
54
+ const EXECUTION_VIEW_COVERAGE = Object.freeze({ truncated: true, history_complete: false, redacted: true });
55
+ const VIEW_CONFIG = {
56
+ control: { schema: CONTROL_VIEW_SCHEMA, format: "json", media: "application/json", maxBytes: RUN_CONTEXT_CONTROL_MAX_BYTES, coverage: CONTROL_VIEW_COVERAGE },
57
+ recipe: { schema: "run-context-recipe-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_RECIPE_MAX_BYTES, coverage: RECIPE_VIEW_COVERAGE },
58
+ trace: { schema: "run-context-trace-v1", format: "jsonl", media: "application/x-ndjson", maxBytes: RUN_CONTEXT_TRACE_MAX_BYTES, coverage: TRACE_VIEW_COVERAGE },
59
+ result: { schema: "run-context-result-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_RESULT_MAX_BYTES, coverage: RESULT_VIEW_COVERAGE },
60
+ artifacts: { schema: "run-context-artifacts-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_ARTIFACT_MAX_BYTES, coverage: ARTIFACT_VIEW_COVERAGE },
61
+ execution: { schema: "run-context-execution-v1", format: "json", media: "application/json", maxBytes: RUN_CONTEXT_EXECUTION_MAX_BYTES, coverage: EXECUTION_VIEW_COVERAGE },
62
+ } as const;
63
+ type ContextExportRole = keyof typeof VIEW_CONFIG;
64
+ class ContextExportBindingRefusal extends Error {}
65
+ type CapturedView = { readonly status: "captured"; readonly captured_at: string; readonly content: string;
66
+ readonly coverage?: Readonly<{ truncated: boolean; redacted: boolean; history_complete?: boolean }>;
67
+ readonly unchanged?: () => boolean };
68
+ type TraceCapture = CapturedView & { readonly original_root: string };
69
+ type ExecutionCapture = CapturedView & { readonly original_root: string };
70
+ type ResultCapture = CapturedView & { readonly original_root: string };
71
+ // Producer-scoped admitted Trace bytes, never a generic registry or saved/hash-reconstructed capability.
72
+ const tracePublications = new WeakMap<RunResultProducer, Readonly<{ root: string; content: string }>>();
73
+ // Separate role authority: a successful Trace publication never admits a Control view.
74
+ const controlPublications = new WeakMap<RunResultProducer, Readonly<{ root: string; content: string }>>();
75
+ const executionPublications = new WeakMap<RunResultProducer, Readonly<{ root: string; content: string }>>();
76
+ const resultPublications = new WeakMap<RunResultProducer, Readonly<{ root: string; content: string }>>();
77
+ type WrittenView = { readonly status: "written"; readonly captured_at: string; readonly entry: Readonly<RunContextEntry> };
78
+ type RecipeUnavailable = { readonly status: "unavailable"; readonly reason: "recipe_capture_absent" |
79
+ "recipe_metadata_unavailable" | "recipe_metadata_invalid" | "recipe_metadata_changed" | "recipe_admission_unavailable" | "recipe_identity_redacted" | "recipe_metadata_redacted" | "recipe_view_too_large" };
80
+ type TraceUnavailable = { readonly status: "unavailable"; readonly reason: "trace_journal_absent" | "trace_journal_unavailable" |
81
+ "trace_journal_invalid" | "trace_journal_changed" | "trace_metadata_unavailable" | "trace_metadata_changed" | "trace_admission_unavailable" | "trace_identity_redacted" | "trace_metadata_redacted" };
82
+ type ResultUnavailable = { readonly status: "unavailable"; readonly reason: "result_absent" | "result_unavailable" |
83
+ "result_invalid" | "result_changed" | "result_metadata_unavailable" | "result_identity_redacted" | "result_metadata_redacted" | "result_view_too_large" |
84
+ "result_diagnostics_invalid" | "result_diagnostics_too_large" };
85
+ type ArtifactUnavailable = { readonly status: "unavailable"; readonly reason: "artifact_declarations_absent" |
86
+ "artifact_declarations_invalid" | "artifact_base_unavailable" | "artifact_metadata_unavailable" | "artifact_metadata_changed" |
87
+ "artifact_manifest_too_large" | "artifact_observation_unavailable" | "artifact_admission_unavailable" | "artifact_identity_redacted" };
88
+ type ExecutionUnavailable = { readonly status: "unavailable"; readonly reason: "execution_absent" | "execution_unavailable" |
89
+ "execution_invalid" | "execution_changed" | "execution_metadata_unavailable" | "execution_admission_unavailable" | "execution_identity_redacted" | "execution_metadata_redacted" | "execution_view_too_large" };
90
+ export type RunExecutionContextExportResult = ExecutionUnavailable | WrittenView;
91
+ function executionUnavailable(reason: ExecutionUnavailable["reason"]): ExecutionUnavailable { return Object.freeze({ status: "unavailable", reason }); }
92
+ export type RunArtifactContextExportResult = ArtifactUnavailable | WrittenView;
93
+ function artifactUnavailable(reason: ArtifactUnavailable["reason"]): ArtifactUnavailable { return Object.freeze({ status: "unavailable", reason }); }
94
+ export type RunResultContextExportResult = ResultUnavailable | WrittenView;
95
+ function resultUnavailable(reason: ResultUnavailable["reason"]): ResultUnavailable { return Object.freeze({ status: "unavailable", reason }); }
96
+ export type RunTraceContextExportResult = TraceUnavailable | WrittenView;
97
+ function traceUnavailable(reason: TraceUnavailable["reason"]): TraceUnavailable { return Object.freeze({ status: "unavailable", reason }); }
98
+ export type RunRecipeContextExportResult = RecipeUnavailable | WrittenView;
99
+ function recipeUnavailable(reason: RecipeUnavailable["reason"]): RecipeUnavailable { return Object.freeze({ status: "unavailable", reason }); }
100
+
101
+ type Unavailable = { readonly status: "unavailable";
102
+ readonly reason: "control_journal_absent" | "control_journal_unavailable" | "control_journal_invalid" | "control_journal_changed" |
103
+ "control_metadata_unavailable" | "control_metadata_changed" | "control_admission_unavailable" | "control_identity_redacted" | "control_metadata_redacted" };
104
+ export type RunControlContextExportResult = Unavailable | WrittenView;
105
+ type ControlCapture = Unavailable | (CapturedView & { readonly original_root: string });
106
+
107
+ function unavailable(reason: Unavailable["reason"]): Unavailable { return Object.freeze({ status: "unavailable", reason }); }
108
+ function sameObject(left: BigIntStats, right: BigIntStats): boolean { return left.dev === right.dev && left.ino === right.ino; }
109
+ function sameFile(left: BigIntStats, right: BigIntStats): boolean {
110
+ return right.isFile() && sameObject(left, right) && left.size === right.size &&
111
+ left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
112
+ }
113
+ function optionalStamp(path: string): BigIntStats | undefined {
114
+ try { return lstatSync(path, { bigint: true }); }
115
+ catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw error; }
116
+ }
117
+
118
+ function captureControl(stateDir: string, producer: RunResultProducer, status: string): ControlCapture {
119
+ const metadata = readContextMetadata(stateDir, producer);
120
+ if (metadata.state === "unavailable") return unavailable(metadata.reason === "changed" ? "control_metadata_changed" : "control_metadata_unavailable");
121
+ const unchanged = captureContextMetadataFence(metadata, producer);
122
+ if (!unchanged) return unavailable("control_metadata_changed");
123
+ const original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
124
+ if (!original) return unavailable("control_admission_unavailable");
125
+ const redact = createRunRecipeBodyRedactor(original, Number.MAX_SAFE_INTEGER);
126
+ const identityUnchanged = createContextIdentityPrivacyCheck(original);
127
+ const headerIdentity = { run: producer.run, run_instance_id: producer.runInstanceId };
128
+ if (!identityUnchanged(headerIdentity)) return unavailable("control_identity_redacted");
129
+ const path = runControlsFile(stateDir);
130
+ const before = optionalStamp(path);
131
+ if (!before) return unavailable("control_journal_absent");
132
+ if (!before.isFile() || before.size > BigInt(RUN_CONTROL_JOURNAL_MAX_BYTES)) return unavailable("control_journal_unavailable");
133
+ const journal = readRunControlJournalFromStateDir(stateDir);
134
+ if (journal.diagnostics.length) return unavailable("control_journal_invalid");
135
+ const after = optionalStamp(path);
136
+ if (!after || !sameFile(before, after)) return unavailable("control_journal_changed");
137
+ const current: RunControlRecord[] = [];
138
+ let foreign = 0;
139
+ const now = Date.now();
140
+ const timestampFields = ["queued_at", "delivered_at", "claimed_at", "handled_at", "failed_at"] as const;
141
+ for (const value of journal.records) {
142
+ if (!value || typeof value !== "object" || Array.isArray(value)) return unavailable("control_journal_invalid");
143
+ const record = value as Record<string, unknown>;
144
+ if (typeof record.run_instance_id !== "string" || !record.run_instance_id) return unavailable("control_journal_invalid");
145
+ if (record.run_instance_id !== producer.runInstanceId) { foreign++; continue; }
146
+ try {
147
+ normalizeControlAction(record.action);
148
+ if (record.input !== undefined) normalizeControlInput(record.input);
149
+ if (record.error !== undefined && typeof record.error !== "string") return unavailable("control_journal_invalid");
150
+ for (const field of timestampFields) {
151
+ const timestamp = record[field];
152
+ if ((field === "queued_at" || timestamp !== undefined) &&
153
+ (typeof timestamp !== "string" || !Number.isFinite(Date.parse(timestamp)))) return unavailable("control_journal_invalid");
154
+ }
155
+ if (classifyRuntimeControl({ run: producer.run, runInstanceId: producer.runInstanceId, status }, value, now).diagnostic) {
156
+ return unavailable("control_journal_invalid");
157
+ }
158
+ } catch { return unavailable("control_journal_invalid"); }
159
+ current.push(record as unknown as RunControlRecord);
160
+ }
161
+ const capacity = computeRunControlCapacity(current);
162
+ if (capacity.oldest_pending_at !== undefined && !identityUnchanged({ oldest_pending_at: capacity.oldest_pending_at })) {
163
+ return unavailable("control_metadata_redacted");
164
+ }
165
+ const capturedAt = new Date().toISOString();
166
+ if (!identityUnchanged({ captured_at: capturedAt })) return unavailable("control_metadata_redacted");
167
+ // Safe references stay exact, never Inspector previews; privacy-changed references omit the whole row.
168
+ const controls = current.slice(-DEFAULT_INSPECT_LINES).flatMap(control => {
169
+ const identity = { id: control.id, run_instance_id: control.run_instance_id, action: control.action,
170
+ ...Object.fromEntries(timestampFields.flatMap(field => control[field] === undefined ? [] : [[field, control[field]]])) };
171
+ if (!identityUnchanged(identity)) return [];
172
+ return [{ ...projectRunControl({ ...control,
173
+ ...(control.input === undefined ? {} : { input: redact(control.input) }),
174
+ ...(control.error === undefined ? {} : { error: redact(control.error) as string }) }), ...identity }];
175
+ });
176
+ const encode = () => JSON.stringify({ schema: CONTROL_VIEW_SCHEMA, run: producer.run, run_instance_id: producer.runInstanceId,
177
+ captured_at: capturedAt, run_status: status, coverage: CONTROL_VIEW_COVERAGE,
178
+ capacity, controls,
179
+ controls_omitted: current.length - controls.length, foreign_controls_omitted: foreign }, null, 2) + "\n";
180
+ let content = encode();
181
+ while (controls.length && Buffer.byteLength(content, "utf8") > RUN_CONTEXT_CONTROL_MAX_BYTES) { controls.shift(); content = encode(); }
182
+ if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_CONTROL_MAX_BYTES) return unavailable("control_journal_unavailable");
183
+ // Fence the admitted metadata frame, not journal freshness after this recorded snapshot.
184
+ return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root }) : unavailable("control_metadata_changed");
185
+ }
186
+
187
+ function record(value: unknown): Record<string, unknown> | undefined {
188
+ return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
189
+ }
190
+ type ContextMetadata = { state: "available"; root: string; path: string; stamp: BigIntStats; meta: Record<string, unknown> }
191
+ | { state: "unavailable"; reason: "unavailable" | "invalid" | "changed" };
192
+ /** Source data only; callers must already hold genuine producer evidence admission. */
193
+ function readContextMetadata(stateDir: string, producer: RunResultProducer): ContextMetadata {
194
+ const root = assertOwnedRunStateDirectory(stateDir, producer.run);
195
+ const path = join(root, "run.json");
196
+ const before = optionalStamp(path);
197
+ if (!before?.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES)) return { state: "unavailable", reason: "unavailable" };
198
+ const read = readJsonFileResilient<unknown>(path, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES });
199
+ const after = optionalStamp(path);
200
+ if (!after || !sameFile(before, after)) return { state: "unavailable", reason: "changed" };
201
+ const meta = record(read.value);
202
+ if (read.diagnostics.length || !meta) return { state: "unavailable", reason: "invalid" };
203
+ if (meta.run !== producer.run || meta.run_instance_id !== producer.runInstanceId || meta.ownerId !== producer.ownerId ||
204
+ meta.state_dir !== root) return { state: "unavailable", reason: "changed" };
205
+ return { state: "available", root, path, stamp: after, meta };
206
+ }
207
+
208
+ /** Readonly current-frame observation, never producer or body permission; callers already hold evidence admission. */
209
+ function captureContextMetadataFence(metadata: Extract<ContextMetadata, { state: "available" }>, producer: RunResultProducer): (() => boolean) | undefined {
210
+ const owner = readRunContextIdentity(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId,
211
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
212
+ if (owner.state !== "available" || owner.local_path !== metadata.root) return undefined;
213
+ const unchanged = (): boolean => {
214
+ try { return owner.unchanged() && sameFile(metadata.stamp, lstatSync(metadata.path, { bigint: true })); }
215
+ catch { return false; }
216
+ };
217
+ return unchanged() ? unchanged : undefined;
218
+ }
219
+
220
+ /** Checked fixed-source I/O only. Each caller must already admit its producer, projection policy and derived target. */
221
+ function retainContextSource(metadata: Extract<ContextMetadata, { state: "available" }>, path: string,
222
+ content: string, maxBytes: number, label: string): { readonly unchanged: () => boolean } {
223
+ const root = lstatSync(metadata.root, { bigint: true }); const markerPath = join(metadata.root, RUN_STATE_OWNERSHIP_FILE);
224
+ const marker = lstatSync(markerPath, { bigint: true }); const evidencePath = join(metadata.root, "evidence");
225
+ let evidence = optionalStamp(evidencePath);
226
+ if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink())) throw new ContextExportBindingRefusal(`${label} directory binding is invalid.`);
227
+ if (!evidence) { mkdirSync(evidencePath); evidence = lstatSync(evidencePath, { bigint: true }); }
228
+ const frameUnchanged = (): boolean => sameObject(root, lstatSync(metadata.root, { bigint: true })) &&
229
+ sameFile(metadata.stamp, lstatSync(metadata.path, { bigint: true })) && sameFile(marker, lstatSync(markerPath, { bigint: true })) &&
230
+ sameObject(evidence!, lstatSync(evidencePath, { bigint: true }));
231
+ if (!frameUnchanged()) throw new ContextExportBindingRefusal(`${label} publication binding changed.`);
232
+ if (optionalStamp(path)) {
233
+ if (readFixedSourceText(path, maxBytes, `${label} source`).text !== content) {
234
+ throw new ContextExportBindingRefusal(`${label} identity already fixes different bytes.`);
235
+ }
236
+ } else {
237
+ // Source, view and index remain separate effects; preserve partial/orphan writes on failure.
238
+ writeFileSync(path, content, { flag: "wx", mode: 0o600 });
239
+ }
240
+ const saved = readFixedSourceText(path, maxBytes, `${label} source`);
241
+ if (saved.text !== content || !frameUnchanged()) throw new ContextExportBindingRefusal(`${label} publication binding changed.`);
242
+ return { unchanged: () => { try { return frameUnchanged() && sameFile(saved.stamp, lstatSync(path, { bigint: true })); } catch { return false; } } };
243
+ }
244
+
245
+ type RecipeProjection = { readonly capture: "definition_and_launch" | "launch_only"; readonly recipe: ReturnType<typeof projectRunRecipe>;
246
+ readonly stored_clock?: NonNullable<ReturnType<typeof readRunSupervisionClock>> };
247
+ function projectCapturedRecipe(meta: Record<string, unknown>): RecipeUnavailable | ({ readonly status: "projected" } & RecipeProjection) {
248
+ const contexts = meta.recipe_context_records;
249
+ if (contexts !== undefined && (!Array.isArray(contexts) || contexts.some(value => !record(value)))) return recipeUnavailable("recipe_metadata_invalid");
250
+ const template = meta.template;
251
+ if (template !== undefined && !(typeof template === "string" && template.length > 0) &&
252
+ !(Array.isArray(template) && template.length > 0) && !record(template)) return recipeUnavailable("recipe_metadata_invalid");
253
+ const recipe = projectRunRecipe(meta, [], createRunRecipeBodyRedactor(meta));
254
+ if (template === undefined && !recipe.definition) return recipeUnavailable("recipe_capture_absent");
255
+ const clock = readRunSupervisionClock(meta);
256
+ if (clock && !createContextIdentityPrivacyCheck(meta)({ started_at: clock.started_at })) return recipeUnavailable("recipe_metadata_redacted");
257
+ return { status: "projected", capture: recipe.definition ? "definition_and_launch" : "launch_only", recipe, ...(clock ? { stored_clock: clock } : {}) };
258
+ }
259
+
260
+ function captureRecipe(stateDir: string, producer: RunResultProducer): RecipeUnavailable | CapturedView {
261
+ const metadata = readContextMetadata(stateDir, producer);
262
+ if (metadata.state === "unavailable") return recipeUnavailable(metadata.reason === "changed" ? "recipe_metadata_changed"
263
+ : metadata.reason === "invalid" ? "recipe_metadata_invalid" : "recipe_metadata_unavailable");
264
+ const unchanged = captureContextMetadataFence(metadata, producer);
265
+ if (!unchanged) return recipeUnavailable("recipe_metadata_changed");
266
+ // Live metadata fences the observation; it cannot renew static launch selection or its privacy policy.
267
+ const meta = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
268
+ if (!meta) return recipeUnavailable("recipe_admission_unavailable");
269
+ const metadataUnredacted = createContextIdentityPrivacyCheck(meta);
270
+ if (!metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId })) return recipeUnavailable("recipe_identity_redacted");
271
+ const projected = projectCapturedRecipe(meta); if (projected.status === "unavailable") return projected;
272
+ const { status: _status, ...fields } = projected; const capturedAt = new Date().toISOString();
273
+ if (!metadataUnredacted({ captured_at: capturedAt })) return recipeUnavailable("recipe_metadata_redacted");
274
+ const content = JSON.stringify({ schema: VIEW_CONFIG.recipe.schema, run: producer.run, run_instance_id: producer.runInstanceId,
275
+ captured_at: capturedAt, coverage: RECIPE_VIEW_COVERAGE, ...fields }, null, 2) + "\n";
276
+ if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_RECIPE_MAX_BYTES) return recipeUnavailable("recipe_view_too_large");
277
+ return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged }) : recipeUnavailable("recipe_metadata_changed");
278
+ }
279
+
280
+ export interface RunRecipeContextView extends RecipeProjection {
281
+ readonly schema: "run-context-recipe-v1"; readonly run: string; readonly run_instance_id: string;
282
+ readonly captured_at: string; readonly coverage: typeof RECIPE_VIEW_COVERAGE;
283
+ }
284
+ export type RunRecipeContextViewReading =
285
+ | { readonly state: "available"; readonly basis: "original_producer_projection_match"; readonly identity: Readonly<RunContextIndexIdentity>;
286
+ readonly path: string; readonly media_type: "application/json"; readonly bytes_total: number; readonly mutable: true;
287
+ readonly view: Readonly<RunRecipeContextView>; readonly unchanged: () => boolean }
288
+ | { readonly state: "unavailable"; readonly reason: "admission_invalid" | "admission_unavailable" | "identity_redacted" | "identity_unavailable" | "identity_mismatch" |
289
+ "observation_changed" | "index_unavailable" | "recipe_unavailable" | "view_unavailable" | "view_invalid" | "view_metadata_redacted" | "view_mismatch" | "response_too_large" };
290
+ type RecipeReadUnavailable = Extract<RunRecipeContextViewReading, { state: "unavailable" }>;
291
+ function recipeReadUnavailable(reason: RecipeReadUnavailable["reason"]): Readonly<RecipeReadUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
292
+ type SavedViewSourceRefusal = "identity_unavailable" | "identity_mismatch" | "observation_changed" |
293
+ "index_unavailable" | "role_unavailable" | "view_unavailable" | "view_invalid";
294
+ /** Checked-source observation only; each private caller already owns its original projection/publication admission. */
295
+ function readSavedContextViewSource(stateDir: string, identity: RunContextIndexIdentity, role: "recipe" | "trace" | "control" | "execution" | "result"):
296
+ | { state: "available"; identity: Readonly<RunContextIndexIdentity>; path: string; text: string; bytes_total: number; unchanged: () => boolean }
297
+ | { state: "unavailable"; reason: SavedViewSourceRefusal } {
298
+ const owner = readRunContextIdentity(stateDir, identity);
299
+ if (owner.state !== "available") return owner;
300
+ let reason: SavedViewSourceRefusal = "index_unavailable";
301
+ try {
302
+ const root = owner.local_path, indexPath = join(root, RUN_CONTEXT_INDEX_FILE), index = optionalStamp(indexPath);
303
+ if (!index?.isFile()) return { state: "unavailable", reason: "index_unavailable" };
304
+ const admitted = readRunContextIndex(root, owner.identity);
305
+ if (admitted.state !== "available") return { state: "unavailable", reason: admitted.reason === "observation_changed" ? "observation_changed" : "index_unavailable" };
306
+ const entry = admitted.index.entries[role], config = VIEW_CONFIG[role];
307
+ if (entry.availability !== "available" || entry.mutable !== true || !isDeepStrictEqual(entry.coverage, config.coverage)) return { state: "unavailable", reason: "role_unavailable" };
308
+ reason = "view_unavailable";
309
+ const evidencePath = join(root, "evidence"), evidence = lstatSync(evidencePath, { bigint: true });
310
+ if (!evidence.isDirectory() || evidence.isSymbolicLink()) return { state: "unavailable", reason: "view_unavailable" };
311
+ const path = runContextExportPath(role), viewPath = join(root, path);
312
+ const captured = readFixedSourceText(viewPath, config.maxBytes, "Run " + role + " saved view");
313
+ const unchanged = (): boolean => { try { return owner.unchanged() && sameFile(index, lstatSync(indexPath, { bigint: true })) &&
314
+ sameObject(evidence, lstatSync(evidencePath, { bigint: true })) && sameFile(captured.stamp, lstatSync(viewPath, { bigint: true })); } catch { return false; } };
315
+ if (!unchanged()) return { state: "unavailable", reason: "observation_changed" };
316
+ if (Number(captured.stamp.size) !== entry.bytes_total) return { state: "unavailable", reason: "view_invalid" };
317
+ return { state: "available", identity: owner.identity, path, text: captured.text, bytes_total: Number(captured.stamp.size), unchanged };
318
+ } catch { return { state: "unavailable", reason }; }
319
+ }
320
+ function freezeContextValue<T>(value: T): Readonly<T> {
321
+ if (value && typeof value === "object") { Object.values(value).forEach(freezeContextValue); Object.freeze(value); }
322
+ return value;
323
+ }
324
+
325
+ /** Kernel-local original data correspondence, not file-issuer authentication or work/caller/restart authority. */
326
+ export function readRunRecipeContextView(stateDir: string, producer: RunResultProducer): Readonly<RunRecipeContextViewReading> {
327
+ let original: Record<string, unknown> | undefined;
328
+ try { original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES); }
329
+ catch { return recipeReadUnavailable("admission_invalid"); }
330
+ if (!original) return recipeReadUnavailable("admission_unavailable");
331
+ if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
332
+ original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId) return recipeReadUnavailable("identity_mismatch");
333
+ const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
334
+ const metadataUnredacted = createContextIdentityPrivacyCheck(original);
335
+ if (!metadataUnredacted(identity)) return recipeReadUnavailable("identity_redacted");
336
+ let projected: ReturnType<typeof projectCapturedRecipe>;
337
+ try { projected = projectCapturedRecipe(original); } catch { return recipeReadUnavailable("admission_unavailable"); }
338
+ if (projected.status === "unavailable") return recipeReadUnavailable("admission_unavailable");
339
+ const { status: _status, ...expected } = projected;
340
+ const source = readSavedContextViewSource(stateDir, identity, "recipe");
341
+ if (source.state !== "available") return recipeReadUnavailable(source.reason === "role_unavailable" ? "recipe_unavailable" : source.reason);
342
+ try {
343
+ const view = record(JSON.parse(source.text));
344
+ if (!view || !contextFields(view, ["schema", "run", "run_instance_id", "captured_at", "coverage", "capture", "recipe", "stored_clock"]) ||
345
+ view.schema !== VIEW_CONFIG.recipe.schema || view.run !== source.identity.run || view.run_instance_id !== source.identity.run_instance_id ||
346
+ !contextCoverage(view.coverage, true) || typeof view.captured_at !== "string" || view.captured_at.length > 32 ||
347
+ !Number.isFinite(Date.parse(view.captured_at)) || new Date(view.captured_at).toISOString() !== view.captured_at) return recipeReadUnavailable("view_invalid");
348
+ const { schema: _schema, run: _run, run_instance_id: _generation, captured_at: _capturedAt, coverage: _coverage, ...fields } = view;
349
+ if (!isDeepStrictEqual(fields, expected)) return recipeReadUnavailable("view_mismatch");
350
+ if (!metadataUnredacted({ captured_at: view.captured_at })) return recipeReadUnavailable("view_metadata_redacted");
351
+ const result: Extract<RunRecipeContextViewReading, { state: "available" }> = Object.freeze({ state: "available", basis: "original_producer_projection_match",
352
+ identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true,
353
+ view: freezeContextValue(view as unknown as RunRecipeContextView), unchanged: source.unchanged });
354
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_RECIPE_MAX_BYTES) return recipeReadUnavailable("response_too_large");
355
+ return source.unchanged() ? result : recipeReadUnavailable("observation_changed");
356
+ } catch { return recipeReadUnavailable("view_invalid"); }
357
+ }
358
+
359
+ export interface RunControlContextView {
360
+ readonly schema: "run-context-control-v1"; readonly run: string; readonly run_instance_id: string; readonly captured_at: string;
361
+ readonly run_status: string; readonly coverage: typeof CONTROL_VIEW_COVERAGE;
362
+ readonly capacity: ReturnType<typeof computeRunControlCapacity>; readonly controls: readonly ProjectedRunControl[];
363
+ readonly controls_omitted: number; readonly foreign_controls_omitted: number;
364
+ }
365
+ export type RunControlContextViewReading =
366
+ | { readonly state: "available"; readonly basis: "original_control_publication_match"; readonly identity: Readonly<RunContextIndexIdentity>;
367
+ readonly path: string; readonly media_type: "application/json"; readonly bytes_total: number; readonly mutable: true;
368
+ readonly view: Readonly<RunControlContextView>; readonly unchanged: () => boolean }
369
+ | { readonly state: "unavailable"; readonly reason: "admission_invalid" | "admission_unavailable" | "publication_unavailable" | "identity_redacted" |
370
+ "identity_unavailable" | "identity_mismatch" | "observation_changed" | "index_unavailable" | "control_unavailable" |
371
+ "view_unavailable" | "view_invalid" | "view_metadata_redacted" | "view_mismatch" | "response_too_large" };
372
+ type ControlReadUnavailable = Extract<RunControlContextViewReading, { state: "unavailable" }>;
373
+ function controlReadUnavailable(reason: ControlReadUnavailable["reason"]): Readonly<ControlReadUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
374
+
375
+ /** Recorded Control data only; neither current readiness/capacity nor authority to admit, dispatch or stop work. */
376
+ export function readRunControlContextView(stateDir: string, producer: RunResultProducer): Readonly<RunControlContextViewReading> {
377
+ let original: Record<string, unknown> | undefined;
378
+ try { original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES); }
379
+ catch { return controlReadUnavailable("admission_invalid"); }
380
+ if (!original) return controlReadUnavailable("admission_unavailable");
381
+ if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
382
+ original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId) return controlReadUnavailable("identity_mismatch");
383
+ const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
384
+ const metadataUnredacted = createContextIdentityPrivacyCheck(original);
385
+ if (!metadataUnredacted(identity)) return controlReadUnavailable("identity_redacted");
386
+ const publication = controlPublications.get(producer);
387
+ if (!publication) return controlReadUnavailable("publication_unavailable");
388
+ if (publication.root !== stateDir) return controlReadUnavailable("identity_mismatch");
389
+ const source = readSavedContextViewSource(stateDir, identity, "control");
390
+ if (source.state !== "available") return controlReadUnavailable(source.reason === "role_unavailable" ? "control_unavailable" : source.reason);
391
+ const unchanged = (): boolean => controlPublications.get(producer) === publication && source.unchanged();
392
+ if (!unchanged()) return controlReadUnavailable("observation_changed");
393
+ if (source.text !== publication.content) return controlReadUnavailable("view_mismatch");
394
+ try {
395
+ const view = JSON.parse(source.text) as RunControlContextView;
396
+ if (!metadataUnredacted({ captured_at: view.captured_at })) return controlReadUnavailable("view_metadata_redacted");
397
+ freezeContextValue(view);
398
+ const result: Extract<RunControlContextViewReading, { state: "available" }> = Object.freeze({ state: "available", basis: "original_control_publication_match",
399
+ identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true, view, unchanged });
400
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_CONTROL_MAX_BYTES) return controlReadUnavailable("response_too_large");
401
+ return unchanged() ? result : controlReadUnavailable("observation_changed");
402
+ } catch { return controlReadUnavailable("view_invalid"); }
403
+ }
404
+
405
+ export interface RunExecutionContextView {
406
+ readonly schema: "run-context-execution-v1"; readonly run: string; readonly run_instance_id: string; readonly captured_at: string;
407
+ readonly coverage: typeof EXECUTION_VIEW_COVERAGE; readonly source_scope: "execution_manifest";
408
+ readonly capture_references: "not_exported"; readonly capture_bodies: "not_read"; readonly session_bodies: "not_read";
409
+ readonly scope_closure: "not_asserted"; readonly execution: Readonly<RunExecutionView>;
410
+ }
411
+ export type RunExecutionContextViewReading =
412
+ | { readonly state: "available"; readonly basis: "original_execution_publication_match"; readonly identity: Readonly<RunContextIndexIdentity>;
413
+ readonly path: string; readonly media_type: "application/json"; readonly bytes_total: number; readonly mutable: true;
414
+ readonly view: Readonly<RunExecutionContextView>; readonly unchanged: () => boolean }
415
+ | { readonly state: "unavailable"; readonly reason: "admission_invalid" | "admission_unavailable" | "publication_unavailable" | "identity_redacted" |
416
+ "identity_unavailable" | "identity_mismatch" | "observation_changed" | "index_unavailable" | "execution_unavailable" |
417
+ "view_unavailable" | "view_invalid" | "view_metadata_redacted" | "view_mismatch" | "response_too_large" };
418
+ type ExecutionReadUnavailable = Extract<RunExecutionContextViewReading, { state: "unavailable" }>;
419
+ function executionReadUnavailable(reason: ExecutionReadUnavailable["reason"]): Readonly<ExecutionReadUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
420
+
421
+ /** Historical admitted metadata only: recorded references/exits/killed facts grant no body, lifecycle or native authority. */
422
+ export function readRunExecutionContextView(stateDir: string, producer: RunResultProducer): Readonly<RunExecutionContextViewReading> {
423
+ let original: Record<string, unknown> | undefined;
424
+ try { original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES); }
425
+ catch { return executionReadUnavailable("admission_invalid"); }
426
+ if (!original) return executionReadUnavailable("admission_unavailable");
427
+ if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
428
+ original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId) return executionReadUnavailable("identity_mismatch");
429
+ const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
430
+ const metadataUnredacted = createContextIdentityPrivacyCheck(original);
431
+ if (!metadataUnredacted(identity)) return executionReadUnavailable("identity_redacted");
432
+ const publication = executionPublications.get(producer);
433
+ if (!publication) return executionReadUnavailable("publication_unavailable");
434
+ if (publication.root !== stateDir) return executionReadUnavailable("identity_mismatch");
435
+ const source = readSavedContextViewSource(stateDir, identity, "execution");
436
+ if (source.state !== "available") return executionReadUnavailable(source.reason === "role_unavailable" ? "execution_unavailable" : source.reason);
437
+ const unchanged = (): boolean => executionPublications.get(producer) === publication && source.unchanged();
438
+ if (!unchanged()) return executionReadUnavailable("observation_changed");
439
+ if (source.text !== publication.content) return executionReadUnavailable("view_mismatch");
440
+ try {
441
+ const view = JSON.parse(source.text) as RunExecutionContextView;
442
+ if (!metadataUnredacted({ captured_at: view.captured_at })) return executionReadUnavailable("view_metadata_redacted");
443
+ freezeContextValue(view);
444
+ const result: Extract<RunExecutionContextViewReading, { state: "available" }> = Object.freeze({ state: "available", basis: "original_execution_publication_match",
445
+ identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true, view, unchanged });
446
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_EXECUTION_MAX_BYTES) return executionReadUnavailable("response_too_large");
447
+ return unchanged() ? result : executionReadUnavailable("observation_changed");
448
+ } catch { return executionReadUnavailable("view_invalid"); }
449
+ }
450
+
451
+ export interface RunResultContextView {
452
+ readonly schema: "run-context-result-v1"; readonly run: string; readonly run_instance_id: string; readonly captured_at: string;
453
+ readonly coverage: typeof RESULT_VIEW_COVERAGE; readonly content_scope: "canonical_outcome_metadata"; readonly primary_text: "not_included";
454
+ readonly diagnostics: Readonly<{ state: "not_recorded" }> | Readonly<{ state: "available";
455
+ source: Readonly<{ path: string; run_instance_id: string; sha256: string }>; bytes_total: number;
456
+ fields: Readonly<Partial<Record<typeof RESULT_DIAGNOSTIC_FIELDS[number], Omit<RunResultDiagnosticExcerpt, "text">>>> }>;
457
+ readonly outcome: Readonly<{ code: number; recorded_at: string; command?: string; error?: string; failure_reason?: string;
458
+ producer_flags?: Readonly<{ reported_killed?: boolean; reported_truncated?: boolean }> }> &
459
+ (Readonly<{ acceptance: "natural"; terminal_status: "done" | "failed" }> | Readonly<{ acceptance: "capture_only" }>);
460
+ }
461
+ export type RunResultContextViewReading =
462
+ | { readonly state: "available"; readonly basis: "original_result_publication_match"; readonly identity: Readonly<RunContextIndexIdentity>;
463
+ readonly path: string; readonly media_type: "application/json"; readonly bytes_total: number; readonly mutable: true;
464
+ readonly view: Readonly<RunResultContextView>; readonly unchanged: () => boolean }
465
+ | { readonly state: "unavailable"; readonly reason: "admission_invalid" | "admission_unavailable" | "publication_unavailable" | "identity_redacted" |
466
+ "identity_unavailable" | "identity_mismatch" | "observation_changed" | "index_unavailable" | "result_unavailable" |
467
+ "view_unavailable" | "view_invalid" | "view_metadata_redacted" | "view_mismatch" | "response_too_large" };
468
+ type ResultReadUnavailable = Extract<RunResultContextViewReading, { state: "unavailable" }>;
469
+ function resultReadUnavailable(reason: ResultReadUnavailable["reason"]): Readonly<ResultReadUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
470
+
471
+ /** Original successful Result-view data only, never current outcome/source/body, active terminal or cleanup authority. */
472
+ export function readRunResultContextView(stateDir: string, producer: RunResultProducer): Readonly<RunResultContextViewReading> {
473
+ let original: Record<string, unknown> | undefined;
474
+ try { original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES); }
475
+ catch { return resultReadUnavailable("admission_invalid"); }
476
+ if (!original) return resultReadUnavailable("admission_unavailable");
477
+ if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
478
+ original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId) return resultReadUnavailable("identity_mismatch");
479
+ const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
480
+ const metadataUnredacted = createContextIdentityPrivacyCheck(original);
481
+ if (!metadataUnredacted(identity)) return resultReadUnavailable("identity_redacted");
482
+ const publication = resultPublications.get(producer);
483
+ if (!publication) return resultReadUnavailable("publication_unavailable");
484
+ if (publication.root !== stateDir) return resultReadUnavailable("identity_mismatch");
485
+ const source = readSavedContextViewSource(stateDir, identity, "result");
486
+ if (source.state !== "available") return resultReadUnavailable(source.reason === "role_unavailable" ? "result_unavailable" : source.reason);
487
+ const unchanged = (): boolean => resultPublications.get(producer) === publication && source.unchanged();
488
+ if (!unchanged()) return resultReadUnavailable("observation_changed");
489
+ if (source.text !== publication.content) return resultReadUnavailable("view_mismatch");
490
+ try {
491
+ const view = JSON.parse(source.text) as RunResultContextView;
492
+ if (!metadataUnredacted({ captured_at: view.captured_at })) return resultReadUnavailable("view_metadata_redacted");
493
+ freezeContextValue(view);
494
+ const result: Extract<RunResultContextViewReading, { state: "available" }> = Object.freeze({ state: "available", basis: "original_result_publication_match",
495
+ identity: source.identity, path: source.path, media_type: "application/json", bytes_total: source.bytes_total, mutable: true, view, unchanged });
496
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_RESULT_MAX_BYTES) return resultReadUnavailable("response_too_large");
497
+ return unchanged() ? result : resultReadUnavailable("observation_changed");
498
+ } catch { return resultReadUnavailable("view_invalid"); }
499
+ }
500
+
501
+ export interface RunTraceContextView {
502
+ readonly header: {
503
+ readonly schema: "run-context-trace-v1"; readonly run: string; readonly run_instance_id: string; readonly captured_at: string;
504
+ readonly source_scope: "canonical_journal"; readonly order: "newest_first"; readonly coverage: typeof TRACE_VIEW_COVERAGE;
505
+ readonly journal: RunTraceSummary; readonly read_omitted_events: number; readonly events_total: number;
506
+ readonly events_shown: number; readonly events_omitted: number;
507
+ };
508
+ readonly items: readonly TraceItem[];
509
+ }
510
+ export type RunTraceContextViewReading =
511
+ | { readonly state: "available"; readonly basis: "original_trace_publication_match"; readonly identity: Readonly<RunContextIndexIdentity>;
512
+ readonly path: string; readonly media_type: "application/x-ndjson"; readonly bytes_total: number; readonly mutable: true;
513
+ readonly view: Readonly<RunTraceContextView>; readonly unchanged: () => boolean }
514
+ | { readonly state: "unavailable"; readonly reason: "admission_invalid" | "admission_unavailable" | "publication_unavailable" | "identity_redacted" |
515
+ "identity_unavailable" | "identity_mismatch" | "observation_changed" | "index_unavailable" | "trace_unavailable" |
516
+ "view_unavailable" | "view_invalid" | "view_metadata_redacted" | "view_mismatch" | "response_too_large" };
517
+ type TraceReadUnavailable = Extract<RunTraceContextViewReading, { state: "unavailable" }>;
518
+ function traceReadUnavailable(reason: TraceReadUnavailable["reason"]): Readonly<TraceReadUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
519
+
520
+ /** Reads original successful publication data, never a live journal projection or a reconstructed binding. */
521
+ export function readRunTraceContextView(stateDir: string, producer: RunResultProducer): Readonly<RunTraceContextViewReading> {
522
+ let original: Record<string, unknown> | undefined;
523
+ try { original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES); }
524
+ catch { return traceReadUnavailable("admission_invalid"); }
525
+ if (!original) return traceReadUnavailable("admission_unavailable");
526
+ if (typeof stateDir !== "string" || !isAbsolute(stateDir) || stateDir !== original.state_dir || original.run !== producer.run ||
527
+ original.run_instance_id !== producer.runInstanceId || original.ownerId !== producer.ownerId) return traceReadUnavailable("identity_mismatch");
528
+ const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) };
529
+ const metadataUnredacted = createContextIdentityPrivacyCheck(original);
530
+ if (!metadataUnredacted(identity)) return traceReadUnavailable("identity_redacted");
531
+ const publication = tracePublications.get(producer);
532
+ if (!publication) return traceReadUnavailable("publication_unavailable");
533
+ if (publication.root !== stateDir) return traceReadUnavailable("identity_mismatch");
534
+ const source = readSavedContextViewSource(stateDir, identity, "trace");
535
+ if (source.state !== "available") return traceReadUnavailable(source.reason === "role_unavailable" ? "trace_unavailable" : source.reason);
536
+ const unchanged = (): boolean => tracePublications.get(producer) === publication && source.unchanged();
537
+ if (!unchanged()) return traceReadUnavailable("observation_changed");
538
+ if (source.text !== publication.content) return traceReadUnavailable("view_mismatch");
539
+ try {
540
+ // Exact canonical JSONL binding precedes parse/freeze. A schema flag grants no authority.
541
+ const [header, ...items] = source.text.trimEnd().split("\n").map(line => JSON.parse(line));
542
+ if (!metadataUnredacted({ captured_at: header.captured_at })) return traceReadUnavailable("view_metadata_redacted");
543
+ const view = freezeContextValue({ header, items } as RunTraceContextView);
544
+ const result: Extract<RunTraceContextViewReading, { state: "available" }> = Object.freeze({ state: "available", basis: "original_trace_publication_match",
545
+ identity: source.identity, path: source.path, media_type: "application/x-ndjson", bytes_total: source.bytes_total, mutable: true, view, unchanged });
546
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_TRACE_MAX_BYTES) return traceReadUnavailable("response_too_large");
547
+ return unchanged() ? result : traceReadUnavailable("observation_changed");
548
+ } catch { return traceReadUnavailable("view_invalid"); }
549
+ }
550
+
551
+ function captureTrace(stateDir: string, producer: RunResultProducer): TraceUnavailable | TraceCapture {
552
+ const root = assertOwnedRunStateDirectory(stateDir, producer.run);
553
+ const path = runTraceFile(root);
554
+ const before = optionalStamp(path);
555
+ if (!before) return traceUnavailable("trace_journal_absent");
556
+ if (!before.isFile() || before.size > BigInt(TRACE_JOURNAL_MAX_BYTES)) return traceUnavailable("trace_journal_unavailable");
557
+ const metadata = readContextMetadata(root, producer);
558
+ if (metadata.state === "unavailable") return traceUnavailable(metadata.reason === "changed" ? "trace_metadata_changed" : "trace_metadata_unavailable");
559
+ const metadataUnchanged = captureContextMetadataFence(metadata, producer);
560
+ if (!metadataUnchanged) return traceUnavailable("trace_metadata_changed");
561
+ // Journal contents stay current; their Recipe privacy cannot be renewed from live metadata.
562
+ const recipe = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
563
+ if (!recipe) return traceUnavailable("trace_admission_unavailable");
564
+ const identityUnchanged = createContextIdentityPrivacyCheck(recipe);
565
+ if (!identityUnchanged({ run: producer.run, run_instance_id: producer.runInstanceId })) return traceUnavailable("trace_identity_redacted");
566
+ const journal = readRunTraceJournal(root);
567
+ const after = optionalStamp(path);
568
+ const metaAfter = optionalStamp(metadata.path);
569
+ if (!after || !sameFile(before, after)) return traceUnavailable("trace_journal_changed");
570
+ if (!metaAfter || !sameFile(metadata.stamp, metaAfter) || !metadataUnchanged()) return traceUnavailable("trace_metadata_changed");
571
+ if (journal.diagnostics.length || journal.omittedPrefixBytes > 0) return traceUnavailable("trace_journal_invalid");
572
+ // Source identity/time and routing tokens stay exact; omit unsafe whole items in the existing bounded source window.
573
+ const items = projectRunTraceJournal(journal, { limit: DEFAULT_INSPECT_LINES, recipe, redactEvidence: createRunRecipeBodyRedactor(recipe) })
574
+ .filter(item => identityUnchanged({ id: item.id, ts: item.ts, kind: item.kind }));
575
+ const capturedAt = new Date().toISOString();
576
+ if (!identityUnchanged({ captured_at: capturedAt })) return traceUnavailable("trace_metadata_redacted");
577
+ const summary = summarizeRunTraceJournal(journal);
578
+ const encode = () => [JSON.stringify({ schema: VIEW_CONFIG.trace.schema, run: producer.run,
579
+ run_instance_id: producer.runInstanceId, captured_at: capturedAt, source_scope: "canonical_journal", order: "newest_first",
580
+ coverage: TRACE_VIEW_COVERAGE, journal: summary, read_omitted_events: journal.omittedEvents,
581
+ events_total: journal.events.length, events_shown: items.length, events_omitted: journal.events.length - items.length }),
582
+ ...items.map(item => JSON.stringify(item))].join("\n") + "\n";
583
+ let content = encode();
584
+ while (items.length && Buffer.byteLength(content, "utf8") > RUN_CONTEXT_TRACE_MAX_BYTES) { items.pop(); content = encode(); }
585
+ if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_TRACE_MAX_BYTES) return traceUnavailable("trace_journal_unavailable");
586
+ const unchanged = (): boolean => {
587
+ try { return metadataUnchanged() && sameFile(after, lstatSync(path, { bigint: true })); }
588
+ catch { return false; }
589
+ };
590
+ if (!metadataUnchanged()) return traceUnavailable("trace_metadata_changed");
591
+ return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root }) : traceUnavailable("trace_journal_changed");
592
+ }
593
+
594
+ function captureResult(stateDir: string, producer: RunResultProducer): ResultUnavailable | ResultCapture {
595
+ const metadata = readContextMetadata(stateDir, producer);
596
+ if (metadata.state === "unavailable") return resultUnavailable(metadata.reason === "changed" ? "result_changed" : "result_metadata_unavailable");
597
+ const captured = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
598
+ if (!captured) return resultUnavailable("result_metadata_unavailable");
599
+ const metadataUnredacted = createContextIdentityPrivacyCheck(captured);
600
+ if (!metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId,
601
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) })) return resultUnavailable("result_identity_redacted");
602
+ const path = join(metadata.root, "result.json");
603
+ const before = optionalStamp(path);
604
+ if (!before) return resultUnavailable("result_absent");
605
+ if (!before.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES)) return resultUnavailable("result_unavailable");
606
+ const snapshot = readRunNaturalOutcomeSnapshot(metadata.root, metadata.meta, resultPath => {
607
+ const read = readJsonFileResilient<unknown>(resultPath, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES });
608
+ return read.diagnostics.length ? undefined : read.value;
609
+ });
610
+ const after = optionalStamp(path);
611
+ const metaAfter = optionalStamp(metadata.path);
612
+ if (!after || !sameFile(before, after) || !metaAfter || !sameFile(metadata.stamp, metaAfter)) return resultUnavailable("result_changed");
613
+ if (!snapshot.result || (snapshot.outcome.state !== "accepted" && snapshot.outcome.state !== "captured")) return resultUnavailable("result_invalid");
614
+ const result = snapshot.result;
615
+ if (!metadataUnredacted({ recorded_at: result.completedAt })) return resultUnavailable("result_metadata_redacted");
616
+ const projected = projectDiagnosticMetadata(producer, result, createRunRecipeTextRedactor(captured));
617
+ if (projected.state === "unavailable") return resultUnavailable(projected.reason);
618
+ const { details, sourceContent } = projected; const hasDetails = Object.keys(details).length > 0;
619
+ const sourcePath = resultDiagnosticsPath(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId, ownerId: producer.ownerId });
620
+ const source = hasDetails ? { path: sourcePath, run_instance_id: producer.runInstanceId,
621
+ sha256: createHash("sha256").update(sourceContent).digest("hex") } : undefined;
622
+ if (source && !metadataUnredacted(source)) return resultUnavailable("result_metadata_redacted");
623
+ const evidence: Record<string, string> = {}; const fields: Record<string, unknown> = {};
624
+ for (const [key, text] of Object.entries(details)) {
625
+ const { text: preview, ...counts } = diagnosticExcerpt(text);
626
+ evidence[key] = preview; fields[key] = counts;
627
+ }
628
+ const diagnostics = hasDetails ? { state: "available", source, bytes_total: Buffer.byteLength(sourceContent, "utf8"), fields }
629
+ : { state: "not_recorded" };
630
+ const flags = { ...(typeof result.killed === "boolean" ? { reported_killed: result.killed } : {}),
631
+ ...(typeof result.truncated === "boolean" ? { reported_truncated: result.truncated } : {}) };
632
+ const capturedAt = new Date().toISOString();
633
+ if (!metadataUnredacted({ captured_at: capturedAt })) return resultUnavailable("result_metadata_redacted");
634
+ const content = JSON.stringify({ schema: VIEW_CONFIG.result.schema, run: producer.run, run_instance_id: producer.runInstanceId,
635
+ captured_at: capturedAt, coverage: RESULT_VIEW_COVERAGE, content_scope: "canonical_outcome_metadata", primary_text: "not_included", diagnostics,
636
+ outcome: { acceptance: snapshot.outcome.state === "accepted" ? "natural" : "capture_only",
637
+ ...(snapshot.outcome.state === "accepted" ? { terminal_status: snapshot.outcome.status } : {}),
638
+ code: result.code, recorded_at: result.completedAt, ...evidence,
639
+ ...(Object.keys(flags).length ? { producer_flags: flags } : {}) } }, null, 2) + "\n";
640
+ if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_RESULT_MAX_BYTES) return resultUnavailable("result_view_too_large");
641
+ const resultUnchanged = (): boolean => { try { return sameFile(after, lstatSync(path, { bigint: true })) &&
642
+ sameFile(metadata.stamp, lstatSync(metadata.path, { bigint: true })); } catch { return false; } };
643
+ if (!resultUnchanged()) return resultUnavailable("result_changed");
644
+ const retained = hasDetails ? retainContextSource(metadata, sourcePath, sourceContent, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics") : undefined;
645
+ const unchanged = (): boolean => resultUnchanged() && (retained?.unchanged() ?? true);
646
+ if (!unchanged()) throw new ContextExportBindingRefusal("Run result diagnostics publication binding changed.");
647
+ return Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root });
648
+ }
649
+
650
+ function projectDiagnosticMetadata(producer: RunResultProducer, result: Partial<Record<typeof RESULT_DIAGNOSTIC_FIELDS[number], unknown>>, redact: (text: string) => string) {
651
+ const details = Object.fromEntries(RESULT_DIAGNOSTIC_FIELDS.flatMap(key => typeof result[key] === "string" ? [[key, redact(result[key] as string)]] : []));
652
+ if (Object.values(details).some(text => Buffer.from(text, "utf8").toString("utf8") !== text)) return { state: "unavailable", reason: "result_diagnostics_invalid" } as const;
653
+ const sourceContent = JSON.stringify({ schema: RUN_RESULT_DIAGNOSTICS_PROTOCOL, run: producer.run, run_instance_id: producer.runInstanceId,
654
+ content_scope: "canonical_outcome_diagnostic_strings", privacy_scope: "producer_admission", primary_text: "not_included",
655
+ coverage: { truncated: false, redacted: true }, fields: details }, null, 2) + "\n";
656
+ if (Buffer.byteLength(sourceContent, "utf8") > RUN_RESULT_DIAGNOSTICS_MAX_BYTES) return { state: "unavailable", reason: "result_diagnostics_too_large" } as const;
657
+ return { state: "available", details, sourceContent } as const;
658
+ }
659
+
660
+ export interface RunResultDiagnosticsPublisher {
661
+ readonly publish: (admission: RunResultDiagnosticsAdmission) => Readonly<RunResultDiagnosticsBinding>;
662
+ readonly readFailure: () => unknown;
663
+ }
664
+ function diagnosticsUnavailable(reason: string): Readonly<RunResultDiagnosticsBinding> {
665
+ return Object.freeze({ result_diagnostics: null, result_diagnostics_unavailable: reason });
666
+ }
667
+
668
+ /** Capture privacy before work. Source I/O occurs only in the genuine synchronous first-result callback, not initialization. */
669
+ export function createRunResultDiagnosticsPublisher(stateDir: string, producer: RunResultProducer): Readonly<RunResultDiagnosticsPublisher> {
670
+ const captured = withRunProducerEvidence(stateDir, producer, () => readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES));
671
+ let redact: ((text: string) => string) | undefined; let metadataUnredacted: ReturnType<typeof createContextIdentityPrivacyCheck> | undefined;
672
+ let identityUnchanged = false; let attempted = false; let failure: unknown;
673
+ try { if (captured) {
674
+ metadataUnredacted = createContextIdentityPrivacyCheck(captured);
675
+ identityUnchanged = metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId,
676
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
677
+ redact = createRunRecipeTextRedactor(captured);
678
+ } } catch { /* No fixed privacy proof means explicit unavailable metadata. */ }
679
+ return Object.freeze({
680
+ publish: (admission: RunResultDiagnosticsAdmission): Readonly<RunResultDiagnosticsBinding> => {
681
+ try {
682
+ return withRunResultDiagnosticsAdmission(stateDir, producer, admission, (candidate: RunResultDiagnosticCandidate) => {
683
+ if (attempted) throw new ContextExportBindingRefusal("Run result diagnostics publication was already attempted.");
684
+ attempted = true;
685
+ if (!redact || !metadataUnredacted) return diagnosticsUnavailable("privacy_capture_unavailable");
686
+ if (!identityUnchanged) return diagnosticsUnavailable("identity_redacted");
687
+ try {
688
+ const projected = projectDiagnosticMetadata(producer, candidate, redact);
689
+ if (projected.state === "unavailable") return diagnosticsUnavailable(projected.reason);
690
+ const fields = RESULT_DIAGNOSTIC_FIELDS.filter(field => Object.hasOwn(projected.details, field));
691
+ if (!fields.length) return diagnosticsUnavailable("not_recorded");
692
+ const metadata = readContextMetadata(stateDir, producer);
693
+ if (metadata.state === "unavailable") {
694
+ if (metadata.reason === "changed" || metadata.reason === "invalid") throw new ContextExportBindingRefusal("Run result diagnostics publication binding changed.");
695
+ return diagnosticsUnavailable("capture_unavailable");
696
+ }
697
+ const path = resultDiagnosticsPath(metadata.root, { run: producer.run, run_instance_id: producer.runInstanceId, ownerId: producer.ownerId });
698
+ const source = { path, run_instance_id: producer.runInstanceId, sha256: createHash("sha256").update(projected.sourceContent).digest("hex") };
699
+ if (!metadataUnredacted(source)) return diagnosticsUnavailable("source_reference_redacted");
700
+ const descriptor = { result_diagnostics: { source, bytes_total: Buffer.byteLength(projected.sourceContent, "utf8"), fields } };
701
+ if (Buffer.byteLength(JSON.stringify(descriptor), "utf8") > RUN_DELIVERY_MODEL_MAX_BYTES) return diagnosticsUnavailable("descriptor_too_large");
702
+ let binding: Readonly<RunResultDiagnosticsBinding>;
703
+ try { binding = decodeRunResultDiagnosticsBinding(metadata.root, producer.runInstanceId, descriptor); }
704
+ catch (error) { throw new ContextExportBindingRefusal("Run result diagnostics descriptor binding is invalid.", { cause: error }); }
705
+ const retained = retainContextSource(metadata, path, projected.sourceContent, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics");
706
+ if (!retained.unchanged()) throw new ContextExportBindingRefusal("Run result diagnostics publication binding changed.");
707
+ return binding;
708
+ } catch (error) {
709
+ if (error instanceof ContextExportBindingRefusal || error instanceof PrimaryTextBindingRefusal) throw error;
710
+ failure = error; return diagnosticsUnavailable("publication_failed");
711
+ }
712
+ });
713
+ } catch (error) {
714
+ if (failure !== undefined && error !== failure) throw new AggregateError([failure, error], "Run result diagnostics authority changed after publication failure.");
715
+ throw error;
716
+ }
717
+ },
718
+ readFailure: () => failure,
719
+ });
720
+ }
721
+
722
+ type ResultDiagnosticField = typeof RESULT_DIAGNOSTIC_FIELDS[number];
723
+ export type RunResultDiagnosticsReading =
724
+ | { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>;
725
+ readonly recorded_acceptance: "natural" | "capture_only"; readonly field: ResultDiagnosticField;
726
+ readonly content_scope: "canonical_outcome_diagnostic_strings"; readonly privacy_scope: "producer_admission";
727
+ readonly primary_text: "not_included"; readonly redacted: true; readonly selection: "recorded_preview" | "byte_range";
728
+ readonly diagnostics: Readonly<{ source: Readonly<{ path: string; run_instance_id: string; sha256: string }>; bytes_total: number }>;
729
+ readonly output: Readonly<RunResultDiagnosticExcerpt>; readonly unchanged: () => boolean }
730
+ | { readonly state: "unavailable"; readonly reason: "selection_invalid" | "identity_unavailable" | "identity_mismatch" | "observation_changed" |
731
+ "index_unavailable" | "publication_unavailable" | "view_unavailable" | "view_invalid" | "diagnostics_unavailable" | "field_unavailable" |
732
+ "source_unavailable" | "source_invalid" | "range_invalid" | "range_too_large" };
733
+ type DiagnosticsReadUnavailable = Extract<RunResultDiagnosticsReading, { state: "unavailable" }>;
734
+ function diagnosticsReadUnavailable(reason: DiagnosticsReadUnavailable["reason"]): Readonly<DiagnosticsReadUnavailable> {
735
+ return Object.freeze({ state: "unavailable", reason });
736
+ }
737
+ function resultDiagnosticsPath(root: string, identity: RunContextIndexIdentity): string {
738
+ const key = createHash("sha256").update(JSON.stringify([identity.run, identity.run_instance_id, identity.ownerId ?? null])).digest("hex");
739
+ return join(root, "evidence", `result-diagnostics-${key}.json`);
740
+ }
741
+ function diagnosticExcerpt(text: string, range?: Readonly<{ start: number; end: number }>, maxChars = RESULT_DIAGNOSTIC_PREVIEW_CHARS): Readonly<RunResultDiagnosticExcerpt> {
742
+ const bytes = Buffer.from(text, "utf8"); const preview = range ? bytes.subarray(range.start, range.end).toString("utf8")
743
+ : Array.from(text).slice(0, maxChars).join("");
744
+ const total = primaryChars(text); const shown = primaryChars(preview);
745
+ return Object.freeze({ text: preview, bytes_total: bytes.length,
746
+ shown_bytes: Object.freeze(range ? { start: range.start, end: range.end } : { start: 0, end: Buffer.byteLength(preview, "utf8") }),
747
+ chars_total: total, chars_shown: shown, chars_omitted: total - shown, truncated: shown < total });
748
+ }
749
+ /** Closed saved projection data only; caller owns source/hash/identity admission and exposure policy. */
750
+ function diagnosticSourceFields(text: string, identity: RunContextIndexIdentity): RunResultDiagnosticCandidate | undefined {
751
+ const saved = record(JSON.parse(text)); const fields = record(saved?.fields);
752
+ if (!saved || !contextFields(saved, ["schema", "run", "run_instance_id", "content_scope", "privacy_scope", "primary_text", "coverage", "fields"]) ||
753
+ saved.schema !== RUN_RESULT_DIAGNOSTICS_PROTOCOL || saved.run !== identity.run || saved.run_instance_id !== identity.run_instance_id ||
754
+ saved.content_scope !== "canonical_outcome_diagnostic_strings" || saved.privacy_scope !== "producer_admission" || saved.primary_text !== "not_included" ||
755
+ !contextCoverage(saved.coverage, false) || !fields || !Object.keys(fields).length || !contextFields(fields, RESULT_DIAGNOSTIC_FIELDS) ||
756
+ Object.values(fields).some(value => typeof value !== "string" || Buffer.from(value, "utf8").toString("utf8") !== value)) return undefined;
757
+ return Object.freeze(fields) as RunResultDiagnosticCandidate;
758
+ }
759
+
760
+ export type RunResultDiagnosticOutcomeReading =
761
+ | { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>; readonly binding: Readonly<RunResultDiagnosticsBinding>;
762
+ readonly excerpt: RunResultDiagnosticPacketExcerpt; readonly terminal_status: "done" | "failed"; readonly unchanged: () => boolean }
763
+ | { readonly state: "unavailable"; readonly reason: "selection_invalid" | "identity_unavailable" | "identity_mismatch" | "observation_changed" |
764
+ "result_unavailable" | "publication_unavailable" | "binding_invalid" | "diagnostics_unavailable" | "nonterminal_capture" | "outcome_mismatch" | "source_unavailable" | "source_invalid";
765
+ readonly result_diagnostics_unavailable?: string };
766
+
767
+ /** Eligible completion only: exact natural first-result metadata binding, never a view/index or raw-output fallback. */
768
+ export function readRunResultDiagnosticOutcome(stateDir: string, expected: unknown, status: unknown): Readonly<RunResultDiagnosticOutcomeReading> {
769
+ type Reason = Extract<RunResultDiagnosticOutcomeReading, { state: "unavailable" }>["reason"];
770
+ const unavailable = (reason: Reason, saved?: string): Readonly<RunResultDiagnosticOutcomeReading> => Object.freeze({ state: "unavailable", reason,
771
+ ...(saved === undefined ? {} : { result_diagnostics_unavailable: saved }) });
772
+ if (status !== "done" && status !== "failed") return unavailable("outcome_mismatch");
773
+ const request = contextReadIdentity(expected); if (!request) return unavailable("selection_invalid");
774
+ const owner = readRunContextIdentity(stateDir, request); if (owner.state !== "available") return unavailable(owner.reason);
775
+ let reason: Reason = "result_unavailable";
776
+ try {
777
+ const root = owner.local_path; const metadata = contextJson(join(root, "run.json")); const meta = metadata.value;
778
+ if (meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL || !Number.isSafeInteger(meta.pid) || Number(meta.pid) <= 0) return unavailable("publication_unavailable");
779
+ const resultPath = join(root, "result.json"); const canonical = contextJson(resultPath);
780
+ const snapshot = readRunNaturalOutcomeSnapshot(root, meta, () => canonical.value);
781
+ const frameUnchanged = (): boolean => { try { return owner.unchanged() && sameFile(metadata.stamp, lstatSync(join(root, "run.json"), { bigint: true })) &&
782
+ sameFile(canonical.stamp, lstatSync(resultPath, { bigint: true })); } catch { return false; } };
783
+ if (!frameUnchanged()) return unavailable("observation_changed");
784
+ if (snapshot.outcome.state === "captured") return unavailable("nonterminal_capture");
785
+ if (!snapshot.result || snapshot.outcome.state !== "accepted") return unavailable("result_unavailable");
786
+ if (snapshot.outcome.status !== status) return unavailable("outcome_mismatch");
787
+ if (snapshot.result.result_diagnostics_protocol !== RUN_RESULT_DIAGNOSTICS_PROTOCOL) return unavailable("publication_unavailable");
788
+ reason = "binding_invalid";
789
+ const binding = decodeRunResultDiagnosticsBinding(root, owner.identity.run_instance_id, snapshot.result); const reference = binding.result_diagnostics;
790
+ if (!reference) return frameUnchanged() ? unavailable("diagnostics_unavailable", binding.result_diagnostics_unavailable) : unavailable("observation_changed");
791
+ if (reference.source.path !== resultDiagnosticsPath(root, owner.identity) || reference.bytes_total > RUN_RESULT_DIAGNOSTICS_MAX_BYTES) return unavailable("binding_invalid");
792
+ reason = "source_unavailable";
793
+ const evidencePath = join(root, "evidence"); const evidence = lstatSync(evidencePath, { bigint: true });
794
+ if (!evidence.isDirectory() || evidence.isSymbolicLink()) return unavailable("source_unavailable");
795
+ const retained = readFixedSourceText(reference.source.path, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics source");
796
+ reason = "source_invalid";
797
+ if (Number(retained.stamp.size) !== reference.bytes_total || createHash("sha256").update(retained.text).digest("hex") !== reference.source.sha256) return unavailable("source_invalid");
798
+ const fields = diagnosticSourceFields(retained.text, owner.identity);
799
+ if (!fields || JSON.stringify(RESULT_DIAGNOSTIC_FIELDS.filter(key => Object.hasOwn(fields, key))) !== JSON.stringify(reference.fields)) return unavailable("source_invalid");
800
+ const field = (["error", "failure_reason", "command"] as const).find(key => Object.hasOwn(fields, key))!;
801
+ const excerpt = decodeRunResultDiagnosticExcerpt(reference, { field, ...diagnosticExcerpt(fields[field]!, undefined, RUN_RESULT_DIAGNOSTIC_EXCERPT_CHARS) });
802
+ const unchanged = (): boolean => { try { return frameUnchanged() && sameObject(evidence, lstatSync(evidencePath, { bigint: true })) &&
803
+ sameFile(retained.stamp, lstatSync(reference.source.path, { bigint: true })); } catch { return false; } };
804
+ return unchanged() ? Object.freeze({ state: "available", identity: owner.identity, binding, excerpt, terminal_status: snapshot.outcome.status, unchanged }) : unavailable("observation_changed");
805
+ } catch { return unavailable(reason); }
806
+ }
807
+
808
+ function diagnosticOutcome(value: unknown): Record<string, unknown> | undefined {
809
+ const outcome = record(value);
810
+ if (!outcome || !contextFields(outcome, ["acceptance", "terminal_status", "code", "recorded_at", "producer_flags", ...RESULT_DIAGNOSTIC_FIELDS]) ||
811
+ typeof outcome.code !== "number" || !Number.isSafeInteger(outcome.code) || typeof outcome.recorded_at !== "string" ||
812
+ !Number.isFinite(Date.parse(outcome.recorded_at)) || new Date(outcome.recorded_at).toISOString() !== outcome.recorded_at ||
813
+ (outcome.acceptance !== "natural" && outcome.acceptance !== "capture_only") ||
814
+ (outcome.acceptance === "natural" ? outcome.terminal_status !== (outcome.code === 0 ? "done" : "failed") : Object.hasOwn(outcome, "terminal_status"))) return undefined;
815
+ if (outcome.producer_flags !== undefined) {
816
+ const flags = record(outcome.producer_flags);
817
+ if (!flags || !Object.keys(flags).length || !contextFields(flags, ["reported_killed", "reported_truncated"]) ||
818
+ Object.values(flags).some(flag => typeof flag !== "boolean")) return undefined;
819
+ }
820
+ if (RESULT_DIAGNOSTIC_FIELDS.some(field => Object.hasOwn(outcome, field) && typeof outcome[field] !== "string")) return undefined;
821
+ return outcome;
822
+ }
823
+
824
+ /** Fixed saved diagnostic metadata only. The query selects one field and optional decoded-field UTF-8 byte range. */
825
+ export function readRunResultDiagnosticsSource(stateDir: string, expected: unknown, query: unknown): Readonly<RunResultDiagnosticsReading> {
826
+ const request = contextReadIdentity(expected); const selection = contextReadRequest(query);
827
+ if (!request || !selection || !contextFields(selection, ["field", "start", "end"]) ||
828
+ !RESULT_DIAGNOSTIC_FIELDS.includes(selection.field as ResultDiagnosticField)) return diagnosticsReadUnavailable("selection_invalid");
829
+ const field = selection.field as ResultDiagnosticField; let range: { start: number; end: number } | undefined;
830
+ if (Object.hasOwn(selection, "start") || Object.hasOwn(selection, "end")) {
831
+ if (!Number.isSafeInteger(selection.start) || !Number.isSafeInteger(selection.end) || Number(selection.start) < 0 ||
832
+ Number(selection.end) < Number(selection.start)) return diagnosticsReadUnavailable("selection_invalid");
833
+ range = { start: Number(selection.start), end: Number(selection.end) };
834
+ if (range.end - range.start > RUN_CONTEXT_RESULT_MAX_BYTES) return diagnosticsReadUnavailable("range_too_large");
835
+ }
836
+ const owner = readRunContextIdentity(stateDir, request);
837
+ if (owner.state !== "available") return diagnosticsReadUnavailable(owner.reason);
838
+ let reason: DiagnosticsReadUnavailable["reason"] = "index_unavailable";
839
+ try {
840
+ const root = owner.local_path; const indexPath = join(root, RUN_CONTEXT_INDEX_FILE); const index = optionalStamp(indexPath);
841
+ if (!index?.isFile()) return diagnosticsReadUnavailable("index_unavailable");
842
+ const admitted = readRunContextIndex(root, owner.identity);
843
+ if (admitted.state !== "available") return diagnosticsReadUnavailable(admitted.reason === "observation_changed" ? "observation_changed" : "index_unavailable");
844
+ const entry = admitted.index.entries.result;
845
+ if (entry.availability !== "available" || entry.mutable !== true || !contextCoverage(entry.coverage, true)) return diagnosticsReadUnavailable("publication_unavailable");
846
+ reason = "view_unavailable";
847
+ const evidencePath = join(root, "evidence"); const evidence = lstatSync(evidencePath, { bigint: true });
848
+ if (!evidence.isDirectory() || evidence.isSymbolicLink()) return diagnosticsReadUnavailable("view_unavailable");
849
+ const viewPath = join(root, runContextExportPath("result"));
850
+ const captured = readFixedSourceText(viewPath, RUN_CONTEXT_RESULT_MAX_BYTES, "Run result diagnostics view");
851
+ const frameUnchanged = (): boolean => { try { return owner.unchanged() && sameFile(index, lstatSync(indexPath, { bigint: true })) &&
852
+ sameObject(evidence, lstatSync(evidencePath, { bigint: true })) && sameFile(captured.stamp, lstatSync(viewPath, { bigint: true })); } catch { return false; } };
853
+ if (!frameUnchanged()) return diagnosticsReadUnavailable("observation_changed");
854
+ reason = "view_invalid";
855
+ const view = record(JSON.parse(captured.text));
856
+ if (!view || !contextFields(view, ["schema", "run", "run_instance_id", "captured_at", "coverage", "content_scope", "primary_text", "diagnostics", "outcome"]) ||
857
+ view.schema !== VIEW_CONFIG.result.schema || view.run !== owner.identity.run || view.run_instance_id !== owner.identity.run_instance_id ||
858
+ view.content_scope !== "canonical_outcome_metadata" || view.primary_text !== "not_included" || !contextCoverage(view.coverage, true) ||
859
+ Number(captured.stamp.size) !== entry.bytes_total || typeof view.captured_at !== "string" || !Number.isFinite(Date.parse(view.captured_at)) ||
860
+ new Date(view.captured_at).toISOString() !== view.captured_at) return diagnosticsReadUnavailable("view_invalid");
861
+ const outcome = diagnosticOutcome(view.outcome); if (!outcome) return diagnosticsReadUnavailable("view_invalid");
862
+ if (!Object.hasOwn(view, "diagnostics")) return diagnosticsReadUnavailable("publication_unavailable");
863
+ const descriptor = record(view.diagnostics); if (!descriptor) return diagnosticsReadUnavailable("view_invalid");
864
+ if (descriptor.state === "not_recorded") return contextFields(descriptor, ["state"]) &&
865
+ RESULT_DIAGNOSTIC_FIELDS.every(key => !Object.hasOwn(outcome, key)) ? diagnosticsReadUnavailable("diagnostics_unavailable") : diagnosticsReadUnavailable("view_invalid");
866
+ const source = record(descriptor.source); const previews = record(descriptor.fields);
867
+ if (!contextFields(descriptor, ["state", "source", "bytes_total", "fields"]) || descriptor.state !== "available" || !source ||
868
+ !contextFields(source, ["path", "run_instance_id", "sha256"]) || source.path !== resultDiagnosticsPath(root, owner.identity) ||
869
+ source.run_instance_id !== owner.identity.run_instance_id || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(source.sha256) ||
870
+ typeof descriptor.bytes_total !== "number" || !Number.isSafeInteger(descriptor.bytes_total) || descriptor.bytes_total <= 0 || descriptor.bytes_total > RUN_RESULT_DIAGNOSTICS_MAX_BYTES ||
871
+ !previews || !Object.keys(previews).length || !contextFields(previews, RESULT_DIAGNOSTIC_FIELDS)) return diagnosticsReadUnavailable("view_invalid");
872
+ reason = "source_unavailable";
873
+ const retained = readFixedSourceText(source.path as string, RUN_RESULT_DIAGNOSTICS_MAX_BYTES, "Run result diagnostics source");
874
+ reason = "source_invalid";
875
+ if (Number(retained.stamp.size) !== descriptor.bytes_total || createHash("sha256").update(retained.text).digest("hex") !== source.sha256) return diagnosticsReadUnavailable("source_invalid");
876
+ const fields = diagnosticSourceFields(retained.text, owner.identity);
877
+ if (!fields) return diagnosticsReadUnavailable("source_invalid");
878
+ const excerpts = new Map<ResultDiagnosticField, Readonly<RunResultDiagnosticExcerpt>>();
879
+ for (const key of RESULT_DIAGNOSTIC_FIELDS) {
880
+ if (!Object.hasOwn(fields, key)) {
881
+ if (Object.hasOwn(previews, key) || Object.hasOwn(outcome, key)) return diagnosticsReadUnavailable("source_invalid");
882
+ continue;
883
+ }
884
+ const text = fields[key]; if (typeof text !== "string" || Buffer.from(text, "utf8").toString("utf8") !== text) return diagnosticsReadUnavailable("source_invalid");
885
+ const excerpt = diagnosticExcerpt(text); const { text: preview, ...counts } = excerpt; const recorded = record(previews[key]);
886
+ const recordedRange = record(recorded?.shown_bytes); const { shown_bytes: bytes, ...scalarCounts } = counts;
887
+ if (!recorded || !contextFields(recorded, Object.keys(counts)) || !recordedRange || !contextFields(recordedRange, ["start", "end"]) ||
888
+ recordedRange.start !== bytes.start || recordedRange.end !== bytes.end || Object.entries(scalarCounts).some(([name, value]) => recorded[name] !== value) ||
889
+ outcome[key] !== preview) return diagnosticsReadUnavailable("source_invalid");
890
+ excerpts.set(key, excerpt);
891
+ }
892
+ const unchanged = (): boolean => { try { return frameUnchanged() && sameFile(retained.stamp, lstatSync(source.path as string, { bigint: true })); } catch { return false; } };
893
+ if (!unchanged()) return diagnosticsReadUnavailable("observation_changed");
894
+ let output = excerpts.get(field); if (!output) return diagnosticsReadUnavailable("field_unavailable");
895
+ if (range) {
896
+ const bytes = Buffer.from(fields[field] as string, "utf8");
897
+ if (range.end > bytes.length || !primaryBoundary(bytes, range.start) || !primaryBoundary(bytes, range.end)) return diagnosticsReadUnavailable("range_invalid");
898
+ output = diagnosticExcerpt(fields[field] as string, range);
899
+ }
900
+ const reading: Extract<RunResultDiagnosticsReading, { state: "available" }> = Object.freeze({ state: "available", identity: owner.identity,
901
+ recorded_acceptance: outcome.acceptance as "natural" | "capture_only", field, content_scope: "canonical_outcome_diagnostic_strings", privacy_scope: "producer_admission",
902
+ primary_text: "not_included", redacted: true, selection: range ? "byte_range" : "recorded_preview",
903
+ diagnostics: Object.freeze({ source: Object.freeze({ path: source.path as string, run_instance_id: source.run_instance_id as string, sha256: source.sha256 as string }), bytes_total: descriptor.bytes_total }),
904
+ output, unchanged });
905
+ if (Buffer.byteLength(JSON.stringify(reading)) > RUN_CONTEXT_RESULT_MAX_BYTES) return diagnosticsReadUnavailable("range_too_large");
906
+ return unchanged() ? reading : diagnosticsReadUnavailable("observation_changed");
907
+ } catch { return diagnosticsReadUnavailable(reason); }
908
+ }
909
+
910
+ // Cwd anchoring can amplify bounded input metadata; refuse before constructing/projecting that expansion.
911
+ function artifactReferenceBudget(artifacts: unknown, cwd: unknown): boolean {
912
+ const declarations = record(artifacts); if (!declarations) return true; // Validation still belongs to the reference owner.
913
+ const baseBytes = typeof cwd === "string" ? Buffer.byteLength(cwd) + (cwd.endsWith(sep) ? 0 : Buffer.byteLength(sep)) : 0;
914
+ let bytes = 0;
915
+ for (const value of Object.values(declarations)) {
916
+ const path = typeof value === "string" ? value : record(value)?.path;
917
+ if (typeof path !== "string") continue;
918
+ bytes += Buffer.byteLength(path) + (isAbsolute(path) ? 0 : baseBytes);
919
+ if (bytes > RUN_ARTIFACT_INVENTORY_MAX_BYTES) return false;
920
+ }
921
+ return true;
922
+ }
923
+
924
+ function captureArtifacts(stateDir: string, producer: RunResultProducer): ArtifactUnavailable | CapturedView {
925
+ const metadata = readContextMetadata(stateDir, producer);
926
+ if (metadata.state === "unavailable") return artifactUnavailable(metadata.reason === "changed" ? "artifact_metadata_changed" : "artifact_metadata_unavailable");
927
+ if (!artifactReferenceBudget(metadata.meta.artifacts, metadata.meta.cwd)) return artifactUnavailable("artifact_manifest_too_large");
928
+ const described = describeRunArtifactReferences(metadata.meta.artifacts, metadata.meta.cwd);
929
+ if (described.state === "unavailable") return artifactUnavailable(described.reason);
930
+ if (!described.references.length) return artifactUnavailable("artifact_declarations_absent");
931
+ const original = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
932
+ if (!original) return artifactUnavailable("artifact_admission_unavailable");
933
+ if (!createContextIdentityPrivacyCheck(original)({ run: producer.run, run_instance_id: producer.runInstanceId,
934
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) })) return artifactUnavailable("artifact_identity_redacted");
935
+ const redact = (value: string): string => redactSessionEvidenceValue(value, Number.MAX_SAFE_INTEGER) as string;
936
+ // Declared output paths are operational references: never turn a redacted path into a promised readable file.
937
+ const prepared = described.references.map(reference => ({ name: redact(reference.name),
938
+ path: redact(reference.path) === reference.path ? reference.path : null,
939
+ ...(reference.kind !== undefined ? { kind: redact(reference.kind) } : {}),
940
+ ...(reference.media_type !== undefined ? { media_type: redact(reference.media_type) } : {}),
941
+ ...(reference.required !== undefined ? { required: reference.required } : {}) }));
942
+ if (Buffer.byteLength(JSON.stringify(prepared), "utf8") > RUN_CONTEXT_ARTIFACT_MAX_BYTES) return artifactUnavailable("artifact_manifest_too_large");
943
+ const artifacts = prepared.map((entry, index) => {
944
+ if (entry.path === null) return { ...entry, availability: "unavailable", reason: "artifact_reference_redacted", mutable: true };
945
+ const observed = observeRunArtifactReference(described.references[index]);
946
+ return { ...entry, availability: observed.availability, mutable: true,
947
+ ...(observed.bytes_total !== undefined ? { bytes_total: observed.bytes_total } : {}),
948
+ ...(observed.reason ? { reason: observed.reason } : {}) };
949
+ });
950
+ const after = optionalStamp(metadata.path);
951
+ if (!after || !sameFile(metadata.stamp, after)) return artifactUnavailable("artifact_metadata_changed");
952
+ const summary = { declared: artifacts.length, available: 0, missing: 0, unavailable: 0 };
953
+ for (const artifact of artifacts) summary[artifact.availability as "available" | "missing" | "unavailable"] += 1;
954
+ const capturedAt = new Date().toISOString();
955
+ const content = JSON.stringify({ schema: VIEW_CONFIG.artifacts.schema, run: producer.run, run_instance_id: producer.runInstanceId,
956
+ captured_at: capturedAt, content_scope: "captured_artifact_references", contents: "not_read", production_provenance: "not_asserted",
957
+ coverage: ARTIFACT_VIEW_COVERAGE, summary, artifacts }, null, 2) + "\n";
958
+ if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_ARTIFACT_MAX_BYTES) return artifactUnavailable("artifact_manifest_too_large");
959
+ return Object.freeze({ status: "captured", captured_at: capturedAt, content });
960
+ }
961
+
962
+ function artifactInventoryPath(root: string, identity: RunContextIndexIdentity): string {
963
+ const key = createHash("sha256").update(JSON.stringify([identity.run, identity.run_instance_id, identity.ownerId ?? null])).digest("hex");
964
+ return join(root, "evidence", `artifact-inventory-${key}.json`);
965
+ }
966
+
967
+ /** Overflow source permission is fixed at genuine producer admission, not refreshed from live declarations. */
968
+ function retainArtifactInventoryView(stateDir: string, producer: RunResultProducer): CapturedView | undefined {
969
+ return withRunProducerEvidence(stateDir, producer, () => {
970
+ const captured = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
971
+ if (!captured || !createContextIdentityPrivacyCheck(captured)({ run: producer.run, run_instance_id: producer.runInstanceId,
972
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) })) return undefined;
973
+ const metadata = readContextMetadata(stateDir, producer);
974
+ if (metadata.state !== "available" ||
975
+ JSON.stringify([captured.artifacts, captured.cwd]) !== JSON.stringify([metadata.meta.artifacts, metadata.meta.cwd])) return undefined;
976
+ if (!artifactReferenceBudget(captured.artifacts, captured.cwd)) return undefined;
977
+ const described = describeRunArtifactReferences(captured.artifacts, captured.cwd);
978
+ if (described.state !== "available" || !described.references.length) return undefined;
979
+ const mask = capturedSecretMask(captured), withoutSecrets = capturedSecretMask(captured, "");
980
+ // Operational output references may be beneath Recipe origins; share secret masking, not origin stripping.
981
+ const redact = (text: string): string => mask(redactSessionEvidenceValue(text, Number.MAX_SAFE_INTEGER) as string);
982
+ const artifacts = described.references.map(reference => {
983
+ const withheld = withoutSecrets(reference.path) !== reference.path || redact(reference.path) !== reference.path;
984
+ return { name: redact(reference.name), path: withheld ? null : reference.path,
985
+ ...(reference.kind !== undefined ? { kind: redact(reference.kind) } : {}),
986
+ ...(reference.media_type !== undefined ? { media_type: redact(reference.media_type) } : {}),
987
+ ...(reference.required !== undefined ? { required: reference.required } : {}),
988
+ availability: "not_observed", ...(withheld ? { reason: "artifact_reference_redacted" } : {}) };
989
+ });
990
+ const coverage = Object.freeze({ truncated: false, redacted: true });
991
+ const summary = { declared: artifacts.length, references_disclosed: artifacts.filter(item => item.path !== null).length,
992
+ references_withheld: artifacts.filter(item => item.path === null).length };
993
+ const common = { run: producer.run, run_instance_id: producer.runInstanceId,
994
+ capture_scope: "producer_admission", contents: "not_read", availability: "not_observed", production_provenance: "not_asserted" };
995
+ const content = JSON.stringify({ schema: "run-artifact-inventory-v1", ...common,
996
+ content_scope: "captured_artifact_declarations", inventory_complete: true, coverage, summary, artifacts }, null, 2) + "\n";
997
+ const bytes = Buffer.byteLength(content);
998
+ if (bytes > RUN_ARTIFACT_INVENTORY_MAX_BYTES) return undefined;
999
+ const path = artifactInventoryPath(metadata.root,
1000
+ { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
1001
+ const inventory = { source: { path, run_instance_id: producer.runInstanceId, sha256: createHash("sha256").update(content).digest("hex") },
1002
+ bytes_total: bytes, declarations_total: artifacts.length };
1003
+ const capturedAt = new Date().toISOString(); const viewCoverage = Object.freeze({ truncated: true, redacted: true });
1004
+ const view = JSON.stringify({ schema: VIEW_CONFIG.artifacts.schema, ...common, captured_at: capturedAt,
1005
+ content_scope: "captured_artifact_inventory_reference", coverage: viewCoverage, summary,
1006
+ artifacts: [], artifacts_shown: 0, artifacts_omitted: artifacts.length, inventory }, null, 2) + "\n";
1007
+ if (Buffer.byteLength(view) > RUN_CONTEXT_ARTIFACT_MAX_BYTES) return undefined;
1008
+ const retained = retainContextSource(metadata, path, content, RUN_ARTIFACT_INVENTORY_MAX_BYTES, "Run artifact inventory");
1009
+ return Object.freeze({ status: "captured", captured_at: capturedAt, content: view, coverage: viewCoverage, unchanged: retained.unchanged });
1010
+ });
1011
+ }
1012
+
1013
+ export interface RunArtifactInventoryPublication {
1014
+ readonly snapshot: () => Readonly<RunArtifactInventoryBinding>;
1015
+ readonly readFailure: () => unknown;
1016
+ }
1017
+
1018
+ /** Kernel preparation, before work/result publication. This projects declarations only, never artifact targets. */
1019
+ export function prepareRunArtifactInventoryPublication(stateDir: string, producer: RunResultProducer): Readonly<RunArtifactInventoryPublication> {
1020
+ const metadata = withRunProducerEvidence(stateDir, producer, () => readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES));
1021
+ let binding: Readonly<RunArtifactInventoryBinding> = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "not_declared" });
1022
+ let unchanged: (() => boolean) | undefined; let failure: unknown;
1023
+ if (!metadata) binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "privacy_capture_unavailable" });
1024
+ else if (metadata.artifacts !== undefined && (!record(metadata.artifacts) || Object.keys(metadata.artifacts as object).length > 0)) {
1025
+ if (!createContextIdentityPrivacyCheck(metadata)({ run: producer.run, run_instance_id: producer.runInstanceId,
1026
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) })) return Object.freeze({
1027
+ snapshot: () => Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "identity_redacted" }), readFailure: () => undefined });
1028
+ try {
1029
+ const captured = retainArtifactInventoryView(stateDir, producer);
1030
+ if (!captured) binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "capture_unavailable" });
1031
+ else {
1032
+ const view = publishContextView(stateDir, producer, "artifacts", captured);
1033
+ publishContextIndex(stateDir, producer, "artifacts", view);
1034
+ const reading = readRunArtifactInventorySource(stateDir, { run: producer.run, run_instance_id: producer.runInstanceId,
1035
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }, { start: 0, end: 0 });
1036
+ binding = decodeRunArtifactInventoryBinding(stateDir, producer.runInstanceId, reading.state === "available"
1037
+ ? { artifact_inventory: reading.inventory } : { artifact_inventory: null, artifact_inventory_unavailable: reading.reason });
1038
+ if (reading.state === "available") unchanged = reading.unchanged;
1039
+ }
1040
+ } catch (error) {
1041
+ if (error instanceof ContextExportBindingRefusal || error instanceof PrimaryTextBindingRefusal) throw error;
1042
+ // Ordinary evidence I/O may be unavailable, but loss of genuine producer authority must still fail closed.
1043
+ try { withRunProducerEvidence(stateDir, producer, () => {}); }
1044
+ catch (authorityError) { throw new AggregateError([error, authorityError], "Run artifact inventory authority changed after publication failure."); }
1045
+ failure = error; binding = Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "publication_failed" });
1046
+ }
1047
+ }
1048
+ return Object.freeze({ snapshot: () => binding.artifact_inventory && !unchanged?.()
1049
+ ? Object.freeze({ artifact_inventory: null, artifact_inventory_unavailable: "observation_changed" }) : binding,
1050
+ readFailure: () => failure });
1051
+ }
1052
+
1053
+ export type RunResultArtifactInventoryReading =
1054
+ | { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>;
1055
+ readonly binding: Readonly<RunArtifactInventoryBinding>; readonly terminal_status: "done" | "failed"; readonly unchanged: () => boolean }
1056
+ | { readonly state: "unavailable"; readonly reason: Extract<RunArtifactInventoryReading, { state: "unavailable" }>["reason"] |
1057
+ "result_unavailable" | "binding_invalid" | "inventory_unavailable" | "nonterminal_capture"; readonly artifact_inventory_unavailable?: string };
1058
+
1059
+ /** Exact natural first-result binding plus retained-source admission; call only for eligible packet members, never ambient checks. */
1060
+ export function readRunResultArtifactInventory(stateDir: string, expected: unknown): Readonly<RunResultArtifactInventoryReading> {
1061
+ type Reason = Extract<RunResultArtifactInventoryReading, { state: "unavailable" }>["reason"];
1062
+ const unavailable = (reason: Reason, saved?: string): Readonly<RunResultArtifactInventoryReading> => Object.freeze({ state: "unavailable", reason,
1063
+ ...(saved === undefined ? {} : { artifact_inventory_unavailable: saved }) });
1064
+ const request = contextReadIdentity(expected); if (!request) return unavailable("selection_invalid");
1065
+ const owner = readRunContextIdentity(stateDir, request); if (owner.state !== "available") return unavailable(owner.reason);
1066
+ let reason: Reason = "result_unavailable";
1067
+ try {
1068
+ const root = owner.local_path; const metadata = contextJson(join(root, "run.json")); const meta = metadata.value;
1069
+ if (meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL || !Number.isSafeInteger(meta.pid) || Number(meta.pid) <= 0) return unavailable("publication_unavailable");
1070
+ const resultPath = join(root, "result.json"); const canonical = contextJson(resultPath);
1071
+ const snapshot = readRunNaturalOutcomeSnapshot(root, meta, () => canonical.value);
1072
+ const frameUnchanged = (): boolean => {
1073
+ try { return owner.unchanged() && sameFile(metadata.stamp, lstatSync(join(root, "run.json"), { bigint: true })) && sameFile(canonical.stamp, lstatSync(resultPath, { bigint: true })); }
1074
+ catch { return false; }
1075
+ };
1076
+ if (!frameUnchanged()) return unavailable("observation_changed");
1077
+ if (snapshot.outcome.state === "captured") return unavailable("nonterminal_capture");
1078
+ if (!snapshot.result || snapshot.outcome.state !== "accepted") return unavailable("result_unavailable");
1079
+ if (snapshot.result.artifact_inventory_protocol !== RUN_ARTIFACT_INVENTORY_PROTOCOL) return unavailable("publication_unavailable");
1080
+ reason = "binding_invalid";
1081
+ const binding = decodeRunArtifactInventoryBinding(root, owner.identity.run_instance_id, snapshot.result);
1082
+ if (!binding.artifact_inventory) return frameUnchanged() ? unavailable("inventory_unavailable", binding.artifact_inventory_unavailable) : unavailable("observation_changed");
1083
+ if (binding.artifact_inventory.source.path !== artifactInventoryPath(root, owner.identity)) return unavailable("binding_invalid");
1084
+ reason = "source_unavailable";
1085
+ const reading = readRunArtifactInventorySource(root, owner.identity, { start: 0, end: 0 });
1086
+ if (reading.state !== "available") return unavailable(reading.reason);
1087
+ const expectedSource = binding.artifact_inventory; const actual = reading.inventory;
1088
+ if (expectedSource.source.path !== actual.source.path || expectedSource.source.run_instance_id !== actual.source.run_instance_id ||
1089
+ expectedSource.source.sha256 !== actual.source.sha256 || expectedSource.bytes_total !== actual.bytes_total || expectedSource.declarations_total !== actual.declarations_total) return unavailable("binding_invalid");
1090
+ const unchanged = (): boolean => frameUnchanged() && reading.unchanged();
1091
+ return unchanged() ? Object.freeze({ state: "available", identity: owner.identity, binding, terminal_status: snapshot.outcome.status, unchanged }) : unavailable("observation_changed");
1092
+ } catch { return unavailable(reason); }
1093
+ }
1094
+
1095
+ // --- Readonly admitted artifact inventory ---
1096
+
1097
+ type ArtifactInventoryReference = Readonly<RunArtifactInventoryReference>;
1098
+ type ArtifactInventorySummary = Readonly<{ declared: number; references_disclosed: number; references_withheld: number }>;
1099
+ export interface RunArtifactInventoryDeclaration {
1100
+ readonly name: string;
1101
+ readonly path: string | null;
1102
+ readonly kind?: string;
1103
+ readonly media_type?: string;
1104
+ readonly required?: boolean;
1105
+ readonly availability: "not_observed";
1106
+ readonly reason?: "artifact_reference_redacted";
1107
+ }
1108
+ export type RunArtifactInventoryReading =
1109
+ | { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>; readonly inventory: ArtifactInventoryReference;
1110
+ readonly capture_scope: "producer_admission"; readonly contents: "not_read"; readonly availability: "not_observed";
1111
+ readonly production_provenance: "not_asserted"; readonly summary: ArtifactInventorySummary;
1112
+ readonly selection: Readonly<{ start: number; end: number }>; readonly artifacts: readonly Readonly<RunArtifactInventoryDeclaration>[];
1113
+ readonly artifacts_omitted: number; readonly coverage: Readonly<{ truncated: boolean; redacted: true }>; readonly unchanged: () => boolean }
1114
+ | { readonly state: "unavailable"; readonly reason: "selection_invalid" | "identity_unavailable" | "identity_mismatch" | "index_unavailable" |
1115
+ "publication_unavailable" | "view_unavailable" | "view_invalid" | "source_unavailable" | "source_invalid" |
1116
+ "range_invalid" | "range_too_large" | "observation_changed" };
1117
+ type ArtifactReadUnavailable = Extract<RunArtifactInventoryReading, { state: "unavailable" }>;
1118
+ function artifactReadUnavailable(reason: ArtifactReadUnavailable["reason"]): Readonly<ArtifactReadUnavailable> {
1119
+ return Object.freeze({ state: "unavailable", reason });
1120
+ }
1121
+ function contextFields(value: Record<string, unknown>, fields: readonly string[]): boolean {
1122
+ return Object.keys(value).every(key => fields.includes(key));
1123
+ }
1124
+ function artifactSummary(value: unknown, total: number): ArtifactInventorySummary | undefined {
1125
+ const source = record(value);
1126
+ if (!source || !contextFields(source, ["declared", "references_disclosed", "references_withheld"]) || source.declared !== total ||
1127
+ typeof source.references_disclosed !== "number" || !Number.isSafeInteger(source.references_disclosed) || source.references_disclosed < 0 ||
1128
+ typeof source.references_withheld !== "number" || !Number.isSafeInteger(source.references_withheld) || source.references_withheld < 0 ||
1129
+ source.references_disclosed + source.references_withheld !== total) return undefined;
1130
+ return Object.freeze({ declared: total, references_disclosed: source.references_disclosed, references_withheld: source.references_withheld });
1131
+ }
1132
+ function artifactDeclaration(value: unknown): Readonly<RunArtifactInventoryDeclaration> | undefined {
1133
+ const source = record(value);
1134
+ if (!source || !contextFields(source, ["name", "path", "kind", "media_type", "required", "availability", "reason"]) ||
1135
+ typeof source.name !== "string" || !source.name.trim() || /[\u0000-\u001f\u007f]/u.test(source.name) || source.availability !== "not_observed" ||
1136
+ (source.kind !== undefined && typeof source.kind !== "string") || (source.media_type !== undefined && typeof source.media_type !== "string") ||
1137
+ (source.required !== undefined && typeof source.required !== "boolean")) return undefined;
1138
+ if (source.path === null ? source.reason !== "artifact_reference_redacted" : typeof source.path !== "string" || !isAbsolute(source.path) ||
1139
+ /[\u0000-\u001f\u007f]/u.test(source.path) || source.reason !== undefined) return undefined;
1140
+ return Object.freeze({ name: source.name, path: source.path as string | null, availability: "not_observed",
1141
+ ...(source.kind === undefined ? {} : { kind: source.kind as string }), ...(source.media_type === undefined ? {} : { media_type: source.media_type as string }),
1142
+ ...(source.required === undefined ? {} : { required: source.required as boolean }), ...(source.path === null ? { reason: "artifact_reference_redacted" as const } : {}) });
1143
+ }
1144
+ function artifactHeader(value: Record<string, unknown>, identity: RunContextIndexIdentity): boolean {
1145
+ return value.run === identity.run && value.run_instance_id === identity.run_instance_id && value.capture_scope === "producer_admission" &&
1146
+ value.contents === "not_read" && value.availability === "not_observed" && value.production_provenance === "not_asserted";
1147
+ }
1148
+ function contextCoverage(value: unknown, truncated: boolean): boolean {
1149
+ const coverage = record(value);
1150
+ return !!coverage && contextFields(coverage, ["truncated", "redacted"]) && coverage.truncated === truncated && coverage.redacted === true;
1151
+ }
1152
+
1153
+ /** Fixed retained-projection policy, not permission inferred from a path/flag. Query is a half-open declaration range, not bytes. */
1154
+ export function readRunArtifactInventorySource(stateDir: string, expected: unknown, query?: unknown): Readonly<RunArtifactInventoryReading> {
1155
+ const request = contextReadIdentity(expected);
1156
+ if (!request) return artifactReadUnavailable("selection_invalid");
1157
+ let range: { start: number; end: number } | undefined;
1158
+ if (query !== undefined) {
1159
+ const value = contextReadRequest(query);
1160
+ if (!value || !contextFields(value, ["start", "end"]) || !Number.isSafeInteger(value.start) || !Number.isSafeInteger(value.end) ||
1161
+ Number(value.start) < 0 || Number(value.end) < Number(value.start)) return artifactReadUnavailable("selection_invalid");
1162
+ range = { start: Number(value.start), end: Number(value.end) };
1163
+ if (range.end - range.start > RUN_CONTEXT_ARTIFACT_MAX_BYTES) return artifactReadUnavailable("range_too_large");
1164
+ }
1165
+ const owner = readRunContextIdentity(stateDir, request);
1166
+ if (owner.state !== "available") return artifactReadUnavailable(owner.reason);
1167
+ let reason: ArtifactReadUnavailable["reason"] = "index_unavailable";
1168
+ try {
1169
+ const root = owner.local_path; const indexPath = join(root, RUN_CONTEXT_INDEX_FILE); const index = optionalStamp(indexPath);
1170
+ if (!index?.isFile()) return artifactReadUnavailable("index_unavailable");
1171
+ const admitted = readRunContextIndex(root, owner.identity);
1172
+ if (admitted.state !== "available") return artifactReadUnavailable(admitted.reason === "observation_changed" ? "observation_changed" : "index_unavailable");
1173
+ const entry = admitted.index.entries.artifacts;
1174
+ if (entry.availability !== "available" || entry.mutable !== true || !contextCoverage(entry.coverage, true)) return artifactReadUnavailable("publication_unavailable");
1175
+ const evidencePath = join(root, "evidence"); const evidence = lstatSync(evidencePath, { bigint: true });
1176
+ if (!evidence.isDirectory() || evidence.isSymbolicLink()) return artifactReadUnavailable("view_unavailable");
1177
+ reason = "view_unavailable";
1178
+ const viewPath = join(root, runContextExportPath("artifacts"));
1179
+ const captured = readFixedSourceText(viewPath, RUN_CONTEXT_ARTIFACT_MAX_BYTES, "Run artifact inventory view");
1180
+ const frameUnchanged = (): boolean => {
1181
+ try { return owner.unchanged() && sameFile(index, lstatSync(indexPath, { bigint: true })) &&
1182
+ sameObject(evidence, lstatSync(evidencePath, { bigint: true })) && sameFile(captured.stamp, lstatSync(viewPath, { bigint: true })); }
1183
+ catch { return false; }
1184
+ };
1185
+ if (!frameUnchanged()) return artifactReadUnavailable("observation_changed");
1186
+ reason = "view_invalid";
1187
+ const view = record(JSON.parse(captured.text));
1188
+ if (!view || Number(captured.stamp.size) !== entry.bytes_total || !contextFields(view, ["schema", "run", "run_instance_id", "capture_scope", "contents", "availability", "production_provenance",
1189
+ "captured_at", "content_scope", "coverage", "summary", "artifacts", "artifacts_shown", "artifacts_omitted", "inventory"]) ||
1190
+ view.schema !== VIEW_CONFIG.artifacts.schema || !artifactHeader(view, owner.identity) || view.content_scope !== "captured_artifact_inventory_reference" ||
1191
+ !contextCoverage(view.coverage, true) || !Array.isArray(view.artifacts) || view.artifacts.length !== 0 || view.artifacts_shown !== 0 ||
1192
+ typeof view.captured_at !== "string" || !Number.isFinite(Date.parse(view.captured_at)) || new Date(view.captured_at).toISOString() !== view.captured_at) return artifactReadUnavailable("view_invalid");
1193
+ const descriptor = record(view.inventory); const source = record(descriptor?.source); const total = descriptor?.declarations_total;
1194
+ if (!descriptor || !contextFields(descriptor, ["source", "bytes_total", "declarations_total"]) || !source || !contextFields(source, ["path", "run_instance_id", "sha256"]) ||
1195
+ source.path !== artifactInventoryPath(root, owner.identity) || source.run_instance_id !== owner.identity.run_instance_id ||
1196
+ typeof source.sha256 !== "string" || source.sha256.length !== 64 || !/^[a-f0-9]{64}$/u.test(source.sha256) ||
1197
+ typeof descriptor.bytes_total !== "number" || !Number.isSafeInteger(descriptor.bytes_total) || descriptor.bytes_total <= 0 || descriptor.bytes_total > RUN_ARTIFACT_INVENTORY_MAX_BYTES ||
1198
+ typeof total !== "number" || !Number.isSafeInteger(total) || total <= 0 || view.artifacts_omitted !== total) return artifactReadUnavailable("view_invalid");
1199
+ const summary = artifactSummary(view.summary, total); if (!summary) return artifactReadUnavailable("view_invalid");
1200
+ const selection = range ?? { start: 0, end: Math.min(total, DEFAULT_INSPECT_LINES) };
1201
+ if (selection.end > total) return artifactReadUnavailable("range_invalid");
1202
+ reason = "source_unavailable";
1203
+ const retained = readFixedSourceText(source.path as string, RUN_ARTIFACT_INVENTORY_MAX_BYTES, "Run artifact inventory source");
1204
+ reason = "source_invalid";
1205
+ if (Number(retained.stamp.size) !== descriptor.bytes_total || createHash("sha256").update(retained.text).digest("hex") !== source.sha256) return artifactReadUnavailable("source_invalid");
1206
+ const inventory = record(JSON.parse(retained.text));
1207
+ if (!inventory || !contextFields(inventory, ["schema", "run", "run_instance_id", "capture_scope", "contents", "availability", "production_provenance", "content_scope", "inventory_complete", "coverage", "summary", "artifacts"]) ||
1208
+ inventory.schema !== "run-artifact-inventory-v1" || !artifactHeader(inventory, owner.identity) || inventory.content_scope !== "captured_artifact_declarations" ||
1209
+ inventory.inventory_complete !== true || !contextCoverage(inventory.coverage, false) || !Array.isArray(inventory.artifacts) || inventory.artifacts.length !== total) return artifactReadUnavailable("source_invalid");
1210
+ const sourceSummary = artifactSummary(inventory.summary, total);
1211
+ if (!sourceSummary || sourceSummary.references_disclosed !== summary.references_disclosed || sourceSummary.references_withheld !== summary.references_withheld) return artifactReadUnavailable("source_invalid");
1212
+ const declarations: Readonly<RunArtifactInventoryDeclaration>[] = []; let withheld = 0;
1213
+ for (const raw of inventory.artifacts) { const item = artifactDeclaration(raw); if (!item) return artifactReadUnavailable("source_invalid"); declarations.push(item); if (item.path === null) withheld++; }
1214
+ if (withheld !== summary.references_withheld) return artifactReadUnavailable("source_invalid");
1215
+ const unchanged = (): boolean => { try { return frameUnchanged() && sameFile(retained.stamp, lstatSync(source.path as string, { bigint: true })); } catch { return false; } };
1216
+ if (!unchanged()) return artifactReadUnavailable("observation_changed");
1217
+ const result: Extract<RunArtifactInventoryReading, { state: "available" }> = Object.freeze({ state: "available", identity: owner.identity,
1218
+ inventory: Object.freeze({ source: Object.freeze({ path: source.path as string, run_instance_id: source.run_instance_id as string, sha256: source.sha256 as string }), bytes_total: descriptor.bytes_total, declarations_total: total }),
1219
+ capture_scope: "producer_admission", contents: "not_read", availability: "not_observed", production_provenance: "not_asserted", summary,
1220
+ selection: Object.freeze(selection), artifacts: Object.freeze(declarations.slice(selection.start, selection.end)), artifacts_omitted: total - (selection.end - selection.start),
1221
+ coverage: Object.freeze({ truncated: selection.start > 0 || selection.end < total, redacted: true }), unchanged });
1222
+ if (Buffer.byteLength(JSON.stringify(result)) > RUN_CONTEXT_ARTIFACT_MAX_BYTES) return artifactReadUnavailable("range_too_large");
1223
+ return unchanged() ? result : artifactReadUnavailable("observation_changed");
1224
+ } catch { return artifactReadUnavailable(reason); }
1225
+ }
1226
+
1227
+ function captureExecution(stateDir: string, producer: RunResultProducer): ExecutionUnavailable | ExecutionCapture {
1228
+ const metadata = readContextMetadata(stateDir, producer);
1229
+ if (metadata.state === "unavailable") return executionUnavailable(metadata.reason === "changed" ? "execution_changed" : "execution_metadata_unavailable");
1230
+ const path = join(metadata.root, "execution.json");
1231
+ const before = optionalStamp(path);
1232
+ if (!before) return executionUnavailable("execution_absent");
1233
+ if (!before.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES)) return executionUnavailable("execution_unavailable");
1234
+ const metadataUnchanged = captureContextMetadataFence(metadata, producer);
1235
+ if (!metadataUnchanged) return executionUnavailable("execution_changed");
1236
+ // Execution facts stay current; their Recipe privacy is fixed before reading the manifest body.
1237
+ const recipe = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
1238
+ if (!recipe) return executionUnavailable("execution_admission_unavailable");
1239
+ const metadataUnredacted = createContextIdentityPrivacyCheck(recipe);
1240
+ if (!metadataUnredacted({ run: producer.run, run_instance_id: producer.runInstanceId })) return executionUnavailable("execution_identity_redacted");
1241
+ const read = readJsonFileResilient<unknown>(path, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES, strictUtf8: true });
1242
+ const after = optionalStamp(path);
1243
+ const metaAfter = optionalStamp(metadata.path);
1244
+ if (!after || !sameFile(before, after) || !metaAfter || !sameFile(metadata.stamp, metaAfter) || !metadataUnchanged()) return executionUnavailable("execution_changed");
1245
+ if (read.diagnostics.length) return executionUnavailable("execution_invalid");
1246
+ const projected = projectRunExecutionManifest({ run: producer.run, run_instance_id: producer.runInstanceId }, read.value, recipe, createRunRecipeBodyRedactor(recipe));
1247
+ if (projected.state === "unavailable") return executionUnavailable("execution_invalid");
1248
+ // Source facts and report/reference tokens stay exact; privacy refuses this view, not the actual root outcome.
1249
+ if (!metadataUnredacted({ updated_at: projected.view.updated_at }) || projected.view.commands.some(command =>
1250
+ !metadataUnredacted({ id: command.id, ...(command.started_at === undefined ? {} : { started_at: command.started_at }),
1251
+ ...(command.completed_at === undefined ? {} : { completed_at: command.completed_at }) }) ||
1252
+ command.attempts.some(attempt => attempt.native_signal !== undefined && !metadataUnredacted({ native_signal: attempt.native_signal })))) {
1253
+ return executionUnavailable("execution_metadata_redacted");
1254
+ }
1255
+ const capturedAt = new Date().toISOString();
1256
+ if (!metadataUnredacted({ captured_at: capturedAt })) return executionUnavailable("execution_metadata_redacted");
1257
+ const content = JSON.stringify({ schema: VIEW_CONFIG.execution.schema, run: producer.run, run_instance_id: producer.runInstanceId,
1258
+ captured_at: capturedAt, coverage: EXECUTION_VIEW_COVERAGE, source_scope: "execution_manifest",
1259
+ capture_references: "not_exported", capture_bodies: "not_read", session_bodies: "not_read", scope_closure: "not_asserted", execution: projected.view }, null, 2) + "\n";
1260
+ if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_EXECUTION_MAX_BYTES) return executionUnavailable("execution_view_too_large");
1261
+ const unchanged = (): boolean => {
1262
+ try { return metadataUnchanged() && sameFile(after, lstatSync(path, { bigint: true })); }
1263
+ catch { return false; }
1264
+ };
1265
+ return unchanged() ? Object.freeze({ status: "captured", captured_at: capturedAt, content, unchanged, original_root: metadata.root }) : executionUnavailable("execution_changed");
1266
+ }
1267
+
1268
+ /** The original runtime producer is required twice: permission to read/project is not permission to publish after replacement. */
1269
+ export function writeRunControlContextExport(
1270
+ stateDir: string,
1271
+ producer: RunResultProducer,
1272
+ ): Readonly<RunControlContextExportResult> {
1273
+ const captured = withRunProducerEvidence(stateDir, producer, context => {
1274
+ try { return captureControl(stateDir, producer, context.status); }
1275
+ catch { return unavailable("control_journal_unavailable"); }
1276
+ });
1277
+ if (captured.status === "unavailable") return captured;
1278
+ const written = publishContextView(stateDir, producer, "control", captured);
1279
+ // Post-success only, including lifecycle release; later index errors remain a separate effect.
1280
+ if (!readRunProducerCleanupFailure(producer)) controlPublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
1281
+ return written;
1282
+ }
1283
+
1284
+ export function writeRunRecipeContextExport(stateDir: string, producer: RunResultProducer): Readonly<RunRecipeContextExportResult> {
1285
+ const captured = withRunProducerEvidence(stateDir, producer, () => {
1286
+ try { return captureRecipe(stateDir, producer); }
1287
+ catch { return recipeUnavailable("recipe_metadata_invalid"); }
1288
+ });
1289
+ return captured.status === "unavailable" ? captured : publishContextView(stateDir, producer, "recipe", captured);
1290
+ }
1291
+
1292
+ export function writeRunTraceContextExport(stateDir: string, producer: RunResultProducer): Readonly<RunTraceContextExportResult> {
1293
+ const captured = withRunProducerEvidence(stateDir, producer, () => {
1294
+ try { return captureTrace(stateDir, producer); }
1295
+ catch { return traceUnavailable("trace_journal_unavailable"); }
1296
+ });
1297
+ if (captured.status === "unavailable") return captured;
1298
+ const written = publishContextView(stateDir, producer, "trace", captured);
1299
+ // A returned commit can retain cleanup failure separately; neither a throw nor that report mints admission.
1300
+ if (!readRunProducerCleanupFailure(producer)) tracePublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
1301
+ return written;
1302
+ }
1303
+
1304
+ export function writeRunResultContextExport(stateDir: string, producer: RunResultProducer): Readonly<RunResultContextExportResult> {
1305
+ const captured = withRunProducerEvidence(stateDir, producer, () => captureResult(stateDir, producer));
1306
+ if (captured.status === "unavailable") return captured;
1307
+ const written = publishContextView(stateDir, producer, "result", captured);
1308
+ if (!readRunProducerCleanupFailure(producer)) resultPublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
1309
+ return written;
1310
+ }
1311
+
1312
+ export function writeRunArtifactContextExport(stateDir: string, producer: RunResultProducer): Readonly<RunArtifactContextExportResult> {
1313
+ const captured = withRunProducerEvidence(stateDir, producer, () => {
1314
+ try { return captureArtifacts(stateDir, producer); }
1315
+ catch { return artifactUnavailable("artifact_observation_unavailable"); }
1316
+ });
1317
+ if (captured.status !== "unavailable") return publishContextView(stateDir, producer, "artifacts", captured);
1318
+ if (captured.reason !== "artifact_manifest_too_large") return captured;
1319
+ const inventoryView = retainArtifactInventoryView(stateDir, producer);
1320
+ return inventoryView ? publishContextView(stateDir, producer, "artifacts", inventoryView) : captured;
1321
+ }
1322
+
1323
+ export function writeRunExecutionContextExport(stateDir: string, producer: RunResultProducer): Readonly<RunExecutionContextExportResult> {
1324
+ const captured = withRunProducerEvidence(stateDir, producer, () => {
1325
+ try { return captureExecution(stateDir, producer); }
1326
+ catch { return executionUnavailable("execution_unavailable"); }
1327
+ });
1328
+ if (captured.status === "unavailable") return captured;
1329
+ const written = publishContextView(stateDir, producer, "execution", captured);
1330
+ if (!readRunProducerCleanupFailure(producer)) executionPublications.set(producer, Object.freeze({ root: captured.original_root, content: captured.content }));
1331
+ return written;
1332
+ }
1333
+
1334
+ function readViewBinding(path: string, role: ContextExportRole): Record<string, unknown> | undefined {
1335
+ const config = VIEW_CONFIG[role];
1336
+ if (config.format === "jsonl") {
1337
+ const read = readJsonlFileResilient<unknown>(path, { maxBytes: config.maxBytes });
1338
+ return read.diagnostics.length ? undefined : record(read.records[0]);
1339
+ }
1340
+ const read = readJsonFileResilient<unknown>(path, undefined, { maxBytes: config.maxBytes });
1341
+ return read.diagnostics.length ? undefined : record(read.value);
1342
+ }
1343
+
1344
+ function publishContextView(stateDir: string, producer: RunResultProducer, role: ContextExportRole, captured: CapturedView): Readonly<WrittenView> {
1345
+ const config = VIEW_CONFIG[role];
1346
+ return withRunProducerEvidence(stateDir, producer, () => {
1347
+ if (captured.unchanged && !captured.unchanged()) throw new ContextExportBindingRefusal("Run context captured source binding changed.");
1348
+ const root = assertOwnedRunStateDirectory(stateDir, producer.run);
1349
+ const directory = lstatSync(root, { bigint: true });
1350
+ const metaPath = join(root, "run.json");
1351
+ const markerPath = join(root, RUN_STATE_OWNERSHIP_FILE);
1352
+ const meta = lstatSync(metaPath, { bigint: true });
1353
+ const marker = lstatSync(markerPath, { bigint: true });
1354
+ const evidencePath = join(root, "evidence");
1355
+ let evidence = optionalStamp(evidencePath);
1356
+ if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink())) throw new ContextExportBindingRefusal("Run context export directory is unavailable or invalid.");
1357
+ if (!evidence) { mkdirSync(evidencePath); evidence = lstatSync(evidencePath, { bigint: true }); }
1358
+ const path = join(root, runContextExportPath(role));
1359
+ const target = optionalStamp(path);
1360
+ if (target) {
1361
+ if (!target.isFile()) throw new ContextExportBindingRefusal("Run context export binding is unavailable or invalid.");
1362
+ const value = readViewBinding(path, role);
1363
+ const after = optionalStamp(path);
1364
+ if (!value || value.schema !== config.schema || value.run !== producer.run ||
1365
+ value.run_instance_id !== producer.runInstanceId || !after || !sameFile(target, after)) {
1366
+ throw new ContextExportBindingRefusal("Run context export binding changed or is unavailable.");
1367
+ }
1368
+ }
1369
+ writeTextAtomic(path, captured.content, { onBeforeReplace() {
1370
+ const currentDirectory = lstatSync(root, { bigint: true });
1371
+ const currentEvidence = lstatSync(evidencePath, { bigint: true });
1372
+ const currentTarget = optionalStamp(path);
1373
+ if ((captured.unchanged && !captured.unchanged()) || !currentDirectory.isDirectory() || !sameObject(directory, currentDirectory) ||
1374
+ !currentEvidence.isDirectory() || !sameObject(evidence, currentEvidence) ||
1375
+ !sameFile(meta, lstatSync(metaPath, { bigint: true })) || !sameFile(marker, lstatSync(markerPath, { bigint: true })) ||
1376
+ (target ? !currentTarget || !sameFile(target, currentTarget) : currentTarget !== undefined)) {
1377
+ throw new ContextExportBindingRefusal("Run context export binding changed or is unavailable.");
1378
+ }
1379
+ } });
1380
+ const entry: Readonly<RunContextEntry> = Object.freeze({ availability: "available", path: runContextExportPath(role),
1381
+ media_type: config.media, bytes_total: Buffer.byteLength(captured.content, "utf8"), mutable: true,
1382
+ coverage: captured.coverage ?? config.coverage });
1383
+ return Object.freeze({ status: "written", captured_at: captured.captured_at, entry });
1384
+ });
1385
+ }
1386
+
1387
+ /** A separate admitted index commit preserves partial view effects; unavailable sources create metadata, never empty views. */
1388
+ export function writeRunControlContextIndex(
1389
+ stateDir: string,
1390
+ producer: RunResultProducer,
1391
+ ): Readonly<RunContextIndexPublication & { status: "published" }> {
1392
+ return publishContextIndex(stateDir, producer, "control", writeRunControlContextExport(stateDir, producer));
1393
+ }
1394
+
1395
+ export function writeRunRecipeContextIndex(
1396
+ stateDir: string,
1397
+ producer: RunResultProducer,
1398
+ ): Readonly<RunContextIndexPublication & { status: "published" }> {
1399
+ return publishContextIndex(stateDir, producer, "recipe", writeRunRecipeContextExport(stateDir, producer));
1400
+ }
1401
+
1402
+ export function writeRunTraceContextIndex(
1403
+ stateDir: string,
1404
+ producer: RunResultProducer,
1405
+ ): Readonly<RunContextIndexPublication & { status: "published" }> {
1406
+ return publishContextIndex(stateDir, producer, "trace", writeRunTraceContextExport(stateDir, producer));
1407
+ }
1408
+
1409
+ export function writeRunResultContextIndex(
1410
+ stateDir: string,
1411
+ producer: RunResultProducer,
1412
+ ): Readonly<RunContextIndexPublication & { status: "published" }> {
1413
+ return publishContextIndex(stateDir, producer, "result", writeRunResultContextExport(stateDir, producer));
1414
+ }
1415
+
1416
+ export function writeRunArtifactContextIndex(
1417
+ stateDir: string,
1418
+ producer: RunResultProducer,
1419
+ ): Readonly<RunContextIndexPublication & { status: "published" }> {
1420
+ return publishContextIndex(stateDir, producer, "artifacts", writeRunArtifactContextExport(stateDir, producer));
1421
+ }
1422
+
1423
+ export function writeRunExecutionContextIndex(
1424
+ stateDir: string,
1425
+ producer: RunResultProducer,
1426
+ ): Readonly<RunContextIndexPublication & { status: "published" }> {
1427
+ return publishContextIndex(stateDir, producer, "execution", writeRunExecutionContextExport(stateDir, producer));
1428
+ }
1429
+
1430
+ function publishContextIndex(stateDir: string, producer: RunResultProducer, role: ContextExportRole,
1431
+ view: RunControlContextExportResult | RunRecipeContextExportResult | RunTraceContextExportResult | RunResultContextExportResult | RunArtifactContextExportResult | RunExecutionContextExportResult): Readonly<RunContextIndexPublication & { status: "published" }> {
1432
+ const entry: RunContextEntry = view.status === "written" ? view.entry
1433
+ : view.reason === "artifact_declarations_absent" ? { availability: "not_applicable", path: null }
1434
+ : view.reason === "control_journal_absent" || view.reason === "trace_journal_absent" || view.reason === "result_absent" || view.reason === "execution_absent" ? { availability: "pending", path: null }
1435
+ : { availability: "unavailable", path: null, reason: view.reason };
1436
+ return withRunProducerEvidence(stateDir, producer, () => {
1437
+ const expected = { run: producer.run, run_instance_id: producer.runInstanceId,
1438
+ ...(producer.ownerId !== undefined ? { ownerId: producer.ownerId } : {}) };
1439
+ const publication = writeRunContextIndexUnderLock(stateDir, expected, { [role]: entry });
1440
+ return Object.freeze({ status: "published", ...publication });
1441
+ });
1442
+ }
1443
+
1444
+ // --- Invocation-local primary command text ---
1445
+
1446
+ interface PrimaryCommandObservation {
1447
+ readonly commandId: string;
1448
+ readonly attempts: readonly CommandTemplateAttemptReceipt[];
1449
+ readonly session: boolean;
1450
+ }
1451
+ export interface RunPrimaryTextPublisher {
1452
+ readonly observe: (result: object, observation: PrimaryCommandObservation) => void;
1453
+ readonly select: (result: object) => void;
1454
+ readonly selectResponse: (text: string, upstreamTruncated: boolean) => void;
1455
+ readonly snapshot: () => Readonly<RunPrimaryTextBinding>;
1456
+ readonly readFailure: () => unknown;
1457
+ }
1458
+ function primarySourcePath(stateDir: string, identity: RunContextIndexIdentity): string {
1459
+ const key = createHash("sha256").update(JSON.stringify([identity.run, identity.run_instance_id, identity.ownerId ?? null])).digest("hex");
1460
+ return join(stateDir, "evidence", `primary-${key}.txt`);
1461
+ }
1462
+ class PrimaryTextBindingRefusal extends Error {}
1463
+ function primaryUnavailable(reason: string): Readonly<RunPrimaryTextBinding> { return Object.freeze({ output: null, output_unavailable: reason }); }
1464
+
1465
+ function readFixedSourceText(path: string, maxBytes = RUN_STREAM_SOURCE_MAX_BYTES, label = "Run primary source"): { text: string; stamp: BigIntStats } {
1466
+ const stamp = lstatSync(path, { bigint: true });
1467
+ if (!stamp.isFile() || stamp.size > BigInt(maxBytes)) throw new PrimaryTextBindingRefusal(`${label} is not a bounded regular file.`);
1468
+ const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
1469
+ try {
1470
+ if (!sameFile(stamp, fstatSync(fd, { bigint: true }))) throw new PrimaryTextBindingRefusal(`${label} changed.`);
1471
+ const bytes = Buffer.alloc(Number(stamp.size)); let offset = 0;
1472
+ while (offset < bytes.length) {
1473
+ const count = readSync(fd, bytes, offset, bytes.length - offset, offset);
1474
+ if (!count) throw new PrimaryTextBindingRefusal(`${label} ended before its bounded read completed.`);
1475
+ offset += count;
1476
+ }
1477
+ let text: string;
1478
+ try { text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes); }
1479
+ catch (error) { throw new PrimaryTextBindingRefusal(`${label} is not valid UTF-8.`, { cause: error }); }
1480
+ if (!sameFile(stamp, fstatSync(fd, { bigint: true })) || !sameFile(stamp, lstatSync(path, { bigint: true }))) throw new PrimaryTextBindingRefusal(`${label} changed.`);
1481
+ return { text, stamp };
1482
+ } finally { closeSync(fd); }
1483
+ }
1484
+
1485
+ export interface RunSnapshotReferences {
1486
+ readonly cwd: string | null;
1487
+ readonly cwd_unavailable?: "cwd_reference_invalid" | "cwd_reference_redacted";
1488
+ readonly context_index: Readonly<{ identity: Readonly<RunContextIndexIdentity>; path: string }> | null;
1489
+ readonly context_index_unavailable?: "index_reference_invalid" | "index_reference_redacted" | "index_unavailable";
1490
+ }
1491
+ export type RunSnapshotReferencesReading =
1492
+ | { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>; readonly references: Readonly<RunSnapshotReferences>; readonly unchanged: () => boolean }
1493
+ | { readonly state: "unavailable"; readonly reason: "privacy_capture_unavailable" | "references_too_large" |
1494
+ "identity_unavailable" | "identity_mismatch" | "observation_changed" | "observation_unavailable" };
1495
+ export interface RunSnapshotReferenceReader { readonly read: () => Readonly<RunSnapshotReferencesReading>; }
1496
+ const snapshotReferenceIssuer = Symbol("Run snapshot reference issuer");
1497
+ class SnapshotReferenceReader implements RunSnapshotReferenceReader {
1498
+ readonly #root: string;
1499
+ readonly #identity: Readonly<RunContextIndexIdentity>;
1500
+ readonly #frameUnchanged: () => boolean;
1501
+ readonly read: RunSnapshotReferenceReader["read"];
1502
+ constructor(issuer: symbol, root: string, identity: Readonly<RunContextIndexIdentity>, frameUnchanged: () => boolean, read: RunSnapshotReferenceReader["read"]) {
1503
+ if (issuer !== snapshotReferenceIssuer) throw new Error("Run snapshot reference reader requires its private issuer.");
1504
+ this.#root = root; this.#identity = identity; this.#frameUnchanged = frameUnchanged; this.read = read; Object.freeze(this);
1505
+ }
1506
+ static admit(value: unknown, stateDir: string, expected: unknown): Readonly<RunSnapshotReferenceReader> | undefined {
1507
+ if (isPromise(value)) { void Promise.prototype.then.call(value, undefined, () => undefined); return undefined; }
1508
+ if (!value || typeof value !== "object" || !(#root in value)) return undefined;
1509
+ const identity = contextReadIdentity(expected);
1510
+ return identity && value.#root === stateDir && identity.run === value.#identity.run && identity.run_instance_id === value.#identity.run_instance_id &&
1511
+ identity.ownerId === value.#identity.ownerId && value.#frameUnchanged() ? value : undefined;
1512
+ }
1513
+ }
1514
+
1515
+ /** Original same-module reader only; schemas, copied objects/prototypes and flags are not permission. */
1516
+ export function admitRunSnapshotReferenceReader(stateDir: string, expected: unknown, value: unknown): Readonly<RunSnapshotReferenceReader> | undefined {
1517
+ return SnapshotReferenceReader.admit(value, stateDir, expected);
1518
+ }
1519
+
1520
+ function snapshotReferenceFrame(root: string): () => boolean {
1521
+ const directory = lstatSync(root, { bigint: true }); const markerPath = join(root, RUN_STATE_OWNERSHIP_FILE); const marker = lstatSync(markerPath, { bigint: true });
1522
+ return (): boolean => { try { const current = lstatSync(root, { bigint: true }); return current.isDirectory() && sameObject(directory, current) && sameFile(marker, lstatSync(markerPath, { bigint: true })); } catch { return false; } };
1523
+ }
1524
+
1525
+ /** Snapshot only own JSON data, without invoking serialization hooks/getters; refuse the complete privacy capture on loss. */
1526
+ function launchPrivacyMetadata(value: unknown): Record<string, unknown> | undefined {
1527
+ try {
1528
+ let bytes = 0; const seen = new Set<object>();
1529
+ const account = (count: number): void => { bytes += count; if (bytes > RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES) throw new Error("Run launch privacy capture exceeds its budget."); };
1530
+ const copy = (item: unknown): unknown => {
1531
+ if (item === null || typeof item === "string" || typeof item === "boolean" || (typeof item === "number" && Number.isFinite(item))) { account(Buffer.byteLength(JSON.stringify(item))); return item; }
1532
+ if (!item || typeof item !== "object" || seen.has(item)) throw new Error("Run launch privacy capture is not JSON data.");
1533
+ const array = Array.isArray(item); const prototype = Object.getPrototypeOf(item);
1534
+ if (prototype !== null && prototype !== (array ? Array.prototype : Object.prototype)) throw new Error("Run launch privacy capture has a custom prototype.");
1535
+ const fields = Object.getOwnPropertyDescriptors(item);
1536
+ if (fields.toJSON && (!Object.hasOwn(fields.toJSON, "value") || typeof fields.toJSON.value === "function")) throw new Error("Run launch privacy capture has a serialization hook.");
1537
+ seen.add(item); account(2); const result: Record<string, unknown> | unknown[] = array ? [] : Object.create(null); Object.setPrototypeOf(result, null);
1538
+ if (array) {
1539
+ const length = (item as unknown[]).length; if (length > RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES) throw new Error("Run launch privacy array exceeds its budget.");
1540
+ for (let i = 0; i < length; i++) { const field = fields[String(i)]; if (field && !Object.hasOwn(field, "value")) throw new Error("Run launch privacy capture has an accessor.");
1541
+ if (i) account(1); (result as unknown[])[i] = copy(field?.value === undefined ? null : field.value); }
1542
+ } else {
1543
+ let count = 0;
1544
+ for (const [key, field] of Object.entries(fields)) { if (!field.enumerable) continue;
1545
+ if (!Object.hasOwn(field, "value")) throw new Error("Run launch privacy capture has an accessor.");
1546
+ if (field.value === undefined) continue;
1547
+ account(Buffer.byteLength(JSON.stringify(key)) + 1 + (count++ ? 1 : 0)); (result as Record<string, unknown>)[key] = copy(field.value);
1548
+ }
1549
+ }
1550
+ seen.delete(item); return result;
1551
+ };
1552
+ const copied = copy(value); return record(copied) ? JSON.parse(JSON.stringify(copied)) as Record<string, unknown> : undefined;
1553
+ } catch { return undefined; }
1554
+ }
1555
+
1556
+ /** Trusted fresh-launch kernel entry, under its already-held lifecycle lock after full metadata assembly, before publication/work.
1557
+ * The caller's actual admission is authority, NOT metadata shape, marker or clock origin as a privacy certificate; not an arbitrary-JS sandbox.
1558
+ * No actor/configuration or saved-data route may invoke this issuer. The original runtime-local reader, never its DTO, crosses the handoff.
1559
+ */
1560
+ export function captureRunAdmissionSnapshotReferences(stateDir: string, metadata: unknown, origin: unknown): Readonly<RunSnapshotReferenceReader> {
1561
+ const data = contextReadRequest(metadata); const identity = contextReadIdentity(metadata);
1562
+ if (!data || !identity || data.pid !== 0 || data.outcome_protocol !== RUN_OUTCOME_PROTOCOL) throw new ContextExportBindingRefusal("Run launch reference admission is invalid.");
1563
+ const root = assertOwnedRunStateDirectory(stateDir, identity.run);
1564
+ if (data.state_dir !== root || readRunContextIdentity(root, identity).state === "available") throw new ContextExportBindingRefusal("Run launch reference admission must be unpublished and root-bound.");
1565
+ const policy = contextReadRequest(data.supervision); let clock;
1566
+ try { clock = policy && readRunSupervisionClock({ started_at: data.started_at, createdAt: data.createdAt, supervision: policy }); } catch { /* Fail closed below. */ }
1567
+ if (!clock || createRunSupervisionTimebase(clock, origin).state !== "available") throw new ContextExportBindingRefusal("Run launch reference admission requires its original clock origin.");
1568
+ return capturedSnapshotReferenceReader({ root, metadata: launchPrivacyMetadata(metadata), frameUnchanged: snapshotReferenceFrame(root) }, identity);
1569
+ }
1570
+
1571
+ function snapshotReference(value: unknown, redact: (text: string) => string, withoutSecrets: (text: string) => string): "invalid" | "redacted" | "available" {
1572
+ if (typeof value !== "string" || !isAbsolute(value) || /[\u0000-\u001f\u007f]/u.test(value) || Buffer.from(value).toString("utf8") !== value) return "invalid";
1573
+ return withoutSecrets(value) === value && redact(value) === value ? "available" : "redacted";
1574
+ }
1575
+
1576
+ /** Private runtime-local reference permission from genuine admission; not lifecycle/timing/body authority or durable transport. */
1577
+ export function createRunSnapshotReferenceReader(stateDir: string, producer: RunResultProducer): Readonly<RunSnapshotReferenceReader> {
1578
+ const capture = withRunProducerEvidence(stateDir, producer, () => {
1579
+ const root = assertOwnedRunStateDirectory(stateDir, producer.run);
1580
+ const metadata = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
1581
+ if (metadata && (metadata.state_dir !== root || metadata.run !== producer.run || metadata.run_instance_id !== producer.runInstanceId || metadata.ownerId !== producer.ownerId)) {
1582
+ throw new ContextExportBindingRefusal("Run snapshot reference capture binding changed.");
1583
+ }
1584
+ return { root, metadata, frameUnchanged: snapshotReferenceFrame(root) };
1585
+ });
1586
+ const identity = Object.freeze({ run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
1587
+ return capturedSnapshotReferenceReader(capture, identity);
1588
+ }
1589
+
1590
+ function capturedSnapshotReferenceReader(capture: { root: string; metadata: Record<string, unknown> | undefined; frameUnchanged: () => boolean }, identity: Readonly<RunContextIndexIdentity>): Readonly<RunSnapshotReferenceReader> {
1591
+ const indexPath = join(capture.root, RUN_CONTEXT_INDEX_FILE); let cwd: RunSnapshotReferences["cwd"] = null;
1592
+ let cwdUnavailable: RunSnapshotReferences["cwd_unavailable"]; let indexUnavailable: RunSnapshotReferences["context_index_unavailable"]; let policyAvailable = false;
1593
+ try {
1594
+ if (capture.metadata) {
1595
+ const redact = createRunRecipeTextRedactor(capture.metadata), withoutSecrets = capturedSecretMask(capture.metadata, "");
1596
+ const cwdState = snapshotReference(capture.metadata.cwd, redact, withoutSecrets);
1597
+ if (cwdState === "available") cwd = capture.metadata.cwd as string; else cwdUnavailable = cwdState === "redacted" ? "cwd_reference_redacted" : "cwd_reference_invalid";
1598
+ const indexState = snapshotReference(indexPath, redact, withoutSecrets);
1599
+ if (indexState !== "available") indexUnavailable = indexState === "redacted" ? "index_reference_redacted" : "index_reference_invalid";
1600
+ policyAvailable = true;
1601
+ }
1602
+ } catch { /* Failed full admission privacy cannot disclose a reference or fall back to live metadata. */ }
1603
+ return new SnapshotReferenceReader(snapshotReferenceIssuer, capture.root, identity, capture.frameUnchanged, (): Readonly<RunSnapshotReferencesReading> => {
1604
+ const unavailable = (reason: Extract<RunSnapshotReferencesReading, { state: "unavailable" }>["reason"]): Readonly<RunSnapshotReferencesReading> => Object.freeze({ state: "unavailable", reason });
1605
+ if (!policyAvailable) return unavailable("privacy_capture_unavailable");
1606
+ if (!capture.frameUnchanged()) return unavailable("observation_changed");
1607
+ const owner = readRunContextIdentity(capture.root, identity); if (owner.state !== "available") return unavailable(owner.reason);
1608
+ try {
1609
+ const maximum = { state: "available", identity, references: { cwd, ...(cwdUnavailable ? { cwd_unavailable: cwdUnavailable } : {}),
1610
+ context_index: indexUnavailable ? null : { identity, path: indexPath }, ...(indexUnavailable ? { context_index_unavailable: indexUnavailable } : {}) } };
1611
+ if (Buffer.byteLength(JSON.stringify(maximum)) > RUN_DELIVERY_MODEL_MAX_BYTES) return unavailable("references_too_large");
1612
+ const before = indexUnavailable ? undefined : optionalStamp(indexPath);
1613
+ const context = indexUnavailable ? undefined : readRunContextIndex(capture.root, identity);
1614
+ const unchanged = (): boolean => {
1615
+ try { const after = indexUnavailable ? undefined : optionalStamp(indexPath);
1616
+ return capture.frameUnchanged() && owner.unchanged() && (indexUnavailable !== undefined ||
1617
+ (before === undefined ? after === undefined : after !== undefined && sameObject(before, after) && before.mode === after.mode && before.size === after.size && before.mtimeNs === after.mtimeNs && before.ctimeNs === after.ctimeNs)); }
1618
+ catch { return false; }
1619
+ };
1620
+ if (!unchanged()) return unavailable("observation_changed");
1621
+ const references: Readonly<RunSnapshotReferences> = Object.freeze({ cwd, ...(cwdUnavailable ? { cwd_unavailable: cwdUnavailable } : {}),
1622
+ context_index: context?.state === "available" ? Object.freeze({ identity: context.identity, path: context.local_path }) : null,
1623
+ ...(context?.state === "available" ? {} : { context_index_unavailable: indexUnavailable ?? "index_unavailable" as const }) });
1624
+ return Object.freeze({ state: "available", identity, references, unchanged });
1625
+ } catch { return unavailable("observation_unavailable"); }
1626
+ });
1627
+ }
1628
+
1629
+ /** Kernel-owned invocation context. Only explicit executor selection publishes command text or its final response. */
1630
+ export function createRunPrimaryTextPublisher(stateDir: string, producer: RunResultProducer): Readonly<RunPrimaryTextPublisher> {
1631
+ const metadata = withRunProducerEvidence(stateDir, producer, () => readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES));
1632
+ let redact: ((text: string) => string) | undefined;
1633
+ try { if (metadata) redact = createRunRecipeTextRedactor(metadata); } catch { /* No privacy proof means no source publication. */ }
1634
+ const identityUnchanged = metadata ? createContextIdentityPrivacyCheck(metadata)({ run: producer.run, run_instance_id: producer.runInstanceId,
1635
+ ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) }) : undefined;
1636
+ let observed: { result: object; observation: PrimaryCommandObservation } | undefined; let count = 0;
1637
+ let binding: Readonly<RunPrimaryTextBinding> = primaryUnavailable("not_selected"); let failure: unknown; let cleanupFailure: unknown;
1638
+ let sourceUnchanged: (() => boolean) | undefined;
1639
+ let responseSelected = false;
1640
+ const capture = (original: string, inputUnchanged: () => boolean, response?: { text_basis: "actor_response"; upstream_truncated: boolean }): void => {
1641
+ if (!redact) { binding = primaryUnavailable("privacy_capture_unavailable"); return; }
1642
+ if (!identityUnchanged) { binding = primaryUnavailable("identity_redacted"); return; }
1643
+ let projected: unknown;
1644
+ try { projected = redact(original); } catch (error) { failure = error; binding = primaryUnavailable("projection_unavailable"); return; }
1645
+ if (typeof projected !== "string" || (response !== undefined && Buffer.from(projected).toString("utf8") !== projected) || Buffer.byteLength(projected) > RUN_STREAM_SOURCE_MAX_BYTES) { binding = primaryUnavailable("projection_unavailable"); return; }
1646
+ const text = projected;
1647
+ let chars = 0; let preview = ""; let shown = 0;
1648
+ for (const point of text) { chars++; if (shown < COMPACT_PREVIEW_CHARS) { preview += point; shown++; } }
1649
+ const path = primarySourcePath(stateDir, { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId === undefined ? {} : { ownerId: producer.ownerId }) });
1650
+ const output = Object.freeze({ source: Object.freeze({ path, run_instance_id: producer.runInstanceId, sha256: createHash("sha256").update(text).digest("hex") }),
1651
+ bytes_total: Buffer.byteLength(text), shown_bytes: Object.freeze({ start: 0, end: Buffer.byteLength(preview) }),
1652
+ chars_total: chars, chars_shown: shown, chars_omitted: chars - shown, preview, truncated: shown < chars, redacted: text !== original, ...response });
1653
+ if (Buffer.byteLength(JSON.stringify({ output })) > RUN_DELIVERY_MODEL_MAX_BYTES) { binding = primaryUnavailable("descriptor_too_large"); return; }
1654
+ binding = withRunProducerEvidence(stateDir, producer, () => {
1655
+ if (!inputUnchanged()) throw new PrimaryTextBindingRefusal("Run primary selected capture binding changed.");
1656
+ const root = assertOwnedRunStateDirectory(stateDir, producer.run); const directory = lstatSync(root, { bigint: true });
1657
+ const metaPath = join(root, "run.json"); const markerPath = join(root, RUN_STATE_OWNERSHIP_FILE);
1658
+ const meta = lstatSync(metaPath, { bigint: true }); const marker = lstatSync(markerPath, { bigint: true });
1659
+ const evidencePath = join(root, "evidence"); let evidence: BigIntStats | undefined;
1660
+ const rootUnchanged = (): boolean => {
1661
+ try { return sameObject(directory, lstatSync(root, { bigint: true })) && sameFile(meta, lstatSync(metaPath, { bigint: true })) && sameFile(marker, lstatSync(markerPath, { bigint: true })); }
1662
+ catch { return false; }
1663
+ };
1664
+ const frameUnchanged = (): boolean => {
1665
+ try { return rootUnchanged() && !!evidence && sameObject(evidence, lstatSync(evidencePath, { bigint: true })); }
1666
+ catch { return false; }
1667
+ };
1668
+ try {
1669
+ evidence = optionalStamp(evidencePath);
1670
+ if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink())) throw new PrimaryTextBindingRefusal("Run primary export directory binding is invalid.");
1671
+ if (!evidence) { mkdirSync(evidencePath); evidence = lstatSync(evidencePath, { bigint: true }); }
1672
+ const existing = optionalStamp(path);
1673
+ if (existing) {
1674
+ if (readFixedSourceText(path).text !== text) throw new PrimaryTextBindingRefusal("Run primary source identity already fixes different bytes.");
1675
+ } else {
1676
+ const temporary = join(evidencePath, `.primary-${randomUUID()}.tmp`); let written = false; let failed = false;
1677
+ try {
1678
+ writeFileSync(temporary, text, { flag: "wx", mode: 0o600 }); written = true;
1679
+ if (!frameUnchanged() || !inputUnchanged()) throw new PrimaryTextBindingRefusal("Run primary publication binding changed.");
1680
+ try { linkSync(temporary, path); }
1681
+ catch (error) { if ((error as NodeJS.ErrnoException).code === "EEXIST") throw new PrimaryTextBindingRefusal("Run primary source target appeared during publication.", { cause: error }); throw error; }
1682
+ } catch (error) { failed = true; throw error; }
1683
+ finally { if (written) { try { unlinkSync(temporary); } catch (error) { cleanupFailure ??= error; if (!failed) throw error; } } }
1684
+ }
1685
+ const saved = readFixedSourceText(path);
1686
+ if (saved.text !== text) throw new PrimaryTextBindingRefusal("Run primary source identity already fixes different bytes.");
1687
+ sourceUnchanged = () => { try { return frameUnchanged() && sameFile(saved.stamp, lstatSync(path, { bigint: true })); } catch { return false; } };
1688
+ } catch (error) {
1689
+ if (error instanceof PrimaryTextBindingRefusal) throw error;
1690
+ failure = error;
1691
+ if (!rootUnchanged() || (evidence && !frameUnchanged()) || !inputUnchanged()) throw new PrimaryTextBindingRefusal("Run primary publication binding changed during export failure.");
1692
+ return primaryUnavailable("export_failed");
1693
+ }
1694
+ if (!frameUnchanged() || !inputUnchanged()) throw new PrimaryTextBindingRefusal("Run primary publication binding changed.");
1695
+ return Object.freeze({ output });
1696
+ });
1697
+ };
1698
+ return Object.freeze({
1699
+ observe(result: object, observation: PrimaryCommandObservation): void {
1700
+ count++; observed = { result, observation: { commandId: observation.commandId, attempts: [...observation.attempts], session: observation.session } };
1701
+ },
1702
+ select(result: object): void {
1703
+ if (!observed || result !== observed.result) throw new PrimaryTextBindingRefusal("Run primary selection requires its exact observed invocation result.");
1704
+ withRunProducerEvidence(stateDir, producer, () => {});
1705
+ const { commandId, attempts, session } = observed.observation;
1706
+ const receipt = attempts[0]; const selected = result as CommandTemplateExecResult; const raw = receipt?.result;
1707
+ if (count !== 1 || session || !/^command-[0-9]{3,}$/.test(commandId) || attempts.length !== 1 || receipt?.attempt !== 1 ||
1708
+ selected.code !== 0 || selected.killed || raw?.code !== 0 || raw.killed) { binding = primaryUnavailable("selection_not_supported"); return; }
1709
+ if (!redact) { binding = primaryUnavailable("privacy_capture_unavailable"); return; }
1710
+ if (!identityUnchanged) { binding = primaryUnavailable("identity_redacted"); return; }
1711
+ const relative = `captures/${commandId}/attempt-001/stdout.log`;
1712
+ if ((raw.stdoutFile !== undefined && raw.stdoutFile !== join(stateDir, relative)) || selected.stdout !== raw.stdout) {
1713
+ throw new PrimaryTextBindingRefusal("Run primary selected capture binding changed.");
1714
+ }
1715
+ const observedBytes = raw.stdoutBytes; const memoryBytes = Buffer.byteLength(raw.stdout); const tail = raw.stdoutTruncated === true;
1716
+ if (!raw.stdoutFile || raw.stdoutCaptureIncomplete ||
1717
+ (raw.stdoutFileBytes !== undefined && raw.stdoutFileBytes !== observedBytes) ||
1718
+ typeof observedBytes !== "number" || !Number.isSafeInteger(observedBytes) || observedBytes < memoryBytes ||
1719
+ (tail ? observedBytes <= memoryBytes || typeof receipt.stdoutSha256 !== "string" || receipt.stdoutSha256.length !== 64 || !/^[a-f0-9]{64}$/.test(receipt.stdoutSha256)
1720
+ : observedBytes !== memoryBytes)) {
1721
+ binding = primaryUnavailable("complete_capture_unavailable"); return;
1722
+ }
1723
+ const input = readExecutionCaptureText(stateDir, relative);
1724
+ if (input.availability !== "available") { binding = primaryUnavailable("capture_unavailable"); return; }
1725
+ // A matching tail/size cannot bind an unseen prefix; compare the privately retained observed-stream digest.
1726
+ if (input.reference.bytes_total !== observedBytes || (tail
1727
+ ? !input.text.endsWith(raw.stdout) || createHash("sha256").update(input.text).digest("hex") !== receipt.stdoutSha256
1728
+ : input.text !== raw.stdout)) throw new PrimaryTextBindingRefusal("Run primary selected capture binding changed.");
1729
+ if (!input.text) { binding = primaryUnavailable("no_primary_text"); return; }
1730
+ capture(input.text, () => input.reference.unchanged());
1731
+ },
1732
+ selectResponse(text: string, upstreamTruncated: boolean): void {
1733
+ withRunProducerEvidence(stateDir, producer, () => {});
1734
+ if (responseSelected) throw new PrimaryTextBindingRefusal("Run final response is already selected.");
1735
+ // Never replace a complete source or heal failed privacy/capture/publication through another selection.
1736
+ if (binding.output || !["not_selected", "selection_not_supported"].includes(binding.output_unavailable)) return;
1737
+ responseSelected = true;
1738
+ if (typeof text !== "string" || typeof upstreamTruncated !== "boolean") throw new PrimaryTextBindingRefusal("Run final response selection is invalid.");
1739
+ if (!text) { binding = primaryUnavailable("no_primary_text"); return; }
1740
+ if (Buffer.byteLength(text) > RUN_STREAM_SOURCE_MAX_BYTES) { binding = primaryUnavailable("response_too_large"); return; }
1741
+ // The executor supplies an immutable call-local response, not a session log or a reconstructed capture.
1742
+ capture(text, () => true, { text_basis: "actor_response", upstream_truncated: upstreamTruncated });
1743
+ },
1744
+ snapshot: (): Readonly<RunPrimaryTextBinding> => {
1745
+ if (binding.output && !sourceUnchanged?.()) throw new PrimaryTextBindingRefusal("Run primary retained source binding changed.");
1746
+ return binding;
1747
+ },
1748
+ readFailure: () => failure === undefined && cleanupFailure === undefined ? undefined : Object.freeze({ error: failure, cleanup_error: cleanupFailure }),
1749
+ });
1750
+ }
1751
+
1752
+ // --- Readonly admitted primary source ---
1753
+
1754
+ export type RunPrimaryTextReading =
1755
+ | { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>; readonly result_acceptance: "natural" | "capture_only";
1756
+ readonly selection: "recorded_preview" | "byte_range"; readonly output: Readonly<RunPrimaryTextOutput>;
1757
+ readonly original_output?: Readonly<RunPrimaryTextOutput>; readonly unchanged: () => boolean }
1758
+ | { readonly state: "unavailable"; readonly reason: "selection_invalid" | "identity_unavailable" | "identity_mismatch" | "observation_changed" |
1759
+ "result_unavailable" | "publication_unavailable" | "binding_invalid" | "output_unavailable" | "source_unavailable" | "source_invalid" |
1760
+ "range_invalid" | "range_too_large"; readonly output_unavailable?: string };
1761
+ type PrimaryReadUnavailable = Extract<RunPrimaryTextReading, { state: "unavailable" }>;
1762
+ function primaryReadUnavailable(reason: PrimaryReadUnavailable["reason"], output_unavailable?: string): Readonly<PrimaryReadUnavailable> {
1763
+ return Object.freeze({ state: "unavailable", reason, ...(output_unavailable === undefined ? {} : { output_unavailable }) });
1764
+ }
1765
+ function contextReadRequest(value: unknown): Record<string, unknown> | undefined {
1766
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
1767
+ const fields = Object.getOwnPropertyDescriptors(value);
1768
+ return Object.values(fields).every(field => Object.hasOwn(field, "value"))
1769
+ ? Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.value])) : undefined;
1770
+ }
1771
+ function contextReadIdentity(expected: unknown): Readonly<RunContextIndexIdentity> | undefined {
1772
+ const value = contextReadRequest(expected);
1773
+ if (!value || typeof value.run !== "string" || !value.run || /[^A-Za-z0-9_.-]/u.test(value.run) ||
1774
+ typeof value.run_instance_id !== "string" || !value.run_instance_id ||
1775
+ (value.ownerId !== undefined && (typeof value.ownerId !== "string" || !value.ownerId))) return undefined;
1776
+ return Object.freeze({ run: value.run, run_instance_id: value.run_instance_id, ...(value.ownerId === undefined ? {} : { ownerId: value.ownerId as string }) });
1777
+ }
1778
+ function contextJson(path: string): { value: Record<string, unknown>; stamp: BigIntStats } {
1779
+ const source = readFixedSourceText(path, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
1780
+ const value = record(JSON.parse(source.text.startsWith("\uFEFF") ? source.text.slice(1) : source.text));
1781
+ if (!value) throw new Error("Run primary metadata is not an object.");
1782
+ return { value, stamp: source.stamp };
1783
+ }
1784
+ function primaryChars(text: string): number { let count = 0; for (const _point of text) count++; return count; }
1785
+ function primaryBoundary(bytes: Buffer, offset: number): boolean {
1786
+ return offset === 0 || offset === bytes.length || (bytes[offset]! & 0xc0) !== 0x80;
1787
+ }
1788
+
1789
+ /** Saved canonical facts authorize only this published source. No producer, index, raw discovery, refresh, work or ACK. */
1790
+ export function readRunPrimaryTextSource(stateDir: string, expected: unknown, query?: unknown): Readonly<RunPrimaryTextReading> {
1791
+ return readPrimaryTextSource(stateDir, expected, query, false);
1792
+ }
1793
+
1794
+ /** Fresh completion admission only: select bounded text from the same verified source observation, not another source read. */
1795
+ export function readRunCompletionTextSource(stateDir: string, expected: unknown): Readonly<RunPrimaryTextReading> {
1796
+ return readPrimaryTextSource(stateDir, expected, undefined, true);
1797
+ }
1798
+
1799
+ function readPrimaryTextSource(stateDir: string, expected: unknown, query: unknown, completion: boolean): Readonly<RunPrimaryTextReading> {
1800
+ const request = contextReadIdentity(expected);
1801
+ if (!request) return primaryReadUnavailable("selection_invalid");
1802
+ let range: { start: number; end: number } | undefined;
1803
+ if (query !== undefined) {
1804
+ const value = contextReadRequest(query);
1805
+ if (!value || Object.keys(value).some(key => key !== "start" && key !== "end") ||
1806
+ !Number.isSafeInteger(value.start) || !Number.isSafeInteger(value.end) || Number(value.start) < 0 || Number(value.end) < Number(value.start)) {
1807
+ return primaryReadUnavailable("selection_invalid");
1808
+ }
1809
+ range = { start: Number(value.start), end: Number(value.end) };
1810
+ if (range.end - range.start > RUN_CONTEXT_RESULT_MAX_BYTES) return primaryReadUnavailable("range_too_large");
1811
+ }
1812
+ const owner = readRunContextIdentity(stateDir, request);
1813
+ if (owner.state === "unavailable") return primaryReadUnavailable(owner.reason);
1814
+ let reason: PrimaryReadUnavailable["reason"] = "result_unavailable";
1815
+ try {
1816
+ const root = owner.local_path; const metadata = contextJson(join(root, "run.json"));
1817
+ const meta = metadata.value;
1818
+ if (meta.outcome_protocol !== RUN_OUTCOME_PROTOCOL || !Number.isSafeInteger(meta.pid) || Number(meta.pid) <= 0) return primaryReadUnavailable("publication_unavailable");
1819
+ const resultPath = join(root, "result.json"); const canonical = contextJson(resultPath);
1820
+ const snapshot = readRunNaturalOutcomeSnapshot(root, meta, () => canonical.value);
1821
+ const frameUnchanged = (): boolean => owner.unchanged() && sameFile(metadata.stamp, lstatSync(join(root, "run.json"), { bigint: true })) &&
1822
+ sameFile(canonical.stamp, lstatSync(resultPath, { bigint: true }));
1823
+ if (!frameUnchanged()) return primaryReadUnavailable("observation_changed");
1824
+ if (!snapshot.result || (snapshot.outcome.state !== "accepted" && snapshot.outcome.state !== "captured")) return primaryReadUnavailable("result_unavailable");
1825
+ if (snapshot.result.primary_text_protocol !== RUN_PRIMARY_TEXT_PROTOCOL) return primaryReadUnavailable("publication_unavailable");
1826
+ let binding: Readonly<RunPrimaryTextBinding>;
1827
+ try { binding = decodeRunPrimaryTextBinding(root, owner.identity.run_instance_id, snapshot.result); }
1828
+ catch { return primaryReadUnavailable("binding_invalid"); }
1829
+ if (!binding.output) return primaryReadUnavailable("output_unavailable", binding.output_unavailable);
1830
+ const output = binding.output;
1831
+ if (snapshot.result.code !== 0 || (snapshot.result.killed === true && output.text_basis !== "actor_response") || output.bytes_total === 0 ||
1832
+ output.source.path !== primarySourcePath(root, owner.identity)) return primaryReadUnavailable("binding_invalid");
1833
+ reason = "source_unavailable";
1834
+ const evidencePath = join(root, "evidence"); const evidence = lstatSync(evidencePath, { bigint: true });
1835
+ if (!evidence.isDirectory() || evidence.isSymbolicLink()) return primaryReadUnavailable("source_unavailable");
1836
+ const source = readFixedSourceText(output.source.path); const bytes = Buffer.from(source.text);
1837
+ const excerpt = output.shown_bytes;
1838
+ if (bytes.length !== output.bytes_total || primaryChars(source.text) !== output.chars_total ||
1839
+ createHash("sha256").update(bytes).digest("hex") !== output.source.sha256 ||
1840
+ !primaryBoundary(bytes, excerpt.start) || !primaryBoundary(bytes, excerpt.end) ||
1841
+ bytes.subarray(excerpt.start, excerpt.end).toString("utf8") !== output.preview) return primaryReadUnavailable("source_invalid");
1842
+ const unchanged = (): boolean => {
1843
+ try {
1844
+ const current = lstatSync(evidencePath, { bigint: true });
1845
+ return frameUnchanged() && current.isDirectory() && sameObject(evidence, current) &&
1846
+ sameFile(source.stamp, lstatSync(output.source.path, { bigint: true }));
1847
+ } catch { return false; }
1848
+ };
1849
+ if (!unchanged()) return primaryReadUnavailable("observation_changed");
1850
+ let selected = output;
1851
+ if (completion) {
1852
+ let end = Math.min(bytes.length, RUN_COMPLETION_MODEL_MAX_BYTES);
1853
+ while (!primaryBoundary(bytes, end)) end--;
1854
+ if (end < bytes.length && bytes[end - 1] === 13 && bytes[end] === 10) end--;
1855
+ range = { start: 0, end };
1856
+ }
1857
+ if (range) {
1858
+ if (range.end > bytes.length || !primaryBoundary(bytes, range.start) || !primaryBoundary(bytes, range.end)) return primaryReadUnavailable("range_invalid");
1859
+ const preview = bytes.subarray(range.start, range.end).toString("utf8"); const shown = primaryChars(preview);
1860
+ selected = Object.freeze({ ...output, shown_bytes: Object.freeze(range), preview, chars_shown: shown, chars_omitted: output.chars_total - shown, truncated: shown < output.chars_total });
1861
+ }
1862
+ const result = Object.freeze({ state: "available" as const, identity: owner.identity, result_acceptance: snapshot.outcome.state === "accepted" ? "natural" as const : "capture_only" as const,
1863
+ selection: range ? "byte_range" as const : "recorded_preview" as const, output: selected,
1864
+ ...(completion ? { original_output: output } : {}), unchanged });
1865
+ // Completion text is private admission data, not a public64KiB encoded tool view.
1866
+ // Its48KiB prefix can need sixfold JSON escaping; the existing journal bound still limits this DTO.
1867
+ const encodedLimit = completion ? RUN_DELIVERY_JOURNAL_MAX_BYTES : RUN_CONTEXT_RESULT_MAX_BYTES;
1868
+ if (Buffer.byteLength(JSON.stringify(result)) > encodedLimit) return primaryReadUnavailable("range_too_large");
1869
+ return unchanged() ? result : primaryReadUnavailable("observation_changed");
1870
+ } catch { return primaryReadUnavailable(reason); }
1871
+ }