@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,278 @@
1
+ /**
2
+ * Activity contracts — the Stage 1 fix for chant #1288 ("Ops are the one
3
+ * place chant accepts untyped arguments and resolves names at runtime").
4
+ *
5
+ * Maintainer decision (#1288, 2026-08-25): a staged approach. Stage 1 (this
6
+ * module) is registered args/return schemas per activity, validated by
7
+ * `chant build` against every step — non-breaking, no change to
8
+ * {@link ActivityStep.args}'s `Record<string, unknown>` shape or the step
9
+ * builders. Stage 2 (a separate PR) regenerates the step builders as fully
10
+ * typed wrappers for editor completion and go-to-definition. #1289 (op.json
11
+ * IR) and #1290 (step-output references) build on these contracts — #1290
12
+ * specifically needs a declared `returns` schema to validate a later step's
13
+ * reference into an earlier one's output against, which is why `returns` is
14
+ * part of the shape here even though nothing in Stage 1 reads it for that.
15
+ *
16
+ * An activity declares a contract alongside its implementation — the same
17
+ * "registration surface" the issue asked for (`activity-registry.ts`'s
18
+ * `collectActivities` already does this for implementations; this is the
19
+ * schema-shaped sibling). `chant build` — via a lexicon's own post-synth
20
+ * check, e.g. the temporal lexicon's TMP012 — resolves each step's `fn`
21
+ * against a contract map built the same way and validates `args` and
22
+ * `outcomeAttribute.from`. A step whose `fn` has no registered contract is
23
+ * skipped (not an error): this is deliberately incremental — a lexicon opts
24
+ * an activity in by declaring a contract for it, and the k8s/aws/azure/gcp/
25
+ * fly activity sets are expected to pick this up lexicon by lexicon rather
26
+ * than all at once (see the issue's "worth checking this lands cleanly"
27
+ * note).
28
+ *
29
+ * Ownership is decentralized on purpose: each lexicon declares contracts for
30
+ * the activities it implements and validates them with its own post-synth
31
+ * check (the same `rulePrefix`-per-lexicon pattern every other check in
32
+ * chant already uses), rather than a shared cross-lexicon registry. A
33
+ * `Temporal::Op` step can call an activity contributed by any lexicon, and
34
+ * `PostSynthContext.entities` already carries the whole resolved graph to
35
+ * every lexicon's checks, so no new plumbing is needed for that to work.
36
+ */
37
+
38
+ import { z } from "zod";
39
+ import type { OpConfig, PhaseDefinition, ActivityStep, StepDefinition } from "./types";
40
+
41
+ /** Every literal value {@link ActivityStep.profile} may hold. Kept in sync with `types.ts`'s `ActivityStep["profile"]`. */
42
+ export const KNOWN_ACTIVITY_PROFILES = [
43
+ "fastIdempotent",
44
+ "longInfra",
45
+ "k8sWait",
46
+ "humanGate",
47
+ "argoSync",
48
+ "policyCheck",
49
+ ] as const;
50
+
51
+ const CONTRACT_BRAND = Symbol.for("chant.op.activityContract");
52
+
53
+ /**
54
+ * A registered activity's args/return schemas — the declaration that lets
55
+ * `chant build` catch a typo'd or mistyped step before it ever reaches a
56
+ * cluster.
57
+ *
58
+ * Author `args` with `z.strictObject(...)` (or an equivalent that rejects
59
+ * unrecognized keys), not `z.object(...)`. Zod's default `.object()` silently
60
+ * drops a key it doesn't recognize instead of failing — exactly the
61
+ * `helmInstall("api", "./chart", { nameSpace: "prod" })` failure class the
62
+ * issue names, where the misspelled key vanishes instead of erroring. A
63
+ * strict schema turns that into a build error.
64
+ */
65
+ export interface ActivityContract<Args = unknown, Return = unknown> {
66
+ readonly [CONTRACT_BRAND]: true;
67
+ /** The activity's registered name — must match a step's `fn`. */
68
+ name: string;
69
+ /** Schema every step's `args` (defaulted to `{}` when omitted) must satisfy. */
70
+ args: z.ZodType<Args>;
71
+ /**
72
+ * Schema the activity resolves to. Optional: an activity that returns
73
+ * nothing meaningful (or hasn't had its return type written down yet) can
74
+ * omit it. Needed to validate a step's `outcomeAttribute.from` path, and —
75
+ * per #1290 — a later step's reference into this one's output.
76
+ */
77
+ returns?: z.ZodType<Return>;
78
+ }
79
+
80
+ /** Declare an activity contract. */
81
+ export function activityContract<ArgsSchema extends z.ZodTypeAny, ReturnSchema extends z.ZodTypeAny = never>(
82
+ name: string,
83
+ args: ArgsSchema,
84
+ returns?: ReturnSchema,
85
+ ): ActivityContract<z.infer<ArgsSchema>, ReturnSchema extends z.ZodTypeAny ? z.infer<ReturnSchema> : unknown> {
86
+ return {
87
+ [CONTRACT_BRAND]: true,
88
+ name,
89
+ args,
90
+ ...(returns ? { returns } : {}),
91
+ } as ActivityContract<z.infer<ArgsSchema>, ReturnSchema extends z.ZodTypeAny ? z.infer<ReturnSchema> : unknown>;
92
+ }
93
+
94
+ /** Structural guard for a value produced by {@link activityContract}. */
95
+ export function isActivityContract(value: unknown): value is ActivityContract {
96
+ return typeof value === "object" && value !== null && (value as Record<symbol, unknown>)[CONTRACT_BRAND] === true;
97
+ }
98
+
99
+ /**
100
+ * Add every {@link ActivityContract} exported from an activity-contracts
101
+ * module to `into`, keyed by its declared `name` — the schema-shaped sibling
102
+ * of `activity-registry.ts`'s `collectActivities`.
103
+ */
104
+ export function collectActivityContracts(mod: Record<string, unknown>, into: Map<string, ActivityContract>): void {
105
+ for (const value of Object.values(mod)) {
106
+ if (isActivityContract(value)) into.set(value.name, value);
107
+ }
108
+ }
109
+
110
+ // ── Validation ──────────────────────────────────────────────────────────────
111
+
112
+ export interface ActivityContractIssue {
113
+ /** The Op the offending step belongs to. */
114
+ opName: string;
115
+ /** The phase the offending step belongs to. */
116
+ phase: string;
117
+ /** The activity name the offending step calls. */
118
+ fn: string;
119
+ /** Human-readable description of the mismatch. */
120
+ message: string;
121
+ }
122
+
123
+ /** Every `ActivityStep` in a phase, including ones nested inside an `EffectStep`. */
124
+ function activityStepsOf(steps: StepDefinition[]): ActivityStep[] {
125
+ return steps.flatMap((s) => (s.kind === "activity" ? [s] : s.kind === "effect" ? s.steps.filter((n) => n.kind === "activity") : []));
126
+ }
127
+
128
+ // Same global symbol `step-output-ref.ts` brands a `StepOutputRef` with —
129
+ // `Symbol.for(...)` interns by string key, so this recognizes one without
130
+ // importing that module (which itself imports `pathExistsInSchema` below;
131
+ // importing the other way would make the two files a cycle).
132
+ const STEP_OUTPUT_REF_BRAND = Symbol.for("chant.op.stepOutputRef");
133
+ function isStepOutputRefValue(value: unknown): boolean {
134
+ return typeof value === "object" && value !== null && (value as Record<symbol, unknown>)[STEP_OUTPUT_REF_BRAND] === true;
135
+ }
136
+
137
+ /** The value at `path` (a zod issue's `.path`) inside `obj`, or `undefined` if any segment doesn't resolve. */
138
+ function valueAtPath(obj: unknown, path: ReadonlyArray<PropertyKey>): unknown {
139
+ let current = obj;
140
+ for (const segment of path) {
141
+ if (current === null || typeof current !== "object") return undefined;
142
+ current = (current as Record<PropertyKey, unknown>)[segment];
143
+ }
144
+ return current;
145
+ }
146
+
147
+ /** Unwrap `ZodOptional`/`ZodNullable`/`ZodDefault` (and similar) down to the schema they wrap. */
148
+ function unwrap(schema: z.ZodTypeAny): z.ZodTypeAny {
149
+ let current = schema;
150
+ while (typeof (current as unknown as { unwrap?: () => z.ZodTypeAny }).unwrap === "function") {
151
+ current = (current as unknown as { unwrap: () => z.ZodTypeAny }).unwrap();
152
+ }
153
+ return current;
154
+ }
155
+
156
+ /**
157
+ * Does dot-path `path` resolve to a field that exists on `schema`? Object
158
+ * shapes only — a return schema whose root (or an intermediate segment) is
159
+ * a `z.record(...)`/`z.array(...)` rather than a `z.object(...)` hard-errors
160
+ * (returns `false`) instead of skipping, deliberately (chant #1290 comment
161
+ * on #1288's pre-merge review): a record's keys are dynamic and an array's
162
+ * elements are index-addressed, neither of which a dot-path segment can
163
+ * check against in any way that's more meaningful than "the author probably
164
+ * meant something else." No declared `returns` schema needs this today, so
165
+ * there's no live case to design against yet. The escape hatch is an empty
166
+ * path — `outcomeAttribute.from` omitted, or a {@link StepOutputRef}'s
167
+ * `path` omitted — which references the whole return value and never calls
168
+ * this function; a record/array-returning activity's whole value is always
169
+ * a valid reference target.
170
+ */
171
+ export function pathExistsInSchema(schema: z.ZodTypeAny, path: string): boolean {
172
+ return schemaAtPath(schema, path.split(".")) !== undefined;
173
+ }
174
+
175
+ /**
176
+ * The zod schema at property-key path `path` inside `schema`, walking
177
+ * through `z.ZodObject` shapes only (unwrapping optional/nullable/default at
178
+ * each level, same as {@link pathExistsInSchema}). `undefined` when a
179
+ * segment doesn't resolve, or an intermediate schema isn't a `z.ZodObject` —
180
+ * same record/array hard-stop {@link pathExistsInSchema} documents. An empty
181
+ * `path` returns `schema` itself (unwrapped).
182
+ */
183
+ export function schemaAtPath(schema: z.ZodTypeAny, path: ReadonlyArray<string>): z.ZodTypeAny | undefined {
184
+ let current = unwrap(schema);
185
+ for (const segment of path) {
186
+ if (!(current instanceof z.ZodObject)) return undefined;
187
+ const shape = current.shape as Record<string, z.ZodTypeAny>;
188
+ if (!(segment in shape)) return undefined;
189
+ current = unwrap(shape[segment]);
190
+ }
191
+ return current;
192
+ }
193
+
194
+ /**
195
+ * A primitive-shape classification of a zod schema — `string`/`number`/
196
+ * `boolean`/`object`/`array`, or `undefined` for anything else (a union,
197
+ * enum, literal, `z.any()`/`z.unknown()`, a transform, …). Used by the
198
+ * step-output-ref cross-contract type check (chant #1950-3) to compare a
199
+ * producer's declared return type against a consumer's declared arg type at
200
+ * the same structural position — deliberately shallow: it bails (returns
201
+ * `undefined`) on anything fancier than these five kinds rather than trying
202
+ * to reason about it, per that check's "bail out silently" design.
203
+ */
204
+ export type PrimitiveSchemaKind = "string" | "number" | "boolean" | "object" | "array";
205
+
206
+ export function primitiveKindOf(schema: z.ZodTypeAny): PrimitiveSchemaKind | undefined {
207
+ const s = unwrap(schema);
208
+ if (s instanceof z.ZodString) return "string";
209
+ if (s instanceof z.ZodNumber) return "number";
210
+ if (s instanceof z.ZodBoolean) return "boolean";
211
+ if (s instanceof z.ZodObject) return "object";
212
+ if (s instanceof z.ZodArray) return "array";
213
+ return undefined;
214
+ }
215
+
216
+ /**
217
+ * Validate every activity step in an Op's phases (main and `onFailure`)
218
+ * against a contract map. A step whose `fn` has no entry in `contracts` is
219
+ * skipped — Stage 1 is opt-in per activity, not a hard requirement that
220
+ * every activity have a declared contract.
221
+ *
222
+ * Catches the four failure classes chant #1288 names:
223
+ * - an unrecognized `profile` (checked against {@link KNOWN_ACTIVITY_PROFILES}, independent of whether `fn` has a contract),
224
+ * - an args key the declared schema doesn't recognize,
225
+ * - an args value of the wrong type (including a required key that's missing),
226
+ * - an `outcomeAttribute.from` path that can't exist on the declared return type.
227
+ */
228
+ export function validateActivitySteps(
229
+ config: Pick<OpConfig, "name" | "phases" | "onFailure">,
230
+ contracts: ReadonlyMap<string, ActivityContract>,
231
+ ): ActivityContractIssue[] {
232
+ const issues: ActivityContractIssue[] = [];
233
+ const phasesToWalk: PhaseDefinition[] = [...config.phases, ...(config.onFailure ?? [])];
234
+
235
+ for (const phase of phasesToWalk) {
236
+ for (const step of activityStepsOf(phase.steps)) {
237
+ if (step.profile && !(KNOWN_ACTIVITY_PROFILES as readonly string[]).includes(step.profile)) {
238
+ issues.push({
239
+ opName: config.name,
240
+ phase: phase.name,
241
+ fn: step.fn,
242
+ message: `unknown profile "${step.profile}" (known: ${KNOWN_ACTIVITY_PROFILES.join(", ")})`,
243
+ });
244
+ }
245
+
246
+ const contract = contracts.get(step.fn);
247
+ if (!contract) continue;
248
+
249
+ const parsed = contract.args.safeParse(step.args ?? {});
250
+ if (!parsed.success) {
251
+ for (const issue of parsed.error.issues) {
252
+ // A step-output reference (#1290) sitting at this path is a
253
+ // placeholder object at build time, not the value it will
254
+ // resolve to — so an args-schema type mismatch here is a false
255
+ // positive; TMP013 (`validateStepOutputRefs`) is what validates
256
+ // a reference, against the *producer's* declared return schema.
257
+ // An unrecognized-key issue's path is the parent object (`[]`
258
+ // for a top-level extra key), which is never itself a reference,
259
+ // so a genuinely misspelled key is still caught either way.
260
+ if (isStepOutputRefValue(valueAtPath(step.args, issue.path))) continue;
261
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(args)";
262
+ issues.push({ opName: config.name, phase: phase.name, fn: step.fn, message: `args.${path}: ${issue.message}` });
263
+ }
264
+ }
265
+
266
+ if (step.outcomeAttribute?.from && contract.returns && !pathExistsInSchema(contract.returns, step.outcomeAttribute.from)) {
267
+ issues.push({
268
+ opName: config.name,
269
+ phase: phase.name,
270
+ fn: step.fn,
271
+ message: `outcomeAttribute.from "${step.outcomeAttribute.from}" does not exist on "${step.fn}"'s declared return type`,
272
+ });
273
+ }
274
+ }
275
+ }
276
+
277
+ return issues;
278
+ }
@@ -50,7 +50,23 @@ describe("op builders are all reachable (#1715)", () => {
50
50
  "spriteApplyServices", "spriteTaskCreate", "spriteTaskRefresh",
51
51
  "spriteTaskRelease", "spritesUp", "spritesDown",
52
52
  ]);
53
- const expected = Object.keys(builders).filter((n) => !FLY_SURFACE.has(n));
53
+ // chant #1288 Stage 2 — these are the temporal lexicon's OWN activities
54
+ // (build.ts, shell.ts, wait.ts, ...), so its barrel re-exports a fully
55
+ // typed twin from its own `./op/builders` instead of core's untyped
56
+ // original — same name, same import path, deliberately not sourced from
57
+ // `@intentius/chant/op` any more. See `lexicons/temporal/src/index.ts`
58
+ // and `lexicons/temporal/src/op/builders.ts`.
59
+ const TEMPORAL_TYPED_SURFACE = new Set([
60
+ "build", "shell", "waitForStack", "lifecycleSnapshot", "teardown", "envTeardown", "httpCheck", "policyGate",
61
+ ]);
62
+ // chant #1288 Stage 2 — an internal helper (`takeProfile`'s sibling) for
63
+ // the lexicon-owned typed step-builder wrappers to reuse, not an
64
+ // Op-authoring primitive itself; nothing authors an Op with it directly,
65
+ // so it has no place in the Op-authoring barrel.
66
+ const INTERNAL_HELPERS = new Set(["takeProfileAndId"]);
67
+ const expected = Object.keys(builders).filter(
68
+ (n) => !FLY_SURFACE.has(n) && !TEMPORAL_TYPED_SURFACE.has(n) && !INTERNAL_HELPERS.has(n),
69
+ );
54
70
  const missing = expected.filter((name) => !reExported.has(name));
55
71
  expect(
56
72
  missing,
@@ -1,5 +1,19 @@
1
1
  import { OpResource } from "./resource";
2
- import type { OpConfig, PhaseDefinition, StepDefinition, ActivityStep, GateStep } from "./types";
2
+ import type { OpConfig, PhaseDefinition, StepDefinition, ActivityStep, GateStep, EffectStep } from "./types";
3
+ import { isEffectReceipt, type EffectReceiptDeclaration } from "../effect-receipt";
4
+ import { receiptCheckInput } from "./receipt-store";
5
+ import { makeOutProxy, type StepOutputRef } from "./step-output-ref";
6
+
7
+ /** An `activity()` result — the plain `ActivityStep` shape plus the `.out` reference sugar (#1290). */
8
+ export interface NamedActivityStep extends ActivityStep {
9
+ /**
10
+ * `step.out.someField` builds a {@link StepOutputRef} into this step's
11
+ * declared return value, for use in a later step's `args`. Throws on
12
+ * access if this step has no `id` (pass one via `activity()`'s third
13
+ * argument) — there is nothing for a reference to name otherwise.
14
+ */
15
+ readonly out: Record<string, StepOutputRef>;
16
+ }
3
17
 
4
18
  // ── Core builders ─────────────────────────────────────────────────────────────
5
19
 
@@ -31,18 +45,44 @@ export function phase(
31
45
  return { name, steps, ...(opts?.parallel ? { parallel: true } : {}) };
32
46
  }
33
47
 
34
- /** Reference a pre-built or custom activity by function name. */
48
+ /**
49
+ * Reference a pre-built or custom activity by function name.
50
+ *
51
+ * The third argument is a profile string (unchanged) or, to name the step
52
+ * so a later step can reference its output (#1290), an options object:
53
+ * `activity("lifecycleDiff", { env: "prod" }, { id: "diff" })`. Named or
54
+ * not, the result carries `.out` — `diff.out.driftedStacks` builds a
55
+ * reference to this step's declared return value; see `stepOutput()` in
56
+ * `@intentius/chant/op` for the equivalent on a step authored as a plain
57
+ * object literal instead of via this builder.
58
+ */
35
59
  export function activity(
36
60
  fn: string,
37
61
  args?: Record<string, unknown>,
38
- profile?: ActivityStep["profile"],
39
- ): ActivityStep {
40
- return {
62
+ opts?: ActivityStep["profile"] | { profile?: ActivityStep["profile"]; id?: string },
63
+ ): NamedActivityStep {
64
+ const profile = typeof opts === "string" ? opts : opts?.profile;
65
+ const id = typeof opts === "string" ? undefined : opts?.id;
66
+ const step = {
41
67
  kind: "activity",
42
68
  fn,
43
69
  ...(args && Object.keys(args).length > 0 ? { args } : {}),
44
70
  ...(profile ? { profile } : {}),
45
- };
71
+ ...(id ? { id } : {}),
72
+ } as NamedActivityStep;
73
+ Object.defineProperty(step, "out", {
74
+ enumerable: false,
75
+ get(): Record<string, StepOutputRef> {
76
+ if (!step.id) {
77
+ throw new Error(
78
+ `activity(${JSON.stringify(fn)}, ...).out: this step has no id — pass one via ` +
79
+ `activity(fn, args, { id: "..." }) before referencing its output.`,
80
+ );
81
+ }
82
+ return makeOutProxy(step.id);
83
+ },
84
+ });
85
+ return step;
46
86
  }
47
87
 
48
88
  /** Insert a human gate — the workflow pauses until the named signal is received. */
@@ -58,6 +98,62 @@ export function gate(
58
98
  };
59
99
  }
60
100
 
101
+ /**
102
+ * Wrap nested steps in read-compare-run-write over an effect receipt (#1834,
103
+ * epic #1703). At run: read the live receipt through the receipt store,
104
+ * compare it against the resolved expectation, and on a match skip the nested
105
+ * steps ("effect already applied"). On a mismatch the nested steps run in
106
+ * authored order, and ONLY when every one of them succeeds is the receipt
107
+ * written — last, once. This step is the sole writer of a receipt (decision
108
+ * 3); a nested-step failure leaves the receipt untouched (stale), so the next
109
+ * run re-proposes the effect.
110
+ *
111
+ * `receipt` is the typed EffectReceipt declaration — import the const from
112
+ * your receipts module. There is no string form: a receipt named by string
113
+ * would sever the step from the declaration that lint, plan, and the lexicon
114
+ * row all key on.
115
+ *
116
+ * A gate authored inside `steps` pauses only when the effect will fire (the
117
+ * matched path never reaches it). The receipt-store activities (`receiptRead`,
118
+ * `receiptWrite`) are provided by the receipt row's lexicon (#1835, aws).
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * import { seededReceipt } from "./receipts";
123
+ * phase("Seed", [
124
+ * effect(seededReceipt, [
125
+ * shell("npm run db:seed"),
126
+ * ]),
127
+ * ]),
128
+ * ```
129
+ */
130
+ export function effect(
131
+ receipt: EffectReceiptDeclaration,
132
+ steps: Array<ActivityStep | GateStep>,
133
+ opts?: { description?: string },
134
+ ): EffectStep {
135
+ if (!isEffectReceipt(receipt)) {
136
+ throw new Error(
137
+ "effect(): `receipt` must be the EffectReceipt declaration itself — import the const; there is no string form",
138
+ );
139
+ }
140
+ for (const step of steps) {
141
+ if ((step as { kind?: unknown }).kind === "effect") {
142
+ throw new Error(
143
+ `effect("${receipt.name}"): effect steps do not nest — one receipt witnesses one effect`,
144
+ );
145
+ }
146
+ }
147
+ const { receipt: ref, expectation } = receiptCheckInput(receipt);
148
+ return {
149
+ kind: "effect",
150
+ receipt: ref,
151
+ ...(expectation !== undefined ? { expectation } : {}),
152
+ steps,
153
+ ...(opts?.description ? { description: opts.description } : {}),
154
+ };
155
+ }
156
+
61
157
  // ── Pre-built activity shortcuts ──────────────────────────────────────────────
62
158
 
63
159
  /**
@@ -78,6 +174,22 @@ function takeProfile(
78
174
  return { args, profile };
79
175
  }
80
176
 
177
+ /**
178
+ * {@link takeProfile}'s sibling for the typed, lexicon-owned step-builder
179
+ * wrappers added in chant #1288 Stage 2 (e.g. `kubectlApply` in
180
+ * `@intentius/chant-lexicon-k8s`): also pulls `id` out of the opts bag so it
181
+ * routes to the step's `id` field (enabling `.out` — #1290) instead of
182
+ * leaking into the activity's args, the same way `profile` would without
183
+ * this split.
184
+ */
185
+ export function takeProfileAndId(
186
+ opts: (Record<string, unknown> & { profile?: ActivityStep["profile"]; id?: string }) | undefined,
187
+ ): { args: Record<string, unknown>; profile?: ActivityStep["profile"]; id?: string } {
188
+ if (!opts) return { args: {} };
189
+ const { profile, id, ...args } = opts as { profile?: ActivityStep["profile"]; id?: string } & Record<string, unknown>;
190
+ return { args, profile, id };
191
+ }
192
+
81
193
  /** Run an npm build script in the given project directory. `opts.script` selects the script (default `build`, e.g. `build:aws`); `opts.env` adds env vars. */
82
194
  export const build = (path: string, opts?: Record<string, unknown>): ActivityStep => {
83
195
  const { args, profile } = takeProfile(opts);
@@ -100,6 +212,22 @@ export const helmInstall = (
100
212
  return activity("helmInstall", { name, chart, ...args }, profile ?? "longInfra");
101
213
  };
102
214
 
215
+ /**
216
+ * Deploy a recorded pinned render by its `sha256:` content digest (chant
217
+ * #1242): the helm lexicon's `helmInstall` activity loads the render from
218
+ * the render store, verifies the digest, and installs those exact bytes as
219
+ * a structure-preserving wrapper chart — no deploy-time render. Defaults to
220
+ * the `longInfra` profile (override via `opts.profile`).
221
+ */
222
+ export const helmInstallPinned = (
223
+ name: string,
224
+ contentDigest: string,
225
+ opts?: { namespace?: string; profile?: ActivityStep["profile"]; [k: string]: unknown },
226
+ ): ActivityStep => {
227
+ const { args, profile } = takeProfile(opts);
228
+ return activity("helmInstall", { name, contentDigest, ...args }, profile ?? "longInfra");
229
+ };
230
+
103
231
  /** Poll for stack readiness (kubectl rollout, CloudFormation complete, etc). Defaults to the `k8sWait` profile (override via `opts.profile`). */
104
232
  export const waitForStack = (name: string, opts?: Record<string, unknown>): ActivityStep => {
105
233
  const { args, profile } = takeProfile(opts);
@@ -133,6 +261,28 @@ export const shell = (
133
261
  ): ActivityStep =>
134
262
  activity("shellCmd", { cmd, ...(opts?.env ? { env: opts.env } : {}) }, opts?.profile);
135
263
 
264
+ /**
265
+ * Ensure a `generated-once` secret exists in the target store (#1829, epic
266
+ * #1365). The op-step surface over the same engine as the `ensure-secret`
267
+ * capability verb (`ensureSecretMaterialization`, core's
268
+ * secret-materialization module): read-then-write — if the secret exists, its
269
+ * declared key-set and any declared `metadata` are verified and the step
270
+ * stops (present means done); if absent, one value per key is minted at
271
+ * apply time and written straight to the store. Never mints over an existing
272
+ * value, never rotates implicitly; a mismatch fails the step loudly, naming
273
+ * key names and metadata keys — never values. No output of the activity
274
+ * carries secret material. The backing activity is provided by the store's
275
+ * lexicon (k8s, #1830).
276
+ */
277
+ export const ensureSecret = (
278
+ name: string,
279
+ keys: string[],
280
+ opts?: { metadata?: Record<string, string>; profile?: ActivityStep["profile"]; [k: string]: unknown },
281
+ ): ActivityStep => {
282
+ const { args, profile } = takeProfile(opts);
283
+ return activity("ensureSecret", { name, keys, ...args }, profile);
284
+ };
285
+
136
286
  /** Run `chant teardown` in the given project directory. Uses `longInfra` profile. */
137
287
  export const teardown = (path: string): ActivityStep =>
138
288
  activity("chantTeardown", { path }, "longInfra");
@@ -200,6 +350,48 @@ export const k3dDown = (name: string, opts?: Record<string, unknown>): ActivityS
200
350
  return activity("k3dDown", { name, ...args }, profile ?? "fastIdempotent");
201
351
  };
202
352
 
353
+ /**
354
+ * Run the pinned k3s installer against a reachable host (`role`: `"server"`
355
+ * or `"agent"`). Idempotent on an already-installed matching version.
356
+ * Defaults to the `longInfra` profile (the installer downloads and starts
357
+ * the binary); override via `opts.profile`.
358
+ *
359
+ * The implementation lives in the k3s lexicon (chant #1601) — the project's
360
+ * `chant.config.ts` must list `"k3s"` in `lexicons` for the activity to load.
361
+ * Bounded exactly as `k3dUp`/`k3dDown` (chant #1410): drives the reachable-host
362
+ * case only, no host provisioning, no SSH orchestration.
363
+ *
364
+ * `opts` accepts `configFile` (required — path to the chant-emitted
365
+ * config.yaml, passed as `--config`), `version` (overrides the lexicon's
366
+ * pinned `INSTALL_K3S_VERSION`), and `tokenFile` (path to a file holding the
367
+ * join token, passed to the installer as `K3S_TOKEN_FILE`). There is no
368
+ * `token` option — the join secret's value never travels through this step;
369
+ * only a file path does (the #1601 token boundary, same stance as #1365).
370
+ */
371
+ export const k3sInstall = (
372
+ role: "server" | "agent",
373
+ opts: { configFile: string; version?: string; tokenFile?: string; profile?: ActivityStep["profile"]; [k: string]: unknown },
374
+ ): ActivityStep => {
375
+ const { args, profile } = takeProfile(opts);
376
+ return activity("k3sInstall", { role, ...args }, profile ?? "longInfra");
377
+ };
378
+
379
+ /**
380
+ * Uninstall k3s from a reachable host (`role`: `"server"` or `"agent"`).
381
+ * Defaults to the `fastIdempotent` profile (override via `opts.profile`).
382
+ * Implementation lives in the k3s lexicon (chant #1601) — requires `"k3s"`
383
+ * in the project's `lexicons`. A host where k3s was never installed is a
384
+ * no-op success, the same shape as `k3dDown` against an already-gone
385
+ * cluster.
386
+ */
387
+ export const k3sUninstall = (
388
+ role: "server" | "agent",
389
+ opts?: Record<string, unknown>,
390
+ ): ActivityStep => {
391
+ const { args, profile } = takeProfile(opts);
392
+ return activity("k3sUninstall", { role, ...args }, profile ?? "fastIdempotent");
393
+ };
394
+
203
395
  /**
204
396
  * Boot a local Floci AWS emulator in Docker and point subsequent steps at it —
205
397
  * sets `AWS_ENDPOINT_URL` + test creds in the process env so a following