@intentius/chant 0.46.0 → 0.50.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 (377) hide show
  1. package/dist/audit/catalog.d.ts +13 -3
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +30 -3
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +9 -2
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/fetch.d.ts.map +1 -1
  8. package/dist/audit/report-html.d.ts.map +1 -1
  9. package/dist/audit/report-model.d.ts +6 -0
  10. package/dist/audit/report-model.d.ts.map +1 -1
  11. package/dist/audit/report.d.ts.map +1 -1
  12. package/dist/audit/rules-doc.d.ts.map +1 -1
  13. package/dist/audit/secrets.d.ts +95 -0
  14. package/dist/audit/secrets.d.ts.map +1 -0
  15. package/dist/audit/wrangler.d.ts +33 -0
  16. package/dist/audit/wrangler.d.ts.map +1 -0
  17. package/dist/build.d.ts +3 -3
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/cli/commands/audit.d.ts +7 -0
  20. package/dist/cli/commands/audit.d.ts.map +1 -1
  21. package/dist/cli/commands/build.d.ts +23 -0
  22. package/dist/cli/commands/build.d.ts.map +1 -1
  23. package/dist/cli/commands/lint.d.ts.map +1 -1
  24. package/dist/cli/handlers/build.d.ts.map +1 -1
  25. package/dist/cli/handlers/components.d.ts +31 -0
  26. package/dist/cli/handlers/components.d.ts.map +1 -1
  27. package/dist/cli/handlers/lifecycle.d.ts +12 -1
  28. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  29. package/dist/cli/handlers/operator.d.ts +32 -0
  30. package/dist/cli/handlers/operator.d.ts.map +1 -0
  31. package/dist/cli/handlers/scenario.d.ts +39 -0
  32. package/dist/cli/handlers/scenario.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts.map +1 -1
  34. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  35. package/dist/cli/mcp/server.d.ts +35 -2
  36. package/dist/cli/mcp/server.d.ts.map +1 -1
  37. package/dist/cli/mcp/tools/explain.d.ts +6 -0
  38. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  39. package/dist/cli/mcp/types.d.ts +29 -1
  40. package/dist/cli/mcp/types.d.ts.map +1 -1
  41. package/dist/cli/plugins.d.ts +1 -1
  42. package/dist/cli/plugins.d.ts.map +1 -1
  43. package/dist/cli/registry.d.ts +14 -2
  44. package/dist/cli/registry.d.ts.map +1 -1
  45. package/dist/cli/reporters/stylish.d.ts +15 -1
  46. package/dist/cli/reporters/stylish.d.ts.map +1 -1
  47. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  48. package/dist/components/auto-release.d.ts +4 -0
  49. package/dist/components/auto-release.d.ts.map +1 -1
  50. package/dist/components/capability.d.ts +17 -2
  51. package/dist/components/capability.d.ts.map +1 -1
  52. package/dist/components/cli-support.d.ts +7 -0
  53. package/dist/components/cli-support.d.ts.map +1 -1
  54. package/dist/components/component.d.ts +15 -0
  55. package/dist/components/component.d.ts.map +1 -1
  56. package/dist/components/driver.d.ts.map +1 -1
  57. package/dist/components/starter-plugin.d.ts +2 -0
  58. package/dist/components/starter-plugin.d.ts.map +1 -1
  59. package/dist/components/verbs/ensure-secret.d.ts +50 -0
  60. package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
  61. package/dist/components/verbs/index.d.ts +13 -0
  62. package/dist/components/verbs/index.d.ts.map +1 -1
  63. package/dist/components/verbs/r2-sync.d.ts +76 -0
  64. package/dist/components/verbs/r2-sync.d.ts.map +1 -0
  65. package/dist/components/verbs/run-agent.d.ts +499 -0
  66. package/dist/components/verbs/run-agent.d.ts.map +1 -0
  67. package/dist/components/verbs/sign.d.ts +30 -0
  68. package/dist/components/verbs/sign.d.ts.map +1 -1
  69. package/dist/components/verbs/wrangler.d.ts +108 -0
  70. package/dist/components/verbs/wrangler.d.ts.map +1 -0
  71. package/dist/composite.d.ts +6 -1
  72. package/dist/composite.d.ts.map +1 -1
  73. package/dist/config.d.ts +26 -0
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/deep-observation.d.ts +14 -0
  76. package/dist/deep-observation.d.ts.map +1 -1
  77. package/dist/discovery/collect.d.ts.map +1 -1
  78. package/dist/discovery/fold-import.d.ts +15 -1
  79. package/dist/discovery/fold-import.d.ts.map +1 -1
  80. package/dist/discovery/fold-rank.d.ts +66 -0
  81. package/dist/discovery/fold-rank.d.ts.map +1 -0
  82. package/dist/discovery/index.d.ts +15 -0
  83. package/dist/discovery/index.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +17 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -0
  86. package/dist/effect-receipt.d.ts +177 -0
  87. package/dist/effect-receipt.d.ts.map +1 -0
  88. package/dist/fold/fold.d.ts +55 -2
  89. package/dist/fold/fold.d.ts.map +1 -1
  90. package/dist/fold/subset.d.ts +20 -0
  91. package/dist/fold/subset.d.ts.map +1 -1
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/lexicon-schema.d.ts +2 -0
  95. package/dist/lexicon-schema.d.ts.map +1 -1
  96. package/dist/lexicon.d.ts +137 -3
  97. package/dist/lexicon.d.ts.map +1 -1
  98. package/dist/lifecycle/change-set.d.ts +33 -5
  99. package/dist/lifecycle/change-set.d.ts.map +1 -1
  100. package/dist/lifecycle/converge-ledger.d.ts +90 -0
  101. package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
  102. package/dist/lifecycle/deep-diff.d.ts +18 -0
  103. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  104. package/dist/lifecycle/deep-observe.d.ts +9 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +33 -0
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
  108. package/dist/lifecycle/git.d.ts +145 -21
  109. package/dist/lifecycle/git.d.ts.map +1 -1
  110. package/dist/lifecycle/index.d.ts +6 -0
  111. package/dist/lifecycle/index.d.ts.map +1 -1
  112. package/dist/lifecycle/lease.d.ts +113 -0
  113. package/dist/lifecycle/lease.d.ts.map +1 -0
  114. package/dist/lifecycle/observation-baseline.d.ts +21 -3
  115. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  116. package/dist/lifecycle/receipt-plan.d.ts +62 -0
  117. package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
  118. package/dist/lifecycle/release-ledger.d.ts +20 -0
  119. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  120. package/dist/lifecycle/scenario-eval.d.ts +42 -0
  121. package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
  122. package/dist/lifecycle/scenario.d.ts +163 -0
  123. package/dist/lifecycle/scenario.d.ts.map +1 -0
  124. package/dist/lifecycle/symptoms.d.ts +63 -0
  125. package/dist/lifecycle/symptoms.d.ts.map +1 -0
  126. package/dist/lifecycle/teardown.d.ts +6 -4
  127. package/dist/lifecycle/teardown.d.ts.map +1 -1
  128. package/dist/lifecycle/unobserved-gate.d.ts +67 -0
  129. package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
  130. package/dist/lint/knowledge-checks.d.ts +48 -0
  131. package/dist/lint/knowledge-checks.d.ts.map +1 -0
  132. package/dist/lint/output-checks.d.ts +5 -0
  133. package/dist/lint/output-checks.d.ts.map +1 -0
  134. package/dist/lint/output-docs.d.ts +94 -0
  135. package/dist/lint/output-docs.d.ts.map +1 -0
  136. package/dist/lint/pipeline-change-gate.d.ts +101 -0
  137. package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
  138. package/dist/lint/post-synth.d.ts +41 -0
  139. package/dist/lint/post-synth.d.ts.map +1 -1
  140. package/dist/lint/receipt-checks.d.ts +9 -0
  141. package/dist/lint/receipt-checks.d.ts.map +1 -0
  142. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
  143. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
  144. package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
  145. package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
  146. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
  147. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
  148. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
  149. package/dist/lint/rules/index.d.ts +3 -1
  150. package/dist/lint/rules/index.d.ts.map +1 -1
  151. package/dist/lsp/lexicon-providers.d.ts +7 -0
  152. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  153. package/dist/okf-read.d.ts +78 -0
  154. package/dist/okf-read.d.ts.map +1 -0
  155. package/dist/op/activity-contract.d.ts +139 -0
  156. package/dist/op/activity-contract.d.ts.map +1 -0
  157. package/dist/op/builders.d.ts +140 -3
  158. package/dist/op/builders.d.ts.map +1 -1
  159. package/dist/op/converge-rule.d.ts +161 -0
  160. package/dist/op/converge-rule.d.ts.map +1 -0
  161. package/dist/op/generate-pipeline.d.ts +39 -0
  162. package/dist/op/generate-pipeline.d.ts.map +1 -0
  163. package/dist/op/index.d.ts +18 -2
  164. package/dist/op/index.d.ts.map +1 -1
  165. package/dist/op/local-executor.d.ts +2 -1
  166. package/dist/op/local-executor.d.ts.map +1 -1
  167. package/dist/op/op-verb-class.d.ts +42 -0
  168. package/dist/op/op-verb-class.d.ts.map +1 -0
  169. package/dist/op/operator.d.ts +128 -0
  170. package/dist/op/operator.d.ts.map +1 -0
  171. package/dist/op/receipt-store.d.ts +138 -0
  172. package/dist/op/receipt-store.d.ts.map +1 -0
  173. package/dist/op/step-output-ref.d.ts +187 -0
  174. package/dist/op/step-output-ref.d.ts.map +1 -0
  175. package/dist/op/types.d.ts +49 -2
  176. package/dist/op/types.d.ts.map +1 -1
  177. package/dist/provenance.d.ts +73 -3
  178. package/dist/provenance.d.ts.map +1 -1
  179. package/dist/runtime-adapter.d.ts +7 -1
  180. package/dist/runtime-adapter.d.ts.map +1 -1
  181. package/dist/secret-materialization.d.ts +138 -0
  182. package/dist/secret-materialization.d.ts.map +1 -0
  183. package/dist/secret-provenance.d.ts +218 -0
  184. package/dist/secret-provenance.d.ts.map +1 -0
  185. package/dist/serializer.d.ts +29 -0
  186. package/dist/serializer.d.ts.map +1 -1
  187. package/dist/toml.d.ts +40 -5
  188. package/dist/toml.d.ts.map +1 -1
  189. package/dist/yaml.d.ts.map +1 -1
  190. package/package.json +4 -1
  191. package/src/audit/catalog.test.ts +1 -1
  192. package/src/audit/catalog.ts +75 -3
  193. package/src/audit/core.test.ts +57 -0
  194. package/src/audit/core.ts +0 -0
  195. package/src/audit/detect-bundle.test.ts +1 -1
  196. package/src/audit/discover.test.ts +24 -0
  197. package/src/audit/discover.ts +40 -4
  198. package/src/audit/fetch.test.ts +216 -3
  199. package/src/audit/fetch.ts +270 -59
  200. package/src/audit/report-html.ts +5 -2
  201. package/src/audit/report-model.ts +9 -0
  202. package/src/audit/report.test.ts +22 -0
  203. package/src/audit/report.ts +3 -2
  204. package/src/audit/rules-doc.ts +13 -1
  205. package/src/audit/secrets.test.ts +303 -0
  206. package/src/audit/secrets.ts +406 -0
  207. package/src/audit/wrangler.test.ts +230 -0
  208. package/src/audit/wrangler.ts +290 -0
  209. package/src/build.test.ts +41 -0
  210. package/src/build.ts +39 -6
  211. package/src/cli/command-group.ts +1 -1
  212. package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
  213. package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
  214. package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
  215. package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
  216. package/src/cli/commands/audit.test.ts +268 -1
  217. package/src/cli/commands/audit.ts +87 -18
  218. package/src/cli/commands/build.test.ts +245 -0
  219. package/src/cli/commands/build.ts +210 -21
  220. package/src/cli/commands/lint.ts +15 -3
  221. package/src/cli/handlers/build.ts +2 -0
  222. package/src/cli/handlers/components.test.ts +199 -1
  223. package/src/cli/handlers/components.ts +160 -3
  224. package/src/cli/handlers/explain.test.ts +70 -1
  225. package/src/cli/handlers/graph.test.ts +20 -0
  226. package/src/cli/handlers/graph.ts +12 -3
  227. package/src/cli/handlers/lifecycle.test.ts +115 -1
  228. package/src/cli/handlers/lifecycle.ts +96 -15
  229. package/src/cli/handlers/operator.test.ts +255 -0
  230. package/src/cli/handlers/operator.ts +240 -0
  231. package/src/cli/handlers/scenario.test.ts +456 -0
  232. package/src/cli/handlers/scenario.ts +330 -0
  233. package/src/cli/main.test.ts +23 -0
  234. package/src/cli/main.ts +72 -1
  235. package/src/cli/mcp/resource-handlers.ts +38 -1
  236. package/src/cli/mcp/server.test.ts +323 -3
  237. package/src/cli/mcp/server.ts +84 -7
  238. package/src/cli/mcp/tools/explain.ts +51 -2
  239. package/src/cli/mcp/types.ts +27 -1
  240. package/src/cli/plugins.ts +4 -2
  241. package/src/cli/registry.ts +14 -2
  242. package/src/cli/reporters/stylish.test.ts +154 -0
  243. package/src/cli/reporters/stylish.ts +154 -33
  244. package/src/codegen/docs-rule-scanning.test.ts +42 -0
  245. package/src/codegen/docs-rule-scanning.ts +25 -2
  246. package/src/components/README.md +7 -0
  247. package/src/components/auto-release.ts +6 -0
  248. package/src/components/capability.ts +17 -2
  249. package/src/components/cli-support.test.ts +17 -0
  250. package/src/components/cli-support.ts +13 -1
  251. package/src/components/component-schema.test.ts +32 -0
  252. package/src/components/component.schema.json +6 -0
  253. package/src/components/component.test.ts +21 -0
  254. package/src/components/component.ts +15 -0
  255. package/src/components/driver.ts +12 -4
  256. package/src/components/registry.test.ts +7 -2
  257. package/src/components/starter-plugin.ts +17 -0
  258. package/src/components/verbs/ensure-secret.test.ts +130 -0
  259. package/src/components/verbs/ensure-secret.ts +79 -0
  260. package/src/components/verbs/index.ts +13 -0
  261. package/src/components/verbs/r2-sync.test.ts +107 -0
  262. package/src/components/verbs/r2-sync.ts +124 -0
  263. package/src/components/verbs/run-agent.test.ts +683 -0
  264. package/src/components/verbs/run-agent.ts +786 -0
  265. package/src/components/verbs/sign.test.ts +19 -0
  266. package/src/components/verbs/sign.ts +34 -2
  267. package/src/components/verbs/wrangler.test.ts +170 -0
  268. package/src/components/verbs/wrangler.ts +241 -0
  269. package/src/composite.ts +31 -2
  270. package/src/config.test.ts +15 -0
  271. package/src/config.ts +30 -0
  272. package/src/deep-observation.test.ts +19 -0
  273. package/src/deep-observation.ts +17 -0
  274. package/src/discovery/collect.ts +11 -2
  275. package/src/discovery/fold-import.test.ts +54 -0
  276. package/src/discovery/fold-import.ts +178 -38
  277. package/src/discovery/fold-rank.test.ts +197 -0
  278. package/src/discovery/fold-rank.ts +346 -0
  279. package/src/discovery/index.ts +16 -1
  280. package/src/discovery/param-deps.test.ts +118 -0
  281. package/src/discovery/param-deps.ts +170 -0
  282. package/src/effect-receipt-exclusion.test.ts +190 -0
  283. package/src/effect-receipt.test.ts +419 -0
  284. package/src/effect-receipt.ts +412 -0
  285. package/src/fold/fold.test.ts +6 -2
  286. package/src/fold/fold.ts +184 -3
  287. package/src/fold/subset.test.ts +95 -6
  288. package/src/fold/subset.ts +66 -2
  289. package/src/index.ts +4 -0
  290. package/src/lexicon-schema.ts +3 -0
  291. package/src/lexicon.ts +151 -5
  292. package/src/lifecycle/change-set.ts +46 -7
  293. package/src/lifecycle/converge-ledger.test.ts +199 -0
  294. package/src/lifecycle/converge-ledger.ts +179 -0
  295. package/src/lifecycle/deep-diff.test.ts +79 -1
  296. package/src/lifecycle/deep-diff.ts +23 -0
  297. package/src/lifecycle/deep-observe.ts +13 -2
  298. package/src/lifecycle/gate-ledger.test.ts +103 -0
  299. package/src/lifecycle/gate-ledger.ts +140 -0
  300. package/src/lifecycle/git.test.ts +430 -0
  301. package/src/lifecycle/git.ts +446 -84
  302. package/src/lifecycle/index.ts +6 -0
  303. package/src/lifecycle/lease.test.ts +343 -0
  304. package/src/lifecycle/lease.ts +270 -0
  305. package/src/lifecycle/observation-baseline.test.ts +46 -0
  306. package/src/lifecycle/observation-baseline.ts +33 -1
  307. package/src/lifecycle/receipt-plan.test.ts +250 -0
  308. package/src/lifecycle/receipt-plan.ts +249 -0
  309. package/src/lifecycle/release-ledger.ts +20 -0
  310. package/src/lifecycle/scenario-eval.test.ts +199 -0
  311. package/src/lifecycle/scenario-eval.ts +158 -0
  312. package/src/lifecycle/scenario.test.ts +195 -0
  313. package/src/lifecycle/scenario.ts +321 -0
  314. package/src/lifecycle/symptoms.test.ts +116 -0
  315. package/src/lifecycle/symptoms.ts +126 -0
  316. package/src/lifecycle/teardown.test.ts +31 -0
  317. package/src/lifecycle/teardown.ts +6 -4
  318. package/src/lifecycle/unobserved-gate.test.ts +109 -0
  319. package/src/lifecycle/unobserved-gate.ts +102 -0
  320. package/src/lint/knowledge-checks.test.ts +80 -0
  321. package/src/lint/knowledge-checks.ts +74 -0
  322. package/src/lint/output-checks.test.ts +85 -0
  323. package/src/lint/output-checks.ts +99 -0
  324. package/src/lint/output-docs.test.ts +220 -0
  325. package/src/lint/output-docs.ts +204 -0
  326. package/src/lint/pipeline-change-gate.test.ts +144 -0
  327. package/src/lint/pipeline-change-gate.ts +153 -0
  328. package/src/lint/post-synth.test.ts +97 -0
  329. package/src/lint/post-synth.ts +60 -0
  330. package/src/lint/receipt-checks.test.ts +101 -0
  331. package/src/lint/receipt-checks.ts +93 -0
  332. package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
  333. package/src/lint/rules/comp/comp.test.ts +49 -1
  334. package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
  335. package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
  336. package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
  337. package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
  338. package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
  339. package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
  340. package/src/lint/rules/index.ts +7 -1
  341. package/src/lsp/lexicon-providers.test.ts +44 -0
  342. package/src/lsp/lexicon-providers.ts +11 -1
  343. package/src/okf-read.test.ts +149 -0
  344. package/src/okf-read.ts +197 -0
  345. package/src/op/activity-contract.test.ts +180 -0
  346. package/src/op/activity-contract.ts +278 -0
  347. package/src/op/builders-exports.test.ts +17 -1
  348. package/src/op/builders.ts +198 -6
  349. package/src/op/converge-rule.test.ts +179 -0
  350. package/src/op/converge-rule.ts +311 -0
  351. package/src/op/effect-step.test.ts +311 -0
  352. package/src/op/generate-pipeline.test.ts +53 -0
  353. package/src/op/generate-pipeline.ts +99 -0
  354. package/src/op/index.ts +40 -3
  355. package/src/op/local-executor.test.ts +92 -0
  356. package/src/op/local-executor.ts +212 -29
  357. package/src/op/op-verb-class.test.ts +126 -0
  358. package/src/op/op-verb-class.ts +115 -0
  359. package/src/op/op.test.ts +25 -2
  360. package/src/op/operator.test.ts +346 -0
  361. package/src/op/operator.ts +213 -0
  362. package/src/op/receipt-store.ts +211 -0
  363. package/src/op/step-output-ref.test.ts +334 -0
  364. package/src/op/step-output-ref.ts +453 -0
  365. package/src/op/types.ts +51 -2
  366. package/src/provenance.test.ts +151 -4
  367. package/src/provenance.ts +118 -4
  368. package/src/runtime-adapter.ts +31 -10
  369. package/src/secret-materialization.test.ts +199 -0
  370. package/src/secret-materialization.ts +235 -0
  371. package/src/secret-provenance.test.ts +388 -0
  372. package/src/secret-provenance.ts +475 -0
  373. package/src/serializer.ts +30 -0
  374. package/src/toml.test.ts +157 -384
  375. package/src/toml.ts +371 -5
  376. package/src/yaml.test.ts +88 -0
  377. package/src/yaml.ts +76 -6
@@ -0,0 +1,786 @@
1
+ /**
2
+ * `run-agent` — phase 1 design: capability schema + registry entry (#1941,
3
+ * epic #1564 "run-agent as a bounded, attested component capability").
4
+ *
5
+ * **Decided (2026-08-25, maintainer comment on #1564).** The agent turn
6
+ * executes on a fly [Sprite](https://sprites.dev), via chant's own sprite
7
+ * lifecycle activities (`lexicons/fly/src/op/activities/sprites.ts` —
8
+ * `spriteCreate`/`spriteCheckpoint`/`spriteExec`/`spriteRestore`/`spriteDestroy`,
9
+ * plus the filesystem activities in `sprite-fs.ts` —
10
+ * `spriteWriteFile`/`spriteReadFile`), **not** fountain's hosted `fountainRun`
11
+ * path (`lexicons/fountain/src/op/activities/fountain-run.ts`): `fountainRun`
12
+ * polls a fountain-managed `Conversation` to a terminal HTTP status and gives
13
+ * chant no checkpoint control, but the epic's compensation story needs chant
14
+ * to own the sprite directly. Compensation is checkpoint-before/restore-on-unwind
15
+ * — "the environment is the transaction," no hand-written inverse action — the
16
+ * same pattern `examples/sprites-agent-task/ops/guarded-task.op.ts` already
17
+ * demonstrates at the Op layer. The offline path is `sprites-fake`/
18
+ * `sprites-emulator` (spritzer), the same `SPRITES_BASE_URL` endpoint-override
19
+ * story `sprites.ts` documents.
20
+ *
21
+ * **Input tracks fountain's `Agent` surface as it stands today**
22
+ * (`lexicons/fountain/src/spec/fountain-openapi.snapshot.json`): `agent` is
23
+ * resolved the same way `fountainRun`'s `resolveAgentId` resolves a name/id
24
+ * against `/api/agents`; `task` is `PromptRequest`-shaped (`prompt` +
25
+ * optional `images`, `ImageInput`'s exact shape). The output's `turn` facts
26
+ * track `Turn`'s shape (`status`/`exit_code`/`started_at`/`ended_at`), even
27
+ * though the turn runs on a chant-owned sprite rather than a fountain-managed
28
+ * `Sandbox` — fountain supplies the *declarative* facts (which model, which
29
+ * runtime, which egress policy via `Environment`), chant drives the
30
+ * *imperative* execution. Revisit only if `BinaryBourbon/fountain#586` (the
31
+ * `Estate` proposal) lands a breaking shape.
32
+ *
33
+ * **Placement.** Registered the same way `docker-build` is (./build.ts) — a
34
+ * factory over an injectable executor seam (`SpriteActivities` below, the
35
+ * `run-agent` analogue of ./cloud-executor.ts's `CloudExecutor`) — so core
36
+ * carries the typed schema without a hard package dependency on
37
+ * `@intentius/chant-lexicon-fly` (core stays cloud/vendor-agnostic in its
38
+ * dependency graph, see docs/components/cloud-boundary; the same reasoning
39
+ * that keeps the AWS leaves in `@intentius/chant-lexicon-aws` rather than
40
+ * here). Unlike the AWS leaves, there is today no fly-lexicon capability
41
+ * plugin for this verb to live in instead, and `run-agent` is a foundational,
42
+ * pipeline-level primitive in the same vein as `docker-build` or
43
+ * `wrangler-deploy` (./wrangler.ts) rather than a swappable per-cloud leaf —
44
+ * the same tradeoff `wrangler.ts`'s module doc weighs for the Cloudflare
45
+ * Workers verbs. Revisit if a broader agent-execution lexicon ever owns this
46
+ * seam instead.
47
+ *
48
+ * **Scope across phases.** Phase 1 (#1941) shipped the typed contract only —
49
+ * `run`/`rollback` were stubs throwing `CapabilityNotImplementedError`, per
50
+ * ../capability.ts's own module doc ("Verb implementations live under
51
+ * `./verbs/*` as typed stubs — no cloud calls, no side effects. Cloud
52
+ * implementations are a later phase"). `rollbackPolicy: "native"` was declared
53
+ * from phase 1 (so COMP003, ../lint/rules/comp/comp003-mutating-no-rollback.ts,
54
+ * never requires a `noRollback` opt-out for this verb once it composes into a
55
+ * component) even before the paired `rollback` body was real — the *policy*
56
+ * was the phase 1 design commitment, the *implementation* is phase 2 (#1942,
57
+ * below). This capability is deliberately not yet registered in any
58
+ * `CapabilityPlugin`'s `STARTER_VERB_FAMILIES`-equivalent set — the fly
59
+ * lexicon's own plugin (`lexicons/fly/src/components/capability-plugin.ts`)
60
+ * registers it under its own `FLY_VERB_FAMILIES`, but that is a separate,
61
+ * later decision from "does core's sequencing logic actually work," which
62
+ * this module answers. #1943 ("Provenance + attestation") owns the
63
+ * `provenance.sourceRef` transcript-hash basis and verify-gate interop; #1944
64
+ * owns the conformance/contract test suite (saga-unwind restore, COMP003
65
+ * refusal) beyond this module's own unit tests.
66
+ *
67
+ * **#1942 (phase 2, this module's `run`/`rollback` body) resolved the
68
+ * following:**
69
+ * - **The exec-throw finding** (pre-merge review of #1946, recorded on
70
+ * #1942): the real `spriteExec` (`lexicons/fly/src/op/activities/sprites.ts`)
71
+ * throws on any non-zero exit, so a *thin* `SpriteActivities.exec` adapter
72
+ * can only ever produce `turn.status: "failed"` without throwing if it
73
+ * reclassifies that throw itself. That reclassification is the fly
74
+ * lexicon adapter's job (`lexicons/fly/src/components/run-agent.ts`,
75
+ * option (a) from the review comment) — `SpriteActivities.exec`'s
76
+ * contract, as this module consumes it, is: **resolve** with `exitCode`
77
+ * for any ordinary command outcome (zero or non-zero), **reject** only for
78
+ * a genuine transport/infra failure. `run()` below trusts that contract
79
+ * completely: it never wraps `sprites.exec` in a try/catch, so a rejection
80
+ * from the injected `sprites` always propagates as a genuine `run()`
81
+ * failure (triggering saga rollback in ../driver.ts), while an ordinary
82
+ * non-zero exit becomes `turn.status: "failed"` on a normal return —
83
+ * exactly the non-throwing first-class result the type already declared.
84
+ * - **Rollback identity (#1944 closes the durable-path gap left open here).**
85
+ * `rollback(ctx, input, output)` receives the identical `input` object
86
+ * `run(ctx, input)` was called with, plus (since #1944) the exact `output`
87
+ * `run()` returned — `../capability.ts`'s `Capability.rollback` grew an
88
+ * optional third parameter for precisely this, and ../driver.ts's saga
89
+ * unwind (both the local in-process path and, via
90
+ * `lexicons/temporal/src/component-op/{activities,serializer}.ts`, the
91
+ * durable Temporal path) always threads it through. `run()` records the
92
+ * sprite id and exact pre-run checkpoint id two ways: in a private
93
+ * `WeakMap` keyed by the exact `input` object (works only when `rollback`
94
+ * is called with that same object — true in-process, e.g. this module's
95
+ * own tests calling `rollback()` directly after `run()`), and — the
96
+ * durable-safe channel — as `output.spriteId`/`output.checkpointId`,
97
+ * already part of `RunAgentOutput` (#1943). `rollback()` prefers `output`'s
98
+ * fields when present, falling back to the `WeakMap` only when a caller
99
+ * never threads `output` through (backward-compatible for any capability
100
+ * caller not yet passing it). **The checkpoint id, not the comment, is
101
+ * what `rollback()` restores by** when either source has one:
102
+ * `sprites.restore({ id, checkpoint: checkpointId })`, which
103
+ * `spriteRestore`'s explicit-id resolution wins over comment on
104
+ * (`lexicons/fly/src/op/activities/sprites.ts`). Restoring by comment alone
105
+ * is unsafe on a reused sprite — two `run()` calls sharing the default
106
+ * `"pre-run"` comment would make a comment-based restore always resolve to
107
+ * the *newest* matching checkpoint, so rolling back the first run would
108
+ * restore the second run's checkpoint (which already contains the first
109
+ * run's mutation) instead of undoing it. Comment-based restore
110
+ * (`workspace.checkpointComment`, default `"pre-run"`) is therefore only a
111
+ * **fallback**, used when no checkpoint id is available from either source
112
+ * (the `workspace.spriteName`-only path below). When there is no sprite id
113
+ * to restore at all (no `output`, no `WeakMap` record, no
114
+ * `workspace.spriteName`), `rollback()` degrades with an explicit,
115
+ * commented no-op return — the same pattern
116
+ * `../../lexicons/aws/src/components/host-delivery.ts`'s `code-deploy`
117
+ * rollback uses (`if (!deploymentId) return;`) — rather than throwing.
118
+ * **Before #1944**, the Temporal durable path (`run` and `rollback`
119
+ * executing as separate Activities, each rebuilding `input` fresh via
120
+ * `resolveStepInput`) never gave the `WeakMap` a hit, so a fresh sprite's
121
+ * rollback there silently degraded to that no-op; passing `output` through
122
+ * (this revision) closes that gap directly, without redesigning the
123
+ * component-op wire format — see #1944's PR description for why this was
124
+ * chosen over the "make the degrade loud" alternative. The generated
125
+ * workflow's saga-unwind loop (`lexicons/temporal/src/component-op/
126
+ * serializer.ts`) also no longer swallows a rollback failure silently
127
+ * (any capability's, not just this one) — it now logs it and surfaces it
128
+ * via a `RollbackFailed` search attribute, defense in depth for a rollback
129
+ * failure unrelated to identity (e.g. the sprite backend itself erroring).
130
+ * - **Destroy vs. leave-alive.** `run()` destroys a freshly created (not
131
+ * `workspace.spriteName`-reused) sprite only when `turn.status ===
132
+ * "completed"`. An ordinary failed turn leaves the sprite alive — it
133
+ * returned normally, so no saga rollback runs, but the failure is exactly
134
+ * when a caller (or this module's own tests) most wants to inspect the
135
+ * sprite or explicitly call `rollback()` to restore it, per this issue's
136
+ * acceptance criteria.
137
+ *
138
+ * **Still open, left for later phases:**
139
+ * - Runtime invocation inside the sprite — `buildRuntimeCommand` below picks
140
+ * a real one-shot CLI invocation for each known `Agent.runtime` value,
141
+ * reading the staged prompt file; whether the sprite image already carries
142
+ * that CLI (vs. `run()` needing a setup step) is unresolved. `RunAgentInput`
143
+ * (frozen by #1941) has no separate `runtime` field, so `input.agent`
144
+ * doubles as the runtime selector for phase 2 — an unrecognized value is
145
+ * passed through verbatim as a literal command (a documented escape hatch
146
+ * for a custom binary already present in the image, and how this module's
147
+ * own tests exercise a scripted failure against the offline fake). Real
148
+ * `agent` name/id -> `Agent.runtime` resolution against fountain's
149
+ * `/api/agents` (`resolveAgentId`, `lexicons/fountain/.../fountain-run.ts`)
150
+ * is out of this issue's scope (and would need a network call the offline
151
+ * path forbids).
152
+ * - Output artifact encoding — `run()` reads one conventional path
153
+ * (`/work/output`) as the sole `artifacts.files` entry when present;
154
+ * individual `spriteReadFile` calls per changed path vs. one tarred
155
+ * snapshot folded into a `BuildArchiveManifest` entry (./build-archive.ts)
156
+ * remains an open question, as does `artifacts.diff`, which `run()` never
157
+ * populates (the real Sprites API has no built-in diff endpoint).
158
+ * - Interrupted turns — `turn.status: "interrupted"` stays in the type but
159
+ * is unreachable from this implementation: any `sprites.exec` rejection
160
+ * (including one caused by an aborted signal) propagates as a genuine
161
+ * `run()` failure rather than being classified as an interrupted turn.
162
+ * Distinguishing "deadline hit mid-run" from "the sprite backend errored"
163
+ * well enough to surface `"interrupted"` safely is left for #1944.
164
+ *
165
+ * **#1943 (this revision) resolved the transcript-hash basis and
166
+ * sign/verify-gate interop, closing #1941's open "transcript hash basis"
167
+ * question:**
168
+ * - **Hash basis.** `provenance.sourceRef`'s digest component is
169
+ * `sha256Digest(JSON.stringify(basis))` over a fixed-key-order object —
170
+ * `{ agent, promptDigest, images, status, exitCode, stdoutDigest,
171
+ * stderrDigest, artifacts, diffDigest }` — built fresh by
172
+ * `computeTranscriptDigest` below every call, never re-ordered from a
173
+ * caller-supplied object, so key order (and therefore the digest) never
174
+ * drifts by construction. Every free-text field (`prompt`, `stdout`,
175
+ * `stderr`, `diff`, each image's `data`) is folded in as its own
176
+ * `sha256Digest`, not embedded verbatim — this keeps a (possibly large,
177
+ * possibly secret-bearing) prompt or transcript out of the digest input's
178
+ * own byte stream while the digest remains exactly as sensitive to those
179
+ * bytes as embedding them would be, and it is *why* this module never
180
+ * needs to retain raw stdout/stderr past computing their digest inline
181
+ * (closing #1941's "is the raw transcript retained anywhere beyond the
182
+ * attested digest" sub-question: no, not even internally). `startedAt`/
183
+ * `endedAt` are deliberately excluded — including wall-clock time would
184
+ * make "the same turn" (same prompt, same exec outcome, same artifacts)
185
+ * hash differently on every real run, defeating the determinism property
186
+ * (`same turn -> same sourceRef`) a verifier actually needs: identity is
187
+ * about *what happened*, not *when*. `artifacts.files` is sorted by
188
+ * `path` before hashing so collection order (never semantically
189
+ * meaningful — there is exactly one conventional artifact today, see
190
+ * `OUTPUT_PATH`) can't perturb the digest.
191
+ * - **`sourceRef` folding.** `RunAgentInput.sourceRef`'s own doc comment
192
+ * promises it is "folded into the output's `provenance.sourceRef`
193
+ * alongside the transcript hash." Concretely: `sourceRef =
194
+ * input.sourceRef ? \`${input.sourceRef}@${transcriptDigest}\` :
195
+ * transcriptDigest` — an `"<sha>@sha256:<hex>"` shape when a source ref is
196
+ * known, reading like the `repo@sha256:...` convention already used
197
+ * throughout this codebase (./sign.ts, ./publish.ts's `uri`), or the bare
198
+ * `sha256:<hex>` transcript digest alone when it is not. `@` (not `:`,
199
+ * which `DockerBuildInput.sourceRef`'s own `"<sha>:<path>"` convention
200
+ * already uses for a different purpose) keeps the split unambiguous.
201
+ * `extractTranscriptDigest` below recovers the trailing
202
+ * `sha256:<hex>` deterministically regardless of what `input.sourceRef`
203
+ * contains, by anchoring on the fixed `sha256:[0-9a-f]{64}` pattern at the
204
+ * very end of the string.
205
+ * - **Attestation interop, decided.** `RunAgentOutput` gained one field,
206
+ * `attestationRef` — a `"<component>/run-agent@sha256:<hex>"` string
207
+ * already shaped like the digest-qualified `repo@sha256:...` reference
208
+ * `./sign.ts`'s `assertDigestRef`/`./verify.ts` already require, built
209
+ * from `ctx.component` and the transcript digest, so a `sign`/
210
+ * `attest-provenance`/`verify` step composed after `run-agent` wires
211
+ * `imageRef: "@RunAgent.attestationRef"` and needs **zero code changes**
212
+ * to any of those three verbs — resolving design point 4 of #1943 exactly
213
+ * as anticipated. The honest caveat, stated plainly rather than left
214
+ * implicit: `attestationRef` is *shaped* like an OCI digest reference, but
215
+ * is not by itself a real, registry-resolvable one — `run-agent`'s turn
216
+ * output is never pushed anywhere by this module. A deployment that wants
217
+ * a genuine `cosign sign`/`cosign verify` round trip against real
218
+ * Rekor/Fulcio needs a registry-backed publish step ahead of `sign`
219
+ * (mirroring `publish-image`'s `uri`, ./publish.ts) — out of this issue's
220
+ * scope, and not needed for the offline contract this issue asks for
221
+ * (injected `ProcessRunner`, no real `cosign`/registry ever touched).
222
+ * `toRunAgentArchiveEntry` below folds the same digest into a
223
+ * `BuildArchiveEntry` (`kind: "asset"`, ./build-archive.ts) per design
224
+ * point 2, for a caller building a full archive manifest.
225
+ * - **Predicate-type decision, made (not left open).** Reuses the existing
226
+ * `predicateType: "https://slsa.dev/provenance/v1"` unchanged — does
227
+ * *not* mint a `run-agent`-specific predicate type. `buildDefinition
228
+ * .buildType` is the field SLSA v1 actually designates for "what kind of
229
+ * recipe produced this" (`RUN_AGENT_BUILD_TYPE` below,
230
+ * `"https://chant.dev/agent-turn/v1"`, the same role
231
+ * `./sign.ts`'s `DEFAULT_BUILD_TYPE` plays for `docker-build`), so a
232
+ * second, parallel `predicateType` taxonomy would duplicate what
233
+ * `buildType` already discriminates. This is also what makes the "zero
234
+ * code changes to `verify.ts`" claim above literally true:
235
+ * `buildVerifyAttestationArgs` hardcodes `--type slsaprovenance1` — a
236
+ * minted `predicateType` would have broken that interop outright, forcing
237
+ * a `verify.ts` change this issue's design point 4 explicitly hoped to
238
+ * avoid.
239
+ * - **Fountain-supplied facts, honestly scoped.** `buildRunAgentProvenanceStatement`
240
+ * below folds `input.agent` into `externalParameters` (the one *declared*
241
+ * fact `RunAgentInput` actually carries today) and `spriteId`/
242
+ * `checkpointId`/`turn.status`/`turn.exitCode` into `internalParameters`
243
+ * (what actually happened). `model`/`runtime`/`Environment.networking_type`
244
+ * /`allowed_hosts` are not included — populating them honestly needs
245
+ * fountain `Agent`/`Environment` resolution against `/api/agents`, which
246
+ * this module's own doc comment already marks out of scope ("Real `agent`
247
+ * name/id -> `Agent.runtime` resolution ... is out of this issue's
248
+ * scope"). Adding those keys later is additive (merged into
249
+ * `externalParameters`, never replacing it), not a shape break.
250
+ */
251
+
252
+ import { createHash } from "node:crypto";
253
+ import type { Capability, DeployContext } from "../capability";
254
+ import type { ProvenanceLink } from "./reproducibility";
255
+ import type { BuildArchiveEntry } from "./build-archive";
256
+ import { buildProvenanceStatement, type InTotoProvenanceStatement } from "./sign";
257
+
258
+ // ── fountain surface excerpts this input/output tracks ──────────────────────
259
+
260
+ /** fountain's `ImageInput` (`PromptRequest.images[]`) — a base64-encoded image attached to a prompt. */
261
+ export interface RunAgentImageInput {
262
+ /** Base64-encoded image bytes. */
263
+ data: string;
264
+ media_type: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
265
+ }
266
+
267
+ /** fountain's `Agent.runtime` — the CLI the turn runs inside the sprite. */
268
+ export type RunAgentRuntime = "claude" | "codex" | "gemini" | "opencode";
269
+
270
+ // ── run-agent ─────────────────────────────────────────────────────────────
271
+
272
+ export interface RunAgentInput {
273
+ /** fountain `Agent` name or id — resolved like `fountainRun`'s `resolveAgentId` (`lexicons/fountain/src/op/activities/fountain-run.ts`) against `/api/agents`. */
274
+ agent: string;
275
+ task: {
276
+ prompt: string;
277
+ /** fountain's `ImageInput` shape (`PromptRequest.images`). */
278
+ images?: RunAgentImageInput[];
279
+ };
280
+ workspace: {
281
+ /** Reuse an existing sprite (warm start). Omit to create a fresh sprite for this turn. */
282
+ spriteName?: string;
283
+ /** Base image for a freshly created sprite. Ignored when reusing `spriteName`. */
284
+ image?: string;
285
+ /** Checkpoint comment for the pre-run checkpoint `rollback()` restores to. Default: `"pre-run"`. */
286
+ checkpointComment?: string;
287
+ };
288
+ /** Folded into the output's `provenance.sourceRef` alongside the transcript hash — same field name `DockerBuildInput.sourceRef` already uses (#614, ./build.ts). Omit when unknown; no `provenance` basis beyond the transcript hash in that case. */
289
+ sourceRef?: string;
290
+ }
291
+
292
+ /** Mirrors fountain's `Turn` shape (`status`/`exit_code`/`started_at`/`ended_at`), even though the turn itself runs on a chant-owned sprite rather than a fountain-managed `Sandbox`. */
293
+ export interface RunAgentTurn {
294
+ status: "completed" | "failed" | "interrupted";
295
+ exitCode: number | null;
296
+ startedAt: string;
297
+ endedAt: string | null;
298
+ }
299
+
300
+ /** One artifact file the turn produced, content-addressed the same way a `BuildArchiveEntry` is (./build-archive.ts). */
301
+ export interface RunAgentArtifactFile {
302
+ path: string;
303
+ digest: string;
304
+ }
305
+
306
+ export interface RunAgentOutput {
307
+ /** The sprite this turn ran on — the same id `rollback()` restores. */
308
+ spriteId: string;
309
+ /** The pre-run checkpoint id — the same id `rollback()` restores to. */
310
+ checkpointId: string;
311
+ turn: RunAgentTurn;
312
+ artifacts: {
313
+ files: RunAgentArtifactFile[];
314
+ /** Unified diff of the workspace against its pre-run checkpoint, when applicable. */
315
+ diff?: string;
316
+ };
317
+ /** #614's shape (./reproducibility.ts) — `sourceRef` is the prompt/transcript hash, folded with `input.sourceRef` when supplied (see this module's doc comment, "sourceRef folding"). */
318
+ provenance: ProvenanceLink;
319
+ /**
320
+ * A `"<component>/run-agent@sha256:<hex>"` reference, digest-qualified the
321
+ * same shape `./sign.ts`'s `assertDigestRef`/`./verify.ts` require (#1943)
322
+ * — wire a `sign`/`attest-provenance`/`verify` step composed after
323
+ * `run-agent` with `imageRef: "@RunAgent.attestationRef"` and none of those
324
+ * three verbs need any code change. See this module's doc comment,
325
+ * "Attestation interop, decided" for the honest caveat (shaped like an OCI
326
+ * digest reference, not itself a registry-resolvable one) and
327
+ * `extractTranscriptDigest`/`toRunAgentArchiveEntry`/
328
+ * `buildRunAgentProvenanceStatement` below for the rest of the interop
329
+ * surface built on top of it.
330
+ */
331
+ attestationRef: string;
332
+ }
333
+
334
+ // ── SpriteActivities: the injectable sprite-lifecycle seam ─────────────────
335
+
336
+ /**
337
+ * The subset of `lexicons/fly/src/op/activities/sprites.ts` +
338
+ * `sprite-fs.ts`'s activity contracts `run-agent` needs, restated
339
+ * structurally here (rather than imported) so this module carries no hard
340
+ * package dependency on `@intentius/chant-lexicon-fly` — the same
341
+ * injectable-seam shape ./cloud-executor.ts's `CloudExecutor`/`DockerClient`
342
+ * use to keep ./build.ts's `docker-build` testable with no real `docker`
343
+ * daemon. A real implementation (#1942) is structurally compatible with
344
+ * `spriteCreate`/`spriteCheckpoint`/`spriteExec`/`spriteRestore`/
345
+ * `spriteDestroy`/`spriteWriteFile`/`spriteReadFile`'s existing signatures —
346
+ * adapting them is a thin wrapper, not a rewrite.
347
+ */
348
+ export interface SpriteActivities {
349
+ create(
350
+ args: { name: string; image?: string },
351
+ signal?: AbortSignal,
352
+ ): Promise<{ id: string; url: string }>;
353
+ checkpoint(
354
+ args: { id: string; comment?: string },
355
+ signal?: AbortSignal,
356
+ ): Promise<{ checkpointId: string }>;
357
+ exec(
358
+ args: { id: string; cmd: string; timeoutMs?: number },
359
+ signal?: AbortSignal,
360
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }>;
361
+ restore(
362
+ args: { id: string; checkpoint?: string; comment?: string },
363
+ signal?: AbortSignal,
364
+ ): Promise<void>;
365
+ destroy(args: { id: string }, signal?: AbortSignal): Promise<void>;
366
+ writeFile(
367
+ args: { id: string; path: string; content: string; mkdir?: boolean },
368
+ signal?: AbortSignal,
369
+ ): Promise<void>;
370
+ readFile(
371
+ args: { id: string; path: string },
372
+ signal?: AbortSignal,
373
+ ): Promise<{ content: string }>;
374
+ }
375
+
376
+ /**
377
+ * Thrown by `defaultSpriteActivities()`'s methods when a caller never injects
378
+ * a real (or fake) `SpriteActivities` — the default remains this
379
+ * not-wired-yet placeholder even after #1942 wires `run()`/`rollback()`'s
380
+ * sequencing logic, because *some* concrete backend still has to be supplied
381
+ * for that logic to have anything to call. `lexicons/fly/src/components/
382
+ * run-agent.ts`'s adapter (the real backend, over this lexicon's own sprite
383
+ * lifecycle activities) or the offline `sprites-fake`/`sprites-emulator` path
384
+ * are what a caller injects instead. A caller that supplies its own
385
+ * `SpriteActivities` never hits this class.
386
+ */
387
+ export class SpriteActivitiesNotWiredError extends Error {
388
+ constructor(public readonly method: keyof SpriteActivities) {
389
+ super(
390
+ `SpriteActivities.${method}: not wired to a real sprite backend yet — sprite lifecycle wiring is #1942 ` +
391
+ `(epic #1564 phase 2). Inject a real or fake ("sprites-fake"/"sprites-emulator") implementation to ` +
392
+ `exercise "run-agent" end to end.`,
393
+ );
394
+ this.name = "SpriteActivitiesNotWiredError";
395
+ }
396
+ }
397
+
398
+ /** Phase 1's placeholder default: every method throws `SpriteActivitiesNotWiredError`. See that class's doc comment. */
399
+ export function defaultSpriteActivities(): SpriteActivities {
400
+ const notWired =
401
+ <M extends keyof SpriteActivities>(method: M) =>
402
+ async (): Promise<never> => {
403
+ throw new SpriteActivitiesNotWiredError(method);
404
+ };
405
+ return {
406
+ create: notWired("create"),
407
+ checkpoint: notWired("checkpoint"),
408
+ exec: notWired("exec"),
409
+ restore: notWired("restore"),
410
+ destroy: notWired("destroy"),
411
+ writeFile: notWired("writeFile"),
412
+ readFile: notWired("readFile"),
413
+ };
414
+ }
415
+
416
+ // ── capability ───────────────────────────────────────────────────────────
417
+
418
+ /**
419
+ * Build the `run-agent` capability. `sprites` is the injectable
420
+ * `SpriteActivities` seam (default: `defaultSpriteActivities()`, the
421
+ * not-wired-yet placeholder — see that function's doc comment) so unit tests
422
+ * never touch a real/emulated sprite, mirroring `createDockerBuildCapability`'s
423
+ * `executor: CloudExecutor` parameter (./build.ts). The fly lexicon's
424
+ * `flyRunAgentCapability` (`lexicons/fly/src/components/run-agent.ts`) is
425
+ * this same factory called with its real `SpriteActivities` adapter.
426
+ *
427
+ * `run()`: `sprites.create` (skipped when reusing `workspace.spriteName`) ->
428
+ * `sprites.checkpoint` (`comment: workspace.checkpointComment ?? "pre-run"`)
429
+ * -> `sprites.writeFile` to stage the prompt -> `sprites.exec` the runtime
430
+ * command (`buildRuntimeCommand`) -> `sprites.readFile` to collect the sole
431
+ * artifact -> `sprites.destroy`, only for a freshly created sprite whose turn
432
+ * completed. See this module's doc comment for the exec-throw resolution,
433
+ * the destroy-vs-leave-alive rule, and the rollback-identity design.
434
+ *
435
+ * `rollback()`: `sprites.restore({ id: spriteId, checkpoint: checkpointId })`
436
+ * — the sole compensation, restoring to the exact pre-run checkpoint `run()`
437
+ * recorded (see this module's doc comment, "Rollback identity"). Prefers the
438
+ * `output` parameter's `spriteId`/`checkpointId` (the durable-safe channel,
439
+ * #1944) over the in-process `WeakMap`, falls back to
440
+ * `sprites.restore({ id: spriteId, comment })` only when neither source has a
441
+ * checkpoint id, and degrades to an explicit no-op when there is no sprite id
442
+ * to restore at all. No hand-written inverse action otherwise; "the
443
+ * environment is the transaction."
444
+ *
445
+ * `rollbackPolicy: "native"` is set explicitly (not left to
446
+ * ../capability.ts's `rollback`-method inference) so the design commitment
447
+ * reads directly off this capability's declaration: a mutating `run-agent`
448
+ * step never needs a `noRollback` opt-out for COMP003
449
+ * (../lint/rules/comp/comp003-mutating-no-rollback.ts).
450
+ */
451
+ export function createRunAgentCapability(
452
+ sprites: SpriteActivities = defaultSpriteActivities(),
453
+ ): Capability<RunAgentInput, RunAgentOutput> {
454
+ // Keyed by the exact `input` object `run()` was called with — a fallback
455
+ // for a caller that never threads `output` through to `rollback()` (see
456
+ // this module's doc comment, "Rollback identity"). The durable-safe path
457
+ // is `output.spriteId`/`output.checkpointId`, already part of
458
+ // `RunAgentOutput` (#1943) and threaded by every current caller (#1944).
459
+ const stateByInput = new WeakMap<RunAgentInput, RunAgentRunState>();
460
+
461
+ return {
462
+ kind: "run-agent",
463
+ rollbackPolicy: "native",
464
+ async run(ctx, input): Promise<RunAgentOutput> {
465
+ const reused = Boolean(input.workspace.spriteName);
466
+ const spriteId = input.workspace.spriteName ?? generateSpriteName(ctx);
467
+ if (!reused) {
468
+ await sprites.create({ name: spriteId, image: input.workspace.image });
469
+ }
470
+
471
+ const checkpointComment = input.workspace.checkpointComment ?? "pre-run";
472
+ const { checkpointId } = await sprites.checkpoint({ id: spriteId, comment: checkpointComment });
473
+ // Recorded as early as possible: even if a later step throws (a genuine
474
+ // infra failure, triggering saga rollback), `rollback()` still finds
475
+ // the sprite id and exact checkpoint it needs to restore.
476
+ stateByInput.set(input, { spriteId, checkpointId });
477
+
478
+ await sprites.writeFile({ id: spriteId, path: PROMPT_PATH, content: input.task.prompt, mkdir: true });
479
+
480
+ const startedAt = new Date().toISOString();
481
+ // No try/catch here by design (see this module's doc comment, "The
482
+ // exec-throw finding"): `sprites.exec` resolves with `exitCode` for any
483
+ // ordinary command outcome and rejects only for a genuine infra
484
+ // failure, which should propagate and trigger saga rollback.
485
+ const execResult = await sprites.exec({ id: spriteId, cmd: buildRuntimeCommand(input.agent) });
486
+ const turn: RunAgentTurn = {
487
+ status: execResult.exitCode === 0 ? "completed" : "failed",
488
+ exitCode: execResult.exitCode,
489
+ startedAt,
490
+ endedAt: new Date().toISOString(),
491
+ };
492
+
493
+ const artifacts = await collectArtifacts(sprites, spriteId);
494
+
495
+ // Destroy only on a normal-return success. A failed turn (status
496
+ // "failed") leaves the sprite alive — no saga rollback runs for it
497
+ // (run() returned, it didn't throw), so this is the caller's own
498
+ // window to inspect the sprite or explicitly call rollback().
499
+ if (!reused && turn.status === "completed") {
500
+ await sprites.destroy({ id: spriteId });
501
+ }
502
+
503
+ // #1943: the transcript digest — see this module's doc comment ("Hash
504
+ // basis") for the exact fields/order this is computed over.
505
+ // execResult.stdout/stderr never outlive this call: they are folded
506
+ // into the digest right here and discarded, never stored on `turn` or
507
+ // anywhere else in `RunAgentOutput`.
508
+ const transcriptDigest = computeTranscriptDigest({
509
+ agent: input.agent,
510
+ prompt: input.task.prompt,
511
+ images: input.task.images,
512
+ turn,
513
+ stdout: execResult.stdout,
514
+ stderr: execResult.stderr,
515
+ artifacts,
516
+ });
517
+ const sourceRef = input.sourceRef ? `${input.sourceRef}@${transcriptDigest}` : transcriptDigest;
518
+ const provenance: ProvenanceLink = {
519
+ sourceRef,
520
+ artifactDigest: artifacts.files[0]?.digest ?? EMPTY_DIGEST,
521
+ };
522
+ const attestationRef = `${ctx.component}/run-agent@${transcriptDigest}`;
523
+
524
+ return { spriteId, checkpointId, turn, artifacts, provenance, attestationRef };
525
+ },
526
+ async rollback(_ctx, input, output): Promise<void> {
527
+ // The durable-safe channel first (#1944): `output` is the exact value
528
+ // this step's own `run()` returned, threaded through by every current
529
+ // caller (../driver.ts locally, lexicons/temporal/src/component-op/
530
+ // {activities,serializer}.ts across the Activity boundary) — it
531
+ // survives even when `rollback()` is called with a freshly-rebuilt
532
+ // `input` object the in-process WeakMap below has never seen. Falls
533
+ // back to the WeakMap for a caller that predates/never threads
534
+ // `output` (see this module's doc comment, "Rollback identity").
535
+ const state = stateByInput.get(input);
536
+ const spriteId = output?.spriteId ?? state?.spriteId ?? input.workspace.spriteName;
537
+ if (!spriteId) {
538
+ // No identity from any source (output, in-process record, or
539
+ // "workspace.spriteName"): there is nothing to identify which sprite
540
+ // to restore. Degrade explicitly rather than throwing into a
541
+ // swallowed catch, the same pattern
542
+ // ../../lexicons/aws/src/components/host-delivery.ts's code-deploy
543
+ // rollback uses (`if (!deploymentId) return;`).
544
+ return;
545
+ }
546
+ // Restore the exact pre-run checkpoint when either source recorded
547
+ // one — wins over comment (see "Rollback identity"), and is the only
548
+ // safe choice on a reused sprite where two runs can share the same
549
+ // default "pre-run" comment.
550
+ const checkpointId = output?.checkpointId ?? state?.checkpointId;
551
+ if (checkpointId) {
552
+ await sprites.restore({ id: spriteId, checkpoint: checkpointId });
553
+ return;
554
+ }
555
+ // Fallback: no checkpoint id from either source (e.g.
556
+ // workspace.spriteName-only, no output, no in-process run() record) —
557
+ // resolve by comment, same as before.
558
+ const comment = input.workspace.checkpointComment ?? "pre-run";
559
+ await sprites.restore({ id: spriteId, comment });
560
+ },
561
+ };
562
+ }
563
+
564
+ // ── run() helpers ────────────────────────────────────────────────────────
565
+
566
+ /** The conventional path `run()` writes the staged prompt to and reads the sole artifact from — the same `/work/*` convention `examples/sprites-agent-task/ops/agent-task.op.ts`'s Stage/Collect phases use. */
567
+ const PROMPT_PATH = "/work/prompt";
568
+ const OUTPUT_PATH = "/work/output";
569
+
570
+ /** `sha256:<hex>` over a string, prefixed the same way ./build-archive.ts's `contentDigest` and ./build.ts's zip/jar digests are. */
571
+ function sha256Digest(content: string): string {
572
+ return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
573
+ }
574
+
575
+ const EMPTY_DIGEST = sha256Digest("");
576
+
577
+ // ── #1943: transcript hash + attestation interop ────────────────────────────
578
+
579
+ /**
580
+ * The exact, fixed-key-order shape `computeTranscriptDigest` hashes — see
581
+ * this module's doc comment ("Hash basis") for the full rationale. Every
582
+ * free-text field is its own digest, never embedded verbatim.
583
+ */
584
+ interface TranscriptBasis {
585
+ agent: string;
586
+ promptDigest: string;
587
+ images: Array<{ mediaType: string; digest: string }>;
588
+ status: RunAgentTurn["status"];
589
+ exitCode: number | null;
590
+ stdoutDigest: string;
591
+ stderrDigest: string;
592
+ artifacts: Array<{ path: string; digest: string }>;
593
+ diffDigest: string;
594
+ }
595
+
596
+ /**
597
+ * Compute the transcript digest that becomes (the digest component of)
598
+ * `provenance.sourceRef` and `attestationRef` — see this module's doc
599
+ * comment, "Hash basis," for the precise field list/order and the rationale
600
+ * for hashing rather than embedding each free-text field. Exported so a
601
+ * caller (or a test) can independently recompute/verify the same digest from
602
+ * the same run() inputs/outputs, without reaching into this module's private
603
+ * `run()` closure.
604
+ */
605
+ export function computeTranscriptDigest(params: {
606
+ agent: string;
607
+ prompt: string;
608
+ images?: RunAgentImageInput[];
609
+ turn: RunAgentTurn;
610
+ stdout: string;
611
+ stderr: string;
612
+ artifacts: RunAgentOutput["artifacts"];
613
+ }): string {
614
+ const basis: TranscriptBasis = {
615
+ agent: params.agent,
616
+ promptDigest: sha256Digest(params.prompt),
617
+ images: (params.images ?? []).map((img) => ({ mediaType: img.media_type, digest: sha256Digest(img.data) })),
618
+ status: params.turn.status,
619
+ exitCode: params.turn.exitCode,
620
+ stdoutDigest: sha256Digest(params.stdout),
621
+ stderrDigest: sha256Digest(params.stderr),
622
+ artifacts: [...params.artifacts.files]
623
+ // Code-point ordering, not localeCompare: the digest must be identical
624
+ // across machines regardless of ICU/locale configuration.
625
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
626
+ .map((f) => ({ path: f.path, digest: f.digest })),
627
+ diffDigest: sha256Digest(params.artifacts.diff ?? ""),
628
+ };
629
+ return sha256Digest(JSON.stringify(basis));
630
+ }
631
+
632
+ /** The trailing `sha256:<hex>` transcript digest a `provenance.sourceRef`/`attestationRef` ends in — see this module's doc comment, "sourceRef folding." */
633
+ const TRANSCRIPT_DIGEST_PATTERN = /sha256:[0-9a-f]{64}$/i;
634
+
635
+ /**
636
+ * Recover the bare transcript digest (`sha256:<hex>`) from a
637
+ * `provenance.sourceRef` or `attestationRef` string, regardless of what (if
638
+ * anything) precedes it — see this module's doc comment, "sourceRef
639
+ * folding," for why anchoring on the fixed `sha256:[0-9a-f]{64}` suffix
640
+ * pattern is unambiguous even when `input.sourceRef` itself contains `@`/`:`.
641
+ */
642
+ export function extractTranscriptDigest(sourceRef: string): string {
643
+ const match = TRANSCRIPT_DIGEST_PATTERN.exec(sourceRef);
644
+ if (!match) {
645
+ throw new Error(
646
+ `extractTranscriptDigest: "${sourceRef}" does not end in a "sha256:<hex>" transcript digest`,
647
+ );
648
+ }
649
+ return match[0];
650
+ }
651
+
652
+ /**
653
+ * `run-agent`'s SLSA `buildDefinition.buildType` — the recipe-kind URI this
654
+ * module's doc comment ("Predicate-type decision, made") settles on in place
655
+ * of minting a new `predicateType`. Mirrors ./sign.ts's own
656
+ * `DEFAULT_BUILD_TYPE` for `docker-build`.
657
+ */
658
+ export const RUN_AGENT_BUILD_TYPE = "https://chant.dev/agent-turn/v1";
659
+
660
+ /**
661
+ * Fold a `RunAgentOutput` into a `BuildArchiveEntry` (#1943 design point 2,
662
+ * ./build-archive.ts) — `kind: "asset"`, content-addressed by the same
663
+ * transcript digest `attestationRef` carries. Returns a bare entry (no
664
+ * `reproducibility` assigned); pass it through `addArchiveEntry` for the
665
+ * kind-appropriate `"best-effort"` default (./reproducibility.ts), the same
666
+ * as any other `asset` entry.
667
+ */
668
+ export function toRunAgentArchiveEntry(component: string, output: RunAgentOutput): BuildArchiveEntry {
669
+ return {
670
+ kind: "asset",
671
+ path: `run-agent/${component}/${output.spriteId}-turn.json`,
672
+ digest: extractTranscriptDigest(output.provenance.sourceRef),
673
+ mediaType: "application/json",
674
+ provenance: output.provenance,
675
+ };
676
+ }
677
+
678
+ /**
679
+ * Build the in-toto SLSA provenance statement for a completed turn, reusing
680
+ * ./sign.ts's `buildProvenanceStatement` unmodified in behavior for every
681
+ * existing (image) caller — `predicateType` stays
682
+ * `"https://slsa.dev/provenance/v1"`, only `buildType` and the
683
+ * `external`/`internalParameters` merge fields (#1943's minimal, documented
684
+ * extension to `BuildProvenanceStatementInput`) differ. See this module's
685
+ * doc comment, "Fountain-supplied facts, honestly scoped," for exactly what
686
+ * is (and is not yet) folded in. The returned statement's `subject` is
687
+ * `output.attestationRef` — sign+attach it the same way `attest-provenance`
688
+ * already does for any other digest-qualified reference.
689
+ */
690
+ export function buildRunAgentProvenanceStatement(
691
+ input: RunAgentInput,
692
+ output: RunAgentOutput,
693
+ builderId: string,
694
+ opts?: { finishedOn?: string; invocationId?: string },
695
+ ): InTotoProvenanceStatement {
696
+ return buildProvenanceStatement({
697
+ imageRef: output.attestationRef,
698
+ provenance: output.provenance,
699
+ builderId,
700
+ buildType: RUN_AGENT_BUILD_TYPE,
701
+ externalParameters: { agent: input.agent },
702
+ internalParameters: {
703
+ spriteId: output.spriteId,
704
+ checkpointId: output.checkpointId,
705
+ turnStatus: output.turn.status,
706
+ turnExitCode: output.turn.exitCode,
707
+ },
708
+ finishedOn: opts?.finishedOn ?? output.turn.endedAt ?? undefined,
709
+ invocationId: opts?.invocationId,
710
+ });
711
+ }
712
+
713
+ /**
714
+ * Map `RunAgentInput.agent` to the one-shot, non-interactive command that
715
+ * reads the staged prompt (`PROMPT_PATH`) and runs it inside the sprite. Each
716
+ * known `RunAgentRuntime` gets its real CLI invocation; any other value is
717
+ * passed through verbatim as a literal command — a real-world escape hatch
718
+ * for a custom binary already present in the sprite image, and how this
719
+ * module's own tests drive a scripted failure/success against the offline
720
+ * fake without needing a real CLI. See this module's doc comment ("Still
721
+ * open, left for later phases") for why no fountain resolution happens here.
722
+ */
723
+ export function buildRuntimeCommand(agent: string): string {
724
+ switch (agent as RunAgentRuntime) {
725
+ case "claude":
726
+ return `claude -p "$(cat ${PROMPT_PATH})" --output-format json`;
727
+ case "codex":
728
+ return `codex exec "$(cat ${PROMPT_PATH})"`;
729
+ case "gemini":
730
+ return `gemini -p "$(cat ${PROMPT_PATH})"`;
731
+ case "opencode":
732
+ return `opencode run "$(cat ${PROMPT_PATH})"`;
733
+ default:
734
+ return agent;
735
+ }
736
+ }
737
+
738
+ /** Deterministic-enough default sprite name when `workspace.spriteName` is omitted — not required to be reproducible across calls (see this module's doc comment on rollback identity: the `WeakMap` is what makes rollback work, not the name shape). */
739
+ function generateSpriteName(ctx: DeployContext): string {
740
+ const suffix = Math.random().toString(36).slice(2, 10);
741
+ return `run-agent-${ctx.component}-${suffix}`.replace(/[^a-zA-Z0-9._-]/g, "-");
742
+ }
743
+
744
+ /**
745
+ * Match the "not found" error shape `lexicons/fly/src/op/activities/
746
+ * sprite-fs.ts`'s `spriteReadFile` throws for a 404 specifically (`sprite
747
+ * <id> read <path>: not found`) — distinct from that same module's >=300
748
+ * `"... failed (<status>): ..."` shape, which is a genuine infra failure. The
749
+ * match is narrow on purpose, mirroring how `lexicons/fly/src/components/
750
+ * run-agent.ts`'s `parseSpriteExecFailure` only reclassifies the one thrown
751
+ * shape it recognizes and lets everything else through.
752
+ */
753
+ function isArtifactNotFoundError(err: unknown): boolean {
754
+ return err instanceof Error && /^sprite .+ read .+: not found$/.test(err.message);
755
+ }
756
+
757
+ /**
758
+ * Read `OUTPUT_PATH` as the turn's sole artifact, when present. Only a
759
+ * "not found" read (no artifact was ever written — e.g. a failed turn that
760
+ * never got that far) is treated as "no artifact"; any other error (a
761
+ * genuine transport/infra failure, or a real "failed (<status>)" from the
762
+ * sprite filesystem API) is rethrown so it propagates as a `run()` rejection
763
+ * and triggers saga rollback, instead of being silently swallowed into an
764
+ * empty `artifacts.files`.
765
+ */
766
+ async function collectArtifacts(
767
+ sprites: SpriteActivities,
768
+ spriteId: string,
769
+ ): Promise<RunAgentOutput["artifacts"]> {
770
+ try {
771
+ const { content } = await sprites.readFile({ id: spriteId, path: OUTPUT_PATH });
772
+ return { files: [{ path: OUTPUT_PATH, digest: sha256Digest(content) }] };
773
+ } catch (err) {
774
+ if (isArtifactNotFoundError(err)) return { files: [] };
775
+ throw err;
776
+ }
777
+ }
778
+
779
+ /** Per-`input` record of what `run()` did, so `rollback()` (called with the same `input` object — see this module's doc comment) can recover the sprite id and exact pre-run checkpoint id without any persisted state. */
780
+ interface RunAgentRunState {
781
+ spriteId: string;
782
+ checkpointId: string;
783
+ }
784
+
785
+ /** Default `run-agent` capability, backed by the not-wired-yet placeholder `SpriteActivities`. */
786
+ export const runAgentCapability: Capability<RunAgentInput, RunAgentOutput> = createRunAgentCapability();