@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,290 @@
1
+ /**
2
+ * Wrangler config audit (#446) — a config format the audit engine could not
3
+ * parse at all before this: `wrangler.toml`, Cloudflare Workers' native
4
+ * declarative deploy file (routes, bindings, vars, observability). Fitting,
5
+ * since chant's own hosted audit path targets the same Workers runtime.
6
+ *
7
+ * Follows `secrets.ts`'s precedent for a format the issue says should ship
8
+ * "only a detector and a set of checks — no authoring surface, no output
9
+ * serializer": no lexicon plugin, no `AuditInput`/`classifyFiles` involvement,
10
+ * no npm package to install. `auditWranglerConfigs` scans the raw candidate
11
+ * files directly (the detector is `isWranglerConfigPath`) and is called
12
+ * alongside `scanForSecrets` from the CLI. `wrangler.toml` is also NOT the
13
+ * `fly` lexicon's `.fly` (this repo already has a `fly` lexicon covering the
14
+ * Machines/flaps REST API with its own `FLY0xx` ids) — this module owns a
15
+ * disjoint namespace (`WRG0xx`) and `AuditFinding.lexicon = "wrangler"`.
16
+ *
17
+ * Pure: no fs, no network, no Node-only global — Workers-safe (epic #350).
18
+ */
19
+
20
+ import { parseTOML, TomlParseError } from "../toml";
21
+ import type { AuditFinding } from "./core";
22
+
23
+ /** The minimal file shape the scanner needs (matches `discover.ts`'s `RepoFile`). */
24
+ export interface ScannableFile {
25
+ path: string;
26
+ content: string;
27
+ }
28
+
29
+ /** Detector: does this path look like a Wrangler config? (native TOML only — see module doc.) */
30
+ export function isWranglerConfigPath(path: string): boolean {
31
+ const base = path.split("/").pop() ?? path;
32
+ return base === "wrangler.toml";
33
+ }
34
+
35
+ type JsonRecord = Record<string, unknown>;
36
+
37
+ function isRecord(v: unknown): v is JsonRecord {
38
+ return typeof v === "object" && v !== null && !Array.isArray(v);
39
+ }
40
+
41
+ function asArray(v: unknown): unknown[] {
42
+ return Array.isArray(v) ? v : [];
43
+ }
44
+
45
+ /** One config scope: the top-level document, or one `[env.<name>]` block. */
46
+ interface Scope {
47
+ /** `"top-level"`, or the env name (`"production"`, `"staging"`, …). */
48
+ name: string;
49
+ /** Whether this scope is the account's default deploy target (`wrangler deploy` with no `--env`). */
50
+ isTopLevel: boolean;
51
+ config: JsonRecord;
52
+ }
53
+
54
+ function scopesOf(doc: JsonRecord): Scope[] {
55
+ const scopes: Scope[] = [{ name: "top-level", isTopLevel: true, config: doc }];
56
+ const env = doc.env;
57
+ if (isRecord(env)) {
58
+ for (const [name, cfg] of Object.entries(env)) {
59
+ if (isRecord(cfg)) scopes.push({ name, isTopLevel: false, config: cfg });
60
+ }
61
+ }
62
+ return scopes;
63
+ }
64
+
65
+ const PROD_NAME_RE = /^(prod|production|live)$/i;
66
+ const NON_PROD_NAME_RE = /^(dev|development|staging|stage|preview|test|testing|sandbox|local)$/i;
67
+
68
+ /** Best-effort 1-based line number of the first line containing `needle`, or undefined. */
69
+ function lineOf(content: string, needle: string): number | undefined {
70
+ if (!needle) return undefined;
71
+ const idx = content.indexOf(needle);
72
+ if (idx === -1) return undefined;
73
+ return content.slice(0, idx).split("\n").length;
74
+ }
75
+
76
+ // ── WRG001: workers_dev in a production-named environment ──────────────────
77
+
78
+ function checkWorkersDevInProd(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
79
+ const out: AuditFinding[] = [];
80
+ for (const scope of scopesOf(doc)) {
81
+ if (!PROD_NAME_RE.test(scope.name)) continue;
82
+ if (scope.config.workers_dev !== true) continue;
83
+ out.push({
84
+ checkId: "WRG001",
85
+ severity: "warning",
86
+ message: `Environment "${scope.name}" sets workers_dev = true — the Worker is reachable on the public *.workers.dev subdomain, bypassing whatever access control a custom domain/zone would provide.`,
87
+ file: file.path,
88
+ lexicon: "wrangler",
89
+ entity: scope.name,
90
+ line: lineOf(file.content, "workers_dev"),
91
+ });
92
+ }
93
+ return out;
94
+ }
95
+
96
+ // ── WRG002: sensitive-named key stored in [vars] instead of a secret ───────
97
+
98
+ const SENSITIVE_VAR_RE = /(SECRET|TOKEN|API[_-]?KEY|PASSWORD|PASSWD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|CLIENT[_-]?SECRET|\bAUTH\b)/i;
99
+
100
+ function checkSecretShapedVars(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
101
+ const out: AuditFinding[] = [];
102
+ for (const scope of scopesOf(doc)) {
103
+ const vars = scope.config.vars;
104
+ if (!isRecord(vars)) continue;
105
+ for (const key of Object.keys(vars)) {
106
+ if (!SENSITIVE_VAR_RE.test(key)) continue;
107
+ out.push({
108
+ checkId: "WRG002",
109
+ severity: "error",
110
+ message: `"${key}" in [vars] (${scope.name}) looks like a credential name. Plaintext vars are committed to source and visible via 'wrangler dev'/the dashboard — use 'wrangler secret put ${key}' instead.`,
111
+ file: file.path,
112
+ lexicon: "wrangler",
113
+ entity: key,
114
+ line: lineOf(file.content, key),
115
+ });
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+
121
+ // ── WRG003: observability explicitly disabled ───────────────────────────────
122
+
123
+ function checkObservabilityDisabled(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
124
+ const out: AuditFinding[] = [];
125
+ for (const scope of scopesOf(doc)) {
126
+ const obs = scope.config.observability;
127
+ if (!isRecord(obs)) continue;
128
+ if (obs.enabled !== false) continue;
129
+ out.push({
130
+ checkId: "WRG003",
131
+ severity: "info",
132
+ message: `Environment "${scope.name}" explicitly disables observability (observability.enabled = false) — no Workers Logs are recorded for this deployment, which will slow down or block incident investigation.`,
133
+ file: file.path,
134
+ lexicon: "wrangler",
135
+ entity: scope.name,
136
+ line: lineOf(file.content, "observability"),
137
+ });
138
+ }
139
+ return out;
140
+ }
141
+
142
+ // ── WRG004: an account-wide wildcard route (unrestricted ingress) ──────────
143
+
144
+ function routePatterns(config: JsonRecord): string[] {
145
+ const patterns: string[] = [];
146
+ for (const r of asArray(config.routes)) {
147
+ if (typeof r === "string") patterns.push(r);
148
+ else if (isRecord(r) && typeof r.pattern === "string") patterns.push(r.pattern);
149
+ }
150
+ if (typeof config.route === "string") patterns.push(config.route);
151
+ return patterns;
152
+ }
153
+
154
+ /** A route pattern with no host scoping at all — matches every zone in the account, not just one. */
155
+ function isAccountWildcard(pattern: string): boolean {
156
+ const p = pattern.trim();
157
+ return p === "*" || p === "*/*";
158
+ }
159
+
160
+ function checkWildcardRoute(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
161
+ const out: AuditFinding[] = [];
162
+ for (const scope of scopesOf(doc)) {
163
+ for (const pattern of routePatterns(scope.config)) {
164
+ if (!isAccountWildcard(pattern)) continue;
165
+ out.push({
166
+ checkId: "WRG004",
167
+ severity: "error",
168
+ message: `Environment "${scope.name}" declares route "${pattern}" — an unscoped wildcard that matches every zone/path on the account, not one domain. Scope it to a specific zone (e.g. "example.com/*").`,
169
+ file: file.path,
170
+ lexicon: "wrangler",
171
+ entity: scope.name,
172
+ line: lineOf(file.content, pattern),
173
+ });
174
+ }
175
+ }
176
+ return out;
177
+ }
178
+
179
+ // ── WRG005: a non-prod environment shares a data-store id with production ──
180
+
181
+ interface BindingSpec {
182
+ key: string;
183
+ idField: string;
184
+ }
185
+ const BINDING_KINDS: BindingSpec[] = [
186
+ { key: "kv_namespaces", idField: "id" },
187
+ { key: "r2_buckets", idField: "bucket_name" },
188
+ { key: "d1_databases", idField: "database_id" },
189
+ ];
190
+
191
+ function checkCrossEnvSharedResource(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
192
+ const out: AuditFinding[] = [];
193
+ const scopes = scopesOf(doc);
194
+ // The top-level scope is the default deploy target (`wrangler deploy` with
195
+ // no `--env`) — treated as "production" only when the file also declares at
196
+ // least one dev/staging-like env, so a single-environment file (top-level
197
+ // only) never trips this on itself.
198
+ const hasNonProdEnv = scopes.some((s) => !s.isTopLevel && NON_PROD_NAME_RE.test(s.name));
199
+ const prodScopeNames = new Set(
200
+ scopes.filter((s) => (s.isTopLevel && hasNonProdEnv) || PROD_NAME_RE.test(s.name)).map((s) => s.name),
201
+ );
202
+ const nonProdScopes = scopes.filter((s) => NON_PROD_NAME_RE.test(s.name));
203
+ if (prodScopeNames.size === 0 || nonProdScopes.length === 0) return out;
204
+
205
+ for (const { key, idField } of BINDING_KINDS) {
206
+ // id -> set of scope names that declare a binding with that id
207
+ const byId = new Map<string, Set<string>>();
208
+ for (const scope of scopes) {
209
+ for (const binding of asArray(scope.config[key])) {
210
+ if (!isRecord(binding)) continue;
211
+ const id = binding[idField];
212
+ if (typeof id !== "string" || id === "") continue;
213
+ const owners = byId.get(id) ?? new Set<string>();
214
+ owners.add(scope.name);
215
+ byId.set(id, owners);
216
+ }
217
+ }
218
+ for (const [id, owners] of byId) {
219
+ const prodOwner = [...owners].find((n) => prodScopeNames.has(n));
220
+ const nonProdOwner = [...owners].find((n) => NON_PROD_NAME_RE.test(n));
221
+ if (!prodOwner || !nonProdOwner) continue;
222
+ out.push({
223
+ checkId: "WRG005",
224
+ severity: "error",
225
+ message: `"${nonProdOwner}" and "${prodOwner}" both bind ${key} ${idField}="${id}" — the non-production environment has full read/write access to the production data store instead of its own copy.`,
226
+ file: file.path,
227
+ lexicon: "wrangler",
228
+ entity: `${key}:${id}`,
229
+ line: lineOf(file.content, id),
230
+ });
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+
236
+ // ── WRG006: static assets/site served from the project root ────────────────
237
+
238
+ const ROOT_DIR_RE = /^\.?\/?$/;
239
+
240
+ function checkAssetsServeRoot(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
241
+ const out: AuditFinding[] = [];
242
+ for (const scope of scopesOf(doc)) {
243
+ const candidates: Array<{ table: string; field: string; value: unknown }> = [];
244
+ if (isRecord(scope.config.site)) candidates.push({ table: "site", field: "bucket", value: scope.config.site.bucket });
245
+ if (isRecord(scope.config.assets)) candidates.push({ table: "assets", field: "directory", value: scope.config.assets.directory });
246
+ for (const c of candidates) {
247
+ if (typeof c.value !== "string" || !ROOT_DIR_RE.test(c.value)) continue;
248
+ out.push({
249
+ checkId: "WRG006",
250
+ severity: "warning",
251
+ message: `Environment "${scope.name}" sets [${c.table}].${c.field} = "${c.value}" — the project root is served as public static assets, including any files not meant to be public (config, source maps, .git).`,
252
+ file: file.path,
253
+ lexicon: "wrangler",
254
+ entity: scope.name,
255
+ line: lineOf(file.content, c.field),
256
+ });
257
+ }
258
+ }
259
+ return out;
260
+ }
261
+
262
+ const CHECKS: Array<(file: ScannableFile, doc: JsonRecord) => AuditFinding[]> = [
263
+ checkWorkersDevInProd,
264
+ checkSecretShapedVars,
265
+ checkObservabilityDisabled,
266
+ checkWildcardRoute,
267
+ checkCrossEnvSharedResource,
268
+ checkAssetsServeRoot,
269
+ ];
270
+
271
+ /**
272
+ * Audit every `wrangler.toml` in `files`. Pure with respect to the filesystem
273
+ * and network. A file that fails to parse contributes no findings — the audit
274
+ * contract is "runs against any repo," never a crash on odd/partial config.
275
+ */
276
+ export function auditWranglerConfigs(files: ScannableFile[]): AuditFinding[] {
277
+ const findings: AuditFinding[] = [];
278
+ for (const file of files) {
279
+ if (!isWranglerConfigPath(file.path)) continue;
280
+ let doc: JsonRecord;
281
+ try {
282
+ doc = parseTOML(file.content);
283
+ } catch (err) {
284
+ if (err instanceof TomlParseError) continue;
285
+ throw err;
286
+ }
287
+ for (const check of CHECKS) findings.push(...check(file, doc));
288
+ }
289
+ return findings;
290
+ }
package/src/build.test.ts CHANGED
@@ -338,6 +338,47 @@ export const discovered = {
338
338
  expect(result.entities.get("discovered")?.entityType).toBe("Discovered");
339
339
  });
340
340
 
341
+ test("a contributor sees the discovered entities, read-only", async () => {
342
+ await writeFile(
343
+ join(testDir, "app.infra.ts"),
344
+ `
345
+ export const discovered = {
346
+ lexicon: "test",
347
+ entityType: "Discovered",
348
+ props: {},
349
+ [Symbol.for("chant.declarable")]: true,
350
+ };
351
+ `,
352
+ );
353
+
354
+ let seenNames: string[] = [];
355
+ const result = await build(testDir, [recordingSerializer([])], undefined, {
356
+ buildRoots: [
357
+ async (ctx) => {
358
+ seenNames = [...(ctx.entities?.keys() ?? [])];
359
+ return { entities: new Map() };
360
+ },
361
+ ],
362
+ });
363
+
364
+ expect(result.errors).toEqual([]);
365
+ expect(seenNames).toContain("discovered");
366
+ });
367
+
368
+ test("a later contributor sees what an earlier one contributed", async () => {
369
+ const seen: string[][] = [];
370
+ await build(testDir, [recordingSerializer([])], undefined, {
371
+ buildRoots: [
372
+ async () => ({ entities: new Map([["first", entity("First")]]) }),
373
+ async (ctx) => {
374
+ seen.push([...(ctx.entities?.keys() ?? [])]);
375
+ return { entities: new Map() };
376
+ },
377
+ ],
378
+ });
379
+ expect(seen[0]).toContain("first");
380
+ });
381
+
341
382
  test("contributors run exactly once per build", async () => {
342
383
  let calls = 0;
343
384
  await build(testDir, [], undefined, {
package/src/build.ts CHANGED
@@ -2,10 +2,13 @@ import type { Declarable } from "./declarable";
2
2
  import type { Serializer, SerializerResult } from "./serializer";
3
3
  import type { OwnershipMarker } from "./ownership";
4
4
  import type { BuildError, DiscoveryErrorType } from "./errors";
5
- import type { IntrinsicDef, BuildRootContribution } from "./lexicon";
5
+ import type { IntrinsicDef, BuildRootContribution, BuildRootContributor } from "./lexicon";
6
6
  import type { BuildParamProvenance } from "./provenance";
7
7
  import { DiscoveryError, BuildError as BuildErrorClass } from "./errors";
8
8
  import { LexiconOutput, isLexiconOutput } from "./lexicon-output";
9
+ import { isSecretDeclaration } from "./secret-provenance";
10
+ import { splitReceiptEntities } from "./effect-receipt";
11
+ import { isScenario } from "./lifecycle/scenario";
9
12
  import { AttrRef } from "./attrref";
10
13
  import { isAttrRefLike } from "./utils";
11
14
  import { isChildProject, type ChildProjectInstance } from "./child-project";
@@ -112,7 +115,10 @@ export function computeStackGraph(
112
115
  // Dependency map: node → producers it depends on.
113
116
  const nodes = lexiconNames.length
114
117
  ? [...lexiconNames]
115
- : [...new Set([...entities.values()].map((e) => e.lexicon))];
118
+ : // Secret provenance declarations (#1828) and plan scenarios (#1292) never
119
+ // form a stack — their pseudo-lexicon has no serializer and must not
120
+ // appear in the manifest.
121
+ [...new Set([...entities.values()].filter((e) => !isSecretDeclaration(e) && !isScenario(e)).map((e) => e.lexicon))];
116
122
  const deps = new Map<string, Set<string>>();
117
123
  for (const n of nodes) deps.set(n, new Set());
118
124
  for (const { from, to } of edges) {
@@ -229,7 +235,7 @@ export interface BuildOptions {
229
235
  * binaries); a contributed name colliding with a discovered entity is a
230
236
  * build error, never a silent overwrite.
231
237
  */
232
- buildRoots?: Array<() => Promise<BuildRootContribution>>;
238
+ buildRoots?: BuildRootContributor[];
233
239
  }
234
240
 
235
241
  export interface BuildResult {
@@ -294,6 +300,14 @@ export function partitionByLexicon(
294
300
  for (const [name, entity] of entities) {
295
301
  // LexiconOutput instances are collected separately; skip them here
296
302
  if (isLexiconOutput(entity)) continue;
303
+ // Secret provenance declarations (#1828) are serializer-neutral: data
304
+ // that lint and lexicons read from the entity map, never output. Keeping
305
+ // them out of every partition means no serializer sees them and no
306
+ // "No serializer found" warning fires for their pseudo-lexicon.
307
+ if (isSecretDeclaration(entity)) continue;
308
+ // Plan scenarios (#1292) are serializer-neutral the same way: a checkable
309
+ // expectation the CLI reads off the entity map, never output.
310
+ if (isScenario(entity)) continue;
297
311
  const lexicon = entity.lexicon;
298
312
  if (!partitions.has(lexicon)) {
299
313
  partitions.set(lexicon, new Map());
@@ -590,13 +604,18 @@ export interface BuildRootMergeResult {
590
604
  */
591
605
  export async function mergeBuildRootEntities(
592
606
  entities: Map<string, Declarable>,
593
- contributors: ReadonlyArray<() => Promise<BuildRootContribution>>,
607
+ contributors: ReadonlyArray<BuildRootContributor>,
594
608
  ): Promise<BuildRootMergeResult> {
595
609
  const warnings: string[] = [];
596
610
  const errors: string[] = [];
597
611
  for (const contribute of contributors) {
598
612
  try {
599
- const contribution = await contribute();
613
+ // The discovered set, read-only, so a contributor can react to what the
614
+ // project DECLARED (a committed-encrypted `declareSecret()` naming a
615
+ // ciphertext file to resolve). Contributors run in order, so this also
616
+ // carries what earlier contributors added; the merge below is still the
617
+ // only writer, which is what keeps the collision refusal meaningful.
618
+ const contribution = await contribute({ entities });
600
619
  warnings.push(...(contribution.warnings ?? []));
601
620
  for (const [name, entity] of contribution.entities) {
602
621
  if (entities.has(name)) {
@@ -785,20 +804,34 @@ async function buildFromDiscoveryResult(
785
804
  const outputs = new Map<string, string | SerializerResult>();
786
805
  for (const [lexiconName, lexiconEntities] of partitions) {
787
806
  const serializer = serializersByName.get(lexiconName);
807
+ // #1832: effect receipts are withheld from the apply-bound entity set at
808
+ // this seam — the narrowest choke point every applier's input flows
809
+ // through, since appliers consume the serialized outputs assembled here.
810
+ // Receipts ride `SerializeContext.receipts` instead, so a lexicon can
811
+ // render them for visibility (#1835) without them ever entering the
812
+ // document an applier writes (or prunes) from. The `effect()` step is the
813
+ // sole receipt writer (epic #1703, decision 3).
814
+ const { applyBound, receipts } = splitReceiptEntities(lexiconEntities);
788
815
  if (serializer) {
789
816
  const lexiconLexiconOutputs = [
790
817
  ...(outputsByLexicon.get(lexiconName) ?? []),
791
818
  ...unassignedOutputs,
792
819
  ];
793
- const serialized = serializer.serialize(lexiconEntities, lexiconLexiconOutputs, {
820
+ const serialized = serializer.serialize(applyBound, lexiconLexiconOutputs, {
794
821
  ownership: options?.ownership,
795
822
  config: options?.config,
823
+ ...(receipts.size > 0 ? { receipts } : {}),
796
824
  });
797
825
  // Collect any non-fatal serializer diagnostics into the build warnings.
798
826
  if (typeof serialized !== "string" && serialized.warnings) {
799
827
  for (const w of serialized.warnings) warnings.push(w);
800
828
  }
801
829
  outputs.set(lexiconName, serialized);
830
+ } else if (applyBound.size === 0 && receipts.size > 0) {
831
+ // A partition holding only receipts (the core factory's "chant"
832
+ // pseudo-lexicon) has nothing apply-bound to serialize; that is the
833
+ // designed shape until a lexicon materializes the receipt (#1835), not
834
+ // a missing-serializer problem.
802
835
  } else {
803
836
  warnings.push(`No serializer found for lexicon "${lexiconName}"`);
804
837
  }
@@ -71,7 +71,7 @@ export interface CommandGroup {
71
71
  export const RESERVED_COMMAND_NAMES: ReadonlySet<string> = new Set([
72
72
  "build", "lint", "list", "describe", "import", "audit", "migrate", "carve",
73
73
  "init", "update", "doctor", "dev", "run", "graph", "vendor", "lifecycle",
74
- "lc", "components", "emulator", "serve",
74
+ "lc", "components", "emulator", "serve", "operator", "approve",
75
75
  ]);
76
76
 
77
77
  /**
@@ -0,0 +1,27 @@
1
+ apiVersion: fountain.dev/v1
2
+ kind: Environment
3
+ metadata:
4
+ name: dev
5
+ spec:
6
+ networking_type: unrestricted
7
+ env_vars:
8
+ API_URL: https://api.example.com
9
+ AWS_SECRET_ACCESS_KEY: not-a-real-secret
10
+ ---
11
+ apiVersion: fountain.dev/v1
12
+ kind: Vault
13
+ metadata:
14
+ name: staging
15
+ spec:
16
+ secrets:
17
+ - key: API_URL
18
+ value: https://staging.example.com
19
+ ---
20
+ apiVersion: fountain.dev/v1
21
+ kind: Agent
22
+ metadata:
23
+ name: researcher
24
+ spec:
25
+ model: anthropic/claude-sonnet-4-6
26
+ runtime: claude
27
+ environment: dev
@@ -0,0 +1,16 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: web
5
+ spec:
6
+ selector:
7
+ matchLabels:
8
+ app: web
9
+ template:
10
+ metadata:
11
+ labels:
12
+ app: web
13
+ spec:
14
+ containers:
15
+ - name: web
16
+ image: nginx:1.27
@@ -0,0 +1,20 @@
1
+ apiVersion: fountain.dev/v1
2
+ kind: Environment
3
+ metadata:
4
+ name: dev
5
+ spec:
6
+ networking_type: limited
7
+ networking_config:
8
+ allowed_hosts:
9
+ - github.com
10
+ env_vars:
11
+ API_URL: https://api.example.com
12
+ ---
13
+ apiVersion: fountain.dev/v1
14
+ kind: Agent
15
+ metadata:
16
+ name: researcher
17
+ spec:
18
+ model: anthropic/claude-sonnet-4-6
19
+ runtime: claude
20
+ environment: dev