@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
@@ -74,10 +74,12 @@ const SUPPORTED_CASES: SubsetCase[] = [
74
74
 
75
75
  const UNSUPPORTED_CASES: SubsetCase[] = [
76
76
  { name: "function call as a value", expr: `getName()` },
77
- { name: "method call as a value", expr: `config.getName()` },
78
- // chant #1082 — a registered helper NAME reached through a namespace is
79
- // still a method call, and still rejected by both sides.
80
- { name: "registered helper name reached as a method", expr: `helpers.phase("Apply", [])` },
77
+ // chant #1966 moved "method call as a value" and "registered helper name
78
+ // reached as a method" out of this strict-parity list and into "documented
79
+ // divergences" below — a method call now folds when its receiver resolves,
80
+ // which is exactly the identifier-resolution asymmetry every other
81
+ // divergence there already documents.
82
+ //
81
83
  // ...and a registered helper with an unfoldable argument is rejected on the
82
84
  // argument, by both sides, at the argument's own position.
83
85
  { name: "registered helper with an unfoldable argument", expr: `phase("Apply", [getName()])` },
@@ -258,6 +260,89 @@ describe("documented divergences — NOT unified by design (see subset.ts module
258
260
  expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
259
261
  });
260
262
 
263
+ test("method call on an unresolved receiver: fold rejects; EVL001 accepts the shape (chant #1966, resolution-dependent)", () => {
264
+ // `config.getName()` — chant #1966 widened `findSubsetViolation` to
265
+ // accept a method call unconditionally (mirroring `.step` and a nested
266
+ // `new` before it), because whether the receiver resolves to a value
267
+ // with that method is resolution-dependent (module doc, point 1) and out
268
+ // of reach for a syntax-only classifier. fold() still rejects it here on
269
+ // the receiver: `config` isn't a `const` this file has.
270
+ const source = `const bad = new Thing({ x: config.getName() });`;
271
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
272
+ const consts = collectConsts(sourceFile);
273
+ const badInit = consts.get("bad") as ts.NewExpression;
274
+
275
+ expect(() => foldResource(badInit, consts, [])).toThrow(FoldError);
276
+
277
+ const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
278
+ expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
279
+ });
280
+
281
+ test("registered helper name reached as a method: not the registered call, on either side — rejected for a different reason since #1966", () => {
282
+ // chant #1082 — a registered helper NAME reached through a namespace is
283
+ // still a method call, never the bare registered call: `fold()`'s
284
+ // `{__helper}` shape only ever matches a BARE identifier callee, so
285
+ // `helpers.phase(...)` never gets that treatment, before or after #1966.
286
+ // What changed is WHY it's rejected — `helpers` is an unresolved
287
+ // identifier now reached through the general method-call case, not "no
288
+ // case for a property-access callee" — same divergence direction as the
289
+ // test above.
290
+ const source = `const bad = new Thing({ x: helpers.phase("Apply", []) });`;
291
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
292
+ const consts = collectConsts(sourceFile);
293
+ const badInit = consts.get("bad") as ts.NewExpression;
294
+
295
+ let error: unknown;
296
+ try {
297
+ foldResource(badInit, consts, []);
298
+ } catch (e) {
299
+ error = e;
300
+ }
301
+ expect(error).toBeInstanceOf(FoldError);
302
+ expect((error as FoldError).message).toContain("unresolved identifier: helpers");
303
+
304
+ const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
305
+ expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
306
+ });
307
+
308
+ test("composite step access: NO LONGER a divergence (chant #1174) — both fold and EVL001 accept it", () => {
309
+ // `Checkout({...}).step` — the single-action Composite() wrapper idiom
310
+ // every lexicon's own docs/examples embed inline inside a Job's
311
+ // `steps:` array. This was a documented, EVL-only-permissive divergence
312
+ // from chant #1544 until #1174: `fold()` had no way to invoke an
313
+ // arbitrary composite factory, so it fell the file back to run
314
+ // (correct, not an error) while EVL001 already opted into treating the
315
+ // shape as valid via a since-removed `allowCompositeStepAccess` flag.
316
+ //
317
+ // fold() now folds this to a `{__compositeStep, args}` envelope
318
+ // (../fold/fold.ts) that ../discovery/fold-import.ts's bridge resolves
319
+ // through the folding file's own imports — interpreted when the callee
320
+ // is a project-file registered Composite (chant #1023), invoked for
321
+ // real otherwise (every lexicon-package composite, which is what
322
+ // `Checkout` is) — and reads `.step` off the REAL result. Kept as a
323
+ // test rather than deleted: the assertion that the divergence stays
324
+ // closed.
325
+ const source = `const bad = new Thing({ x: Checkout({}).step });`;
326
+ const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
327
+ const consts = collectConsts(sourceFile);
328
+ const badInit = consts.get("bad") as ts.NewExpression;
329
+
330
+ expect(foldResource(badInit, consts, [])).toEqual({
331
+ __resource: "Thing",
332
+ props: { x: { __compositeStep: "Checkout", args: [{}] } },
333
+ });
334
+
335
+ // findSubsetViolation (fold()'s own shared predicate, no opt-in needed
336
+ // anymore) agrees the shape is admissible.
337
+ const stepAccessExpr = (
338
+ (badInit.arguments![0] as ts.ObjectLiteralExpression).properties[0] as ts.PropertyAssignment
339
+ ).initializer;
340
+ expect(findSubsetViolation(stepAccessExpr)).toBeUndefined();
341
+
342
+ const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
343
+ expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
344
+ });
345
+
261
346
  test("nested resource construction: NO LONGER a divergence (chant #1169) — both fold and EVL001 accept it", () => {
262
347
  // This was the largest divergence in the table until #1169: a nested
263
348
  // `new Type()` as a property value could only fold to a {__resource, props}
@@ -352,10 +437,14 @@ describe("findSubsetViolation — optional intrinsic registry (#1044)", () => {
352
437
  expect(v?.message).toContain("function call as a value is not foldable: Ref(...)");
353
438
  });
354
439
 
355
- test("the registry doesn't widen anything else: a name in it without the opt-in, a method call, and .map all stay violations", () => {
440
+ test("the registry doesn't widen anything else: a name in it without the opt-in, a closure-taking method, and a plain call stay violations", () => {
356
441
  const notOptedIn: IntrinsicDef[] = [{ name: "Reference", isTag: false }];
357
442
  expect(findSubsetViolation(propValue(`Reference("db")`), notOptedIn)).toBeDefined();
358
- expect(findSubsetViolation(propValue(`aws.Ref("db")`), REF)).toBeDefined();
443
+ // chant #1966 — `aws.Ref("db")` is a method call (`Ref` reached through a
444
+ // namespace, not a bare identifier), so it's no longer here: shape-wise
445
+ // it's now the same resolution-dependent divergence
446
+ // "method call on an unresolved receiver" documents above, unrelated to
447
+ // whether `Ref` itself is opted into `foldsAsCall`.
359
448
  expect(findSubsetViolation(propValue(`cidrs.map((c) => c)`, `const cidrs = [];`), REF)).toBeDefined();
360
449
  expect(findSubsetViolation(propValue(`makeName("a")`), REF)).toBeDefined();
361
450
  });
@@ -1,6 +1,6 @@
1
1
  import * as ts from "typescript";
2
2
  import { isFoldableHelperName } from "./foldable-helpers";
3
- import { intrinsicCallFolds, type IntrinsicDef } from "../lexicon";
3
+ import { intrinsicCallFolds, intrinsicCallFoldsEagerly, type IntrinsicDef } from "../lexicon";
4
4
 
5
5
  /**
6
6
  * subset — the single canonical definition of chant's statically-foldable
@@ -265,6 +265,26 @@ function checkArrayElement(
265
265
  * environment-dependent exceptions). Returns the first (deepest,
266
266
  * `fold()`-evaluation-order) unsupported node, or `undefined` when `node`'s
267
267
  * whole shape is foldable.
268
+ *
269
+ * The `<call>(...).step` composite-consumer idiom (`Checkout({...}).step`,
270
+ * every lexicon's single-action `Composite()` wrapper embedded inline in a
271
+ * `Job`'s `steps` array — see composites.mdx) used to be an EVL-only,
272
+ * MORE-permissive divergence here (chant #1544's `allowCompositeStepAccess`
273
+ * opt-in parameter, since removed): `fold()` had no way to invoke an
274
+ * arbitrary composite factory, so it fell the file back to run for this
275
+ * shape (documented, correct behavior, never an error) while this shared
276
+ * predicate had to be told to stop treating that fallback as a lint error.
277
+ * chant #1174 closed the divergence — `fold()` now folds exactly this shape
278
+ * (narrowed to the literal member name `"step"`, the one idiom actually in
279
+ * use) — so this classifier accepts it unconditionally, the same way it
280
+ * already accepts a nested `new Type(...)` unconditionally since #1169.
281
+ *
282
+ * chant #1966 adds a CallExpression whose callee is a property access —
283
+ * `github.actor.toString()`, `matrix("os").toString()`, `[...].join(",")` —
284
+ * to the same unconditional-acceptance set: whether the receiver actually
285
+ * folds to a value with a callable method of that name is resolution-
286
+ * dependent (module doc, point 1), so this classifier stays permissive and
287
+ * only recurses into shape.
268
288
  */
269
289
  export function findSubsetViolation(
270
290
  node: ts.Node,
@@ -334,6 +354,13 @@ export function findSubsetViolation(
334
354
  }
335
355
 
336
356
  if (ts.isPropertyAccessExpression(node)) {
357
+ // chant #1174 — see this function's doc comment. `<call>(...).step`
358
+ // (any callee, any argument shape) is the composite-consumer idiom, and
359
+ // `fold()` now folds it too (narrowed to the literal member `"step"` —
360
+ // see fold.ts's PropertyAccessExpression branch).
361
+ if (node.name.text === "step" && ts.isCallExpression(node.expression)) {
362
+ return undefined;
363
+ }
337
364
  return findSubsetViolation(node.expression, intrinsics);
338
365
  }
339
366
 
@@ -358,7 +385,10 @@ export function findSubsetViolation(
358
385
  }
359
386
  // Flow-insensitive — see module doc: fold() short-circuits &&/||/?? and
360
387
  // only evaluates the taken side; EVL requires both sides shape-valid.
361
- return findSubsetViolation(node.left, intrinsics) ?? findSubsetViolation(node.right, intrinsics);
388
+ return (
389
+ findSubsetViolation(node.left, intrinsics) ??
390
+ findSubsetViolation(node.right, intrinsics)
391
+ );
362
392
  }
363
393
 
364
394
  if (ts.isConditionalExpression(node)) {
@@ -426,6 +456,40 @@ export function findSubsetViolation(
426
456
  return undefined;
427
457
  }
428
458
 
459
+ // chant #1966 — the eager counterpart of the #1044 case above: a lexicon
460
+ // intrinsic registered with `foldsEagerly` instead of `foldsAsCall`
461
+ // ({@link intrinsicCallFoldsEagerly}, ../lexicon.ts). Same registry-gated
462
+ // shape, same reasoning.
463
+ if (
464
+ intrinsics &&
465
+ ts.isIdentifier(node.expression) &&
466
+ intrinsics.some((i) => i.name === (node.expression as ts.Identifier).text && intrinsicCallFoldsEagerly(i))
467
+ ) {
468
+ for (const arg of node.arguments) {
469
+ const v = findSubsetViolation(arg, intrinsics);
470
+ if (v) return v;
471
+ }
472
+ return undefined;
473
+ }
474
+
475
+ // chant #1966 — a method call (`github.actor.toString()`, `[...].join(",")`,
476
+ // `matrix("os").toString()`): `fold()` now folds this shape unconditionally
477
+ // — the receiver must fold to a real value with the named method, which is
478
+ // resolution-dependent (module doc, point 1) and so out of reach for a
479
+ // syntax-only classifier, exactly the same asymmetry as `.step` above and
480
+ // a same-file `new` used as a value (#1169). Accepted here regardless of
481
+ // the intrinsics registry, unlike the two cases just above: nothing about
482
+ // whether a method call folds depends on a lexicon's registration.
483
+ if (ts.isPropertyAccessExpression(node.expression)) {
484
+ const v = findSubsetViolation(node.expression.expression, intrinsics);
485
+ if (v) return v;
486
+ for (const arg of node.arguments) {
487
+ const argViolation = findSubsetViolation(arg, intrinsics);
488
+ if (argViolation) return argViolation;
489
+ }
490
+ return undefined;
491
+ }
492
+
429
493
  return violation(node, callExpressionMessage(node));
430
494
  }
431
495
 
package/src/index.ts CHANGED
@@ -7,6 +7,9 @@
7
7
  export * from "./declarable";
8
8
  export * from "./composite";
9
9
  export * from "./provenance";
10
+ export * from "./secret-provenance";
11
+ export * from "./secret-materialization";
12
+ export * from "./effect-receipt";
10
13
  export * from "./build-params";
11
14
  export * from "./intrinsic";
12
15
  export * from "./types";
@@ -43,6 +46,7 @@ export * from "./lint/selectors";
43
46
  export * from "./lint/named-checks";
44
47
  export * from "./lint/post-synth";
45
48
  export * from "./lint/policy";
49
+ export * from "./lint/pipeline-change-gate";
46
50
  export * from "./lint/rule-loader";
47
51
  export * from "./lint/discover";
48
52
  export * from "./import/parser";
@@ -21,6 +21,9 @@ export const IntrinsicDefSchema = z.object({
21
21
  // registration written before #1044 means. Only an explicit `true` opts a
22
22
  // call into folding. See IntrinsicDef.foldsAsCall in ../lexicon.ts.
23
23
  foldsAsCall: z.boolean().optional(),
24
+ // chant #1966 — same default-off shape as foldsAsCall, for the EAGER
25
+ // call-form opt-in. See IntrinsicDef.foldsEagerly in ../lexicon.ts.
26
+ foldsEagerly: z.boolean().optional(),
24
27
  });
25
28
 
26
29
  // ---------------------------------------------------------------------------
package/src/lexicon.ts CHANGED
@@ -226,6 +226,29 @@ export interface IntrinsicDef {
226
226
  * run path would have called, from the module the source itself named.
227
227
  */
228
228
  readonly foldsAsCall?: boolean;
229
+ /**
230
+ * chant #1966 — opt this intrinsic's PLAIN-CALL form into EAGER folding:
231
+ * unlike {@link foldsAsCall}, which defers to a `{__intrinsic}` envelope
232
+ * revived later, `fold()` (../fold/fold.ts) calls the real function
233
+ * immediately, with the folded arguments, and its real return value IS the
234
+ * folded value.
235
+ *
236
+ * Exists for a lexicon helper whose result is commonly embedded directly in
237
+ * a template literal (`` `${matrix("os")}` ``, github's
238
+ * `matrix`/`inputs`/…) — an envelope is still a plain `{__intrinsic, args}`
239
+ * object when the enclosing template folds, so `String()` coercion at fold
240
+ * time would stringify it as `"[object Object]"` instead of the real value.
241
+ * Eager evaluation produces the real, already-live result instead.
242
+ *
243
+ * Same admission bar as {@link foldsAsCall} (a pure function of its
244
+ * arguments — no I/O, no mutable state, no dependency on anything but its
245
+ * arguments) and mutually exclusive with both {@link isTag} and
246
+ * `foldsAsCall`: a plain call folds exactly one way.
247
+ * `../discovery/fold-import.ts`'s `resolveActiveLexiconExport` checks this
248
+ * to admit the function export into `externals` at all — a lexicon
249
+ * package's function export is excluded there otherwise.
250
+ */
251
+ readonly foldsEagerly?: boolean;
229
252
  }
230
253
 
231
254
  /**
@@ -257,8 +280,8 @@ export interface IntrinsicDef {
257
280
  * `dist/manifest.json`) that may predate either field. `undefined` there
258
281
  * means what it always did — not a tag, not opted in.
259
282
  */
260
- export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean }): boolean {
261
- return intrinsicTagFolds(def) || intrinsicCallFolds(def);
283
+ export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean; foldsEagerly?: boolean }): boolean {
284
+ return intrinsicTagFolds(def) || intrinsicCallFolds(def) || intrinsicCallFoldsEagerly(def);
262
285
  }
263
286
 
264
287
  /** True when this intrinsic's TAGGED-TEMPLATE form folds (`Sub\`...\``) — see {@link intrinsicFolds}. */
@@ -280,6 +303,15 @@ export function intrinsicCallFolds(def: { isTag?: boolean; foldsAsCall?: boolean
280
303
  return def.isTag !== true && def.foldsAsCall === true;
281
304
  }
282
305
 
306
+ /**
307
+ * True when this intrinsic's PLAIN-CALL form folds EAGERLY (chant #1966) —
308
+ * i.e. the lexicon opted it in via {@link IntrinsicDef.foldsEagerly}.
309
+ * `isTag: true` disqualifies, same as {@link intrinsicCallFolds}.
310
+ */
311
+ export function intrinsicCallFoldsEagerly(def: { isTag?: boolean; foldsEagerly?: boolean }): boolean {
312
+ return def.isTag !== true && def.foldsEagerly === true;
313
+ }
314
+
283
315
  /**
284
316
  * Options passed to a MigrationSource by `chant migrate`.
285
317
  */
@@ -406,6 +438,61 @@ export interface ComponentPipelineResult {
406
438
  jobs: ComponentPipelineJob[];
407
439
  }
408
440
 
441
+ /**
442
+ * Finding-mode a scheduled Op surfaces on drift — the vocabulary the
443
+ * temporal-lexicon composites (`WorkflowAuditOp`/`PipelineAuditOp`/
444
+ * `ReconcileOp`) already declare on the Op itself (#927). The mode is baked
445
+ * into the Op's own activity args at build time and is never re-passed on the
446
+ * generated CI invocation; here it decides only what token/permission wiring
447
+ * the generated job needs to act on a finding — elevated write access for
448
+ * `issue`/`pull-request`/`merge-request`, none for `report`.
449
+ */
450
+ export type OpFindingMode = "report" | "issue" | "pull-request" | "merge-request";
451
+
452
+ /** One scheduled Op to generate CI for — the cron-triggered counterpart to a component (generate mode). */
453
+ export interface ScheduledOpSpec {
454
+ /** Op name (`*.op.ts`'s `Op({ name })`) — what `chant run <name>` targets. */
455
+ name: string;
456
+ /** Cron expression driving the CI-native trigger — the CI-only alternative to a Temporal `TemporalSchedule`. */
457
+ schedule: string;
458
+ /** This Op's finding-mode, for permission/token wiring only (see {@link OpFindingMode}). Default: "report" — no elevated permissions. */
459
+ findingMode?: OpFindingMode;
460
+ }
461
+
462
+ /** One CI job generated for a scheduled Op. */
463
+ export interface OpPipelineJob {
464
+ /** CI job name (safe as a YAML key). */
465
+ jobName: string;
466
+ /** The Op this job runs. */
467
+ op: string;
468
+ /** Cron expression this job's workflow is scheduled on. */
469
+ schedule: string;
470
+ /** The finding-mode wired for this job. */
471
+ findingMode: OpFindingMode;
472
+ }
473
+
474
+ /**
475
+ * One generated CI file for a scheduled Op. Unlike a component pipeline —
476
+ * one combined document for the whole graph — a cron trigger is
477
+ * workflow-scoped in every CI provider chant targets (GitHub Actions'
478
+ * `on.schedule`, a GitLab pipeline schedule bound to one `.gitlab-ci.yml`), so
479
+ * each scheduled Op gets its own file.
480
+ */
481
+ export interface OpPipelineFile {
482
+ /** Suggested file name (e.g. `<op-name>.yml`), relative to the provider's workflow directory. */
483
+ name: string;
484
+ /** The synthesized CI YAML for this Op. */
485
+ yaml: string;
486
+ }
487
+
488
+ /** The synthesized CI pipeline for a set of scheduled Ops (generate mode's Op counterpart, #927). */
489
+ export interface OpPipelineResult {
490
+ /** One file per scheduled Op. */
491
+ files: OpPipelineFile[];
492
+ /** Every generated job, in emit order — one per Op. */
493
+ jobs: OpPipelineJob[];
494
+ }
495
+
409
496
  /**
410
497
  * Live status of a single deploy unit (a CloudFormation stack, a K8s release, …)
411
498
  * addressed by its deployed name — the per-component presence signal
@@ -439,6 +526,24 @@ export interface BuildRootContext {
439
526
  projectRoot: string;
440
527
  /** The resolved project configuration, for the lexicon's own namespace. */
441
528
  config: Record<string, unknown>;
529
+ /**
530
+ * The discovered entities, read-only (#1828 / SOPS provenance). A
531
+ * contributor that reacts to what the project DECLARED — rather than to
532
+ * what its config listed — reads them here: the committed-encrypted
533
+ * `declareSecret()` hook resolves each declaration's `file` into a sidecar
534
+ * entity, and any future declaration-driven contributor needs the same.
535
+ *
536
+ * Read-only on purpose. The merge that follows is the only thing that adds
537
+ * to the entity set, and it refuses a name collision rather than
538
+ * overwriting; a contributor mutating the map directly would slip past
539
+ * that. Contributors run in order, so a contributor also sees entities
540
+ * earlier contributors added.
541
+ *
542
+ * Optional: a caller that has no entity set (a plugin hook invoked
543
+ * directly, a graph mode that never discovered) omits it, and a hook must
544
+ * treat an absent map as an empty one.
545
+ */
546
+ entities?: ReadonlyMap<string, Declarable>;
442
547
  }
443
548
 
444
549
  /**
@@ -450,6 +555,17 @@ export interface BuildRootContribution {
450
555
  warnings?: string[];
451
556
  }
452
557
 
558
+ /**
559
+ * A plugin's `buildRoots` hook, already bound to this invocation's project
560
+ * root and config (`collectBuildRootContributors`, ./cli/plugins.ts). What is
561
+ * NOT bindable that early is the entity set — discovery has not run yet — so
562
+ * the merge supplies it when it calls the closure. A contributor that ignores
563
+ * the argument is still assignable, which is what every pre-#1828 hook does.
564
+ */
565
+ export type BuildRootContributor = (
566
+ ctx: Pick<BuildRootContext, "entities">,
567
+ ) => Promise<BuildRootContribution>;
568
+
453
569
  export interface LexiconPlugin {
454
570
  // ── Required ──────────────────────────────────────────────
455
571
  /** Human-readable name (e.g. "aws", "gcp") */
@@ -540,6 +656,17 @@ export interface LexiconPlugin {
540
656
  */
541
657
  auditCatalog?(): Record<string, RuleMeta>;
542
658
 
659
+ /**
660
+ * Parse standalone template content (a file audit discovery classified for
661
+ * this lexicon) into the lexicon's entity graph, keyed the way `ctx.entities`
662
+ * is during a build. Lets entity-reading post-synth checks fire on
663
+ * `chant audit` of hand-written manifests — parse-to-graph rather than
664
+ * output-reading rule variants (#1567). Implementations must tolerate
665
+ * arbitrary external content: a malformed document yields no entities, never
666
+ * a throw. Omit for lexicons whose audit checks read `ctx.outputs`.
667
+ */
668
+ auditEntities?(content: string): Map<string, Declarable>;
669
+
543
670
  /**
544
671
  * Machine-readable spec-coverage accounting for `check-lexicon` (#1330).
545
672
  *
@@ -600,6 +727,20 @@ export interface LexiconPlugin {
600
727
  options?: ComponentPipelineOptions,
601
728
  ): ComponentPipelineResult;
602
729
 
730
+ /**
731
+ * Generate CI for a set of scheduled, stateless Ops — the cron-triggered
732
+ * sibling of {@link generateComponentPipeline} (#927). Each
733
+ * `ScheduledOpSpec` becomes its own CI file (cron triggers are
734
+ * workflow-scoped in every CI provider chant targets), invoking `chant run
735
+ * <name>` on the local executor — the same one-shot invocation `chant run
736
+ * <op>` runs by hand. Only CI-provider lexicons (gitlab, github, forgejo)
737
+ * implement this; omit for lexicons that are not CI providers.
738
+ */
739
+ generateOpPipeline?(
740
+ ops: ScheduledOpSpec[],
741
+ options?: ComponentPipelineOptions,
742
+ ): OpPipelineResult;
743
+
603
744
  /**
604
745
  * Render this lexicon's config-declared build roots into entities (#1548
605
746
  * piece 3) — the seam for a build root that is NOT typed chant source. The
@@ -1115,10 +1256,15 @@ export interface TeardownOutcome {
1115
1256
  /**
1116
1257
  * - `deleted` — gone, including already-gone (deletion is idempotent);
1117
1258
  * - `failed` — the delete errored (core retries these once);
1118
- * - `not-prunable` — deliberately not deleted; `detail` says why.
1259
+ * - `not-prunable` — deliberately not deleted; `detail` says why;
1260
+ * - `retained` — owned and no longer declared, but deliberately kept
1261
+ * (#1365 decision 5): a `generated-once` secret never enters the prunable
1262
+ * set, because deleting it would destroy the only copy of material chant
1263
+ * never held. Reported loudly, never deleted; deletion is an explicit act
1264
+ * (`kubectl delete`, or a future gated op), never a sweep's.
1119
1265
  */
1120
- outcome: "deleted" | "failed" | "not-prunable";
1121
- /** The error for `failed`, the reason for `not-prunable`. */
1266
+ outcome: "deleted" | "failed" | "not-prunable" | "retained";
1267
+ /** The error for `failed`, the reason for `not-prunable`/`retained`. */
1122
1268
  detail?: string;
1123
1269
  }
1124
1270
 
@@ -31,11 +31,30 @@ import { unobservedReasonText, type UnobservedReason } from "../observation";
31
31
  * not drift, just the runtime doing its job. `runtimeOwner` names the
32
32
  * declared entity it belongs to.
33
33
  * - `noop` — declared and live with no drift, or already reconciled.
34
+ * - `effect` — a declared effect receipt (#1832) whose live value is absent or
35
+ * differs from the resolved expectation: the effect step will fire. Never a
36
+ * `create` or `update` — the generic apply path is observe-only to receipts,
37
+ * and the `effect()` step is the sole writer (epic #1703, decision 3). Read
38
+ * `effect` for the effect's identity and `effectReason` for why it fires.
34
39
  * - `unobserved` — declared, and the lexicon could not look (#1089). Not a
35
40
  * proposal at all: it is the plan admitting a hole. Never a create, never a
36
41
  * delete. Read `unobservedReason` for which hole.
37
42
  */
38
- export type ChangeAction = "create" | "update" | "delete" | "adopt" | "runtime" | "noop" | "unobserved";
43
+ export type ChangeAction = "create" | "update" | "delete" | "adopt" | "runtime" | "noop" | "effect" | "unobserved";
44
+
45
+ /**
46
+ * Why an `effect` entry proposes a fire (#1832).
47
+ *
48
+ * - `receipt-absent` — the provider confirmed the receipt absent: the effect
49
+ * has never recorded a run (or the run crashed before the write — the
50
+ * at-least-once case this classification exists to preserve).
51
+ * - `receipt-stale` — the receipt is live but its value differs from the
52
+ * resolved expectation: the effect's inputs changed since the last run.
53
+ * - `unresolved-input` — a reference input could not resolve at plan time, so
54
+ * the expectation cannot be computed. The fire is proposed rather than
55
+ * guessed away; the effect step resolves again at run.
56
+ */
57
+ export type EffectFireReason = "receipt-absent" | "receipt-stale" | "unresolved-input";
39
58
 
40
59
  /**
41
60
  * Who answers "is this resource chant's?". `unknown` until a live ownership
@@ -100,6 +119,16 @@ export interface ChangeSetEntry {
100
119
  queried?: string;
101
120
  /** The declared entity this resource's owner chain resolves to, for `action: "runtime"` (#1077). */
102
121
  runtimeOwner?: string;
122
+ /**
123
+ * The effect a receipt witnesses (#1832), for entries derived from an effect
124
+ * receipt. On `action: "effect"` it names what will fire; on a receipt's
125
+ * `noop`/`unobserved` rows it keeps the attribution.
126
+ */
127
+ effect?: string;
128
+ /** Why the effect fires, for `action: "effect"` (#1832). */
129
+ effectReason?: EffectFireReason;
130
+ /** Human-readable backing for `effectReason` (the digests that differ, the unresolved path). */
131
+ effectDetail?: string;
103
132
  }
104
133
 
105
134
  export interface ChangeSet {
@@ -234,13 +263,14 @@ export function buildChangeSet(env: string, input: DiffLiveInput, options?: Chan
234
263
  return { env, entries };
235
264
  }
236
265
 
237
- const ACTION_ORDER: ChangeAction[] = ["create", "update", "delete", "adopt", "runtime", "noop", "unobserved"];
266
+ const ACTION_ORDER: ChangeAction[] = ["create", "update", "effect", "delete", "adopt", "runtime", "noop", "unobserved"];
238
267
 
239
268
  /** Count entries per action. */
240
269
  export function summarize(cs: ChangeSet): Record<ChangeAction, number> {
241
270
  const counts: Record<ChangeAction, number> = {
242
271
  create: 0,
243
272
  update: 0,
273
+ effect: 0,
244
274
  delete: 0,
245
275
  adopt: 0,
246
276
  runtime: 0,
@@ -257,10 +287,10 @@ export function summarize(cs: ChangeSet): Record<ChangeAction, number> {
257
287
  * GitLab renders an `artifacts:reports:terraform` artifact in the merge-request
258
288
  * UI as "N to add, M to change, K to delete". The format is generic — any tool
259
289
  * that emits this JSON gets the widget — and the chant plan maps onto it
260
- * directly. Only the mutating actions count: `adopt`, `runtime`, `noop` and
261
- * `unobserved` are excluded, since the widget has no column for "live but
262
- * undeclared", "expected runtime child" (#1077), "no change", or "could not
263
- * look" (#1089). The widget is therefore a floor, not a complete plan: read
290
+ * directly. Only the mutating actions count: `adopt`, `runtime`, `noop`,
291
+ * `effect` and `unobserved` are excluded, since the widget has no column for
292
+ * "live but undeclared", "expected runtime child" (#1077), "no change", "an
293
+ * effect will fire" (#1832), or "could not look" (#1089). The widget is therefore a floor, not a complete plan: read
264
294
  * the full change set when entities are unobserved or classified runtime.
265
295
  *
266
296
  * The widget label reads "Terraform" regardless of producer; that is GitLab's
@@ -292,9 +322,18 @@ export function renderChangeSet(cs: ChangeSet): string {
292
322
  ? "\nUNOBSERVED (declared; chant could not read live state — no action proposed):"
293
323
  : action === "runtime"
294
324
  ? "\nRUNTIME (owned by a declared resource; not drift, never a delete/adopt candidate):"
295
- : `\n${action.toUpperCase()}:`,
325
+ : action === "effect"
326
+ ? "\nEFFECT (receipt absent or stale; the effect step fires — the generic apply never writes a receipt):"
327
+ : `\n${action.toUpperCase()}:`,
296
328
  );
297
329
  for (const e of group) {
330
+ if (e.action === "effect") {
331
+ lines.push(
332
+ ` effect will fire: ${e.effect ?? e.name} — receipt ${e.name}${e.type ? ` (${e.type})` : ""}` +
333
+ `${e.effectDetail ? ` — ${e.effectDetail}` : ""}`,
334
+ );
335
+ continue;
336
+ }
298
337
  const own = e.ownership === "unknown" ? "" : ` [${e.ownership}]`;
299
338
  const why = e.unobservedReason
300
339
  ? ` — ${unobservedReasonText(e.unobservedReason)}${e.unobservedDetail ? `: ${e.unobservedDetail}` : ""}`