@intentius/chant 0.102.0 → 0.104.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 (464) hide show
  1. package/dist/audit/core.d.ts +2 -0
  2. package/dist/audit/core.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/change-set.d.ts +232 -0
  5. package/dist/change-set.d.ts.map +1 -0
  6. package/dist/cli/commands/audit.d.ts +7 -0
  7. package/dist/cli/commands/audit.d.ts.map +1 -1
  8. package/dist/cli/commands/build.d.ts.map +1 -1
  9. package/dist/cli/handlers/build.d.ts.map +1 -1
  10. package/dist/cli/handlers/change-set.d.ts +11 -0
  11. package/dist/cli/handlers/change-set.d.ts.map +1 -0
  12. package/dist/cli/handlers/fan-out.d.ts +33 -0
  13. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  14. package/dist/cli/handlers/operator.d.ts +7 -0
  15. package/dist/cli/handlers/operator.d.ts.map +1 -1
  16. package/dist/cli/handlers/pr.d.ts +31 -0
  17. package/dist/cli/handlers/pr.d.ts.map +1 -0
  18. package/dist/cli/handlers/run.d.ts.map +1 -1
  19. package/dist/cli/main.d.ts.map +1 -1
  20. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  21. package/dist/cli/mcp/workspace-tools.d.ts +3 -2
  22. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  23. package/dist/cli/registry.d.ts +63 -1
  24. package/dist/cli/registry.d.ts.map +1 -1
  25. package/dist/components/auto-release.d.ts +16 -0
  26. package/dist/components/auto-release.d.ts.map +1 -1
  27. package/dist/components/capability.d.ts +53 -0
  28. package/dist/components/capability.d.ts.map +1 -1
  29. package/dist/components/changed-files.d.ts +31 -0
  30. package/dist/components/changed-files.d.ts.map +1 -0
  31. package/dist/components/cli-support.d.ts +9 -1
  32. package/dist/components/cli-support.d.ts.map +1 -1
  33. package/dist/components/component.d.ts +24 -0
  34. package/dist/components/component.d.ts.map +1 -1
  35. package/dist/components/deploy-units.d.ts.map +1 -1
  36. package/dist/components/driver.d.ts +13 -0
  37. package/dist/components/driver.d.ts.map +1 -1
  38. package/dist/components/fan-out-output.d.ts +6 -0
  39. package/dist/components/fan-out-output.d.ts.map +1 -1
  40. package/dist/components/fan-out-record.d.ts +41 -0
  41. package/dist/components/fan-out-record.d.ts.map +1 -0
  42. package/dist/components/fan-out-run.d.ts +65 -0
  43. package/dist/components/fan-out-run.d.ts.map +1 -1
  44. package/dist/components/fan-out-support.d.ts +2 -0
  45. package/dist/components/fan-out-support.d.ts.map +1 -1
  46. package/dist/components/fan-out.d.ts +6 -0
  47. package/dist/components/fan-out.d.ts.map +1 -1
  48. package/dist/components/gated-wave-pipeline.d.ts +50 -0
  49. package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
  50. package/dist/components/index.d.ts +1 -1
  51. package/dist/components/index.d.ts.map +1 -1
  52. package/dist/components/layers.d.ts +25 -0
  53. package/dist/components/layers.d.ts.map +1 -0
  54. package/dist/components/pr-pipeline.d.ts +61 -0
  55. package/dist/components/pr-pipeline.d.ts.map +1 -0
  56. package/dist/components/pr-run.d.ts +119 -0
  57. package/dist/components/pr-run.d.ts.map +1 -0
  58. package/dist/components/promote.d.ts.map +1 -1
  59. package/dist/components/verbs/shell.d.ts.map +1 -1
  60. package/dist/components/wave-plan.d.ts +49 -0
  61. package/dist/components/wave-plan.d.ts.map +1 -0
  62. package/dist/gated-waves.d.ts +147 -0
  63. package/dist/gated-waves.d.ts.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/lexicon.d.ts +48 -0
  67. package/dist/lexicon.d.ts.map +1 -1
  68. package/dist/lifecycle/gate-ledger.d.ts +7 -0
  69. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  70. package/dist/lifecycle/git.d.ts +46 -1
  71. package/dist/lifecycle/git.d.ts.map +1 -1
  72. package/dist/lint/post-synth.d.ts +18 -0
  73. package/dist/lint/post-synth.d.ts.map +1 -1
  74. package/dist/op/activities/activity-contracts.d.ts +126 -0
  75. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  76. package/dist/op/activities/change-set.d.ts +62 -0
  77. package/dist/op/activities/change-set.d.ts.map +1 -0
  78. package/dist/op/activities/decide.d.ts +2 -0
  79. package/dist/op/activities/decide.d.ts.map +1 -1
  80. package/dist/op/activities/factory.d.ts +137 -0
  81. package/dist/op/activities/factory.d.ts.map +1 -0
  82. package/dist/op/activities/index.d.ts +4 -0
  83. package/dist/op/activities/index.d.ts.map +1 -1
  84. package/dist/op/activity-contract.d.ts.map +1 -1
  85. package/dist/op/builders.d.ts +11 -0
  86. package/dist/op/builders.d.ts.map +1 -1
  87. package/dist/op/decide-backend.d.ts +5 -1
  88. package/dist/op/decide-backend.d.ts.map +1 -1
  89. package/dist/op/decide-read-inputs.d.ts +4 -2
  90. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  91. package/dist/op/factory.d.ts +83 -0
  92. package/dist/op/factory.d.ts.map +1 -0
  93. package/dist/op/index.d.ts +4 -1
  94. package/dist/op/index.d.ts.map +1 -1
  95. package/dist/op/op-ir.d.ts +2 -0
  96. package/dist/op/op-ir.d.ts.map +1 -1
  97. package/dist/op/op-verb-class.d.ts.map +1 -1
  98. package/dist/op/types.d.ts +8 -0
  99. package/dist/op/types.d.ts.map +1 -1
  100. package/dist/op/work-lease-run.d.ts.map +1 -1
  101. package/dist/plan-summary.d.ts +207 -0
  102. package/dist/plan-summary.d.ts.map +1 -0
  103. package/dist/pr-forge.d.ts +105 -0
  104. package/dist/pr-forge.d.ts.map +1 -0
  105. package/dist/pr-loop.d.ts +178 -0
  106. package/dist/pr-loop.d.ts.map +1 -0
  107. package/dist/telemetry-attribution.d.ts +31 -0
  108. package/dist/telemetry-attribution.d.ts.map +1 -1
  109. package/dist/terraform/hcl2json.d.ts +45 -0
  110. package/dist/terraform/hcl2json.d.ts.map +1 -0
  111. package/dist/terraform/parse.d.ts +4 -40
  112. package/dist/terraform/parse.d.ts.map +1 -1
  113. package/dist/workspace/box-cli.d.ts +12 -0
  114. package/dist/workspace/box-cli.d.ts.map +1 -0
  115. package/dist/workspace/box-factory.d.ts +27 -3
  116. package/dist/workspace/box-factory.d.ts.map +1 -1
  117. package/dist/workspace/box-listing.d.ts +85 -0
  118. package/dist/workspace/box-listing.d.ts.map +1 -0
  119. package/dist/workspace/box-publish.d.ts +147 -0
  120. package/dist/workspace/box-publish.d.ts.map +1 -0
  121. package/dist/workspace/broker-protocol.d.ts +190 -0
  122. package/dist/workspace/broker-protocol.d.ts.map +1 -0
  123. package/dist/workspace/checks.d.ts.map +1 -1
  124. package/dist/workspace/composites.d.ts +2 -2
  125. package/dist/workspace/composites.d.ts.map +1 -1
  126. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
  127. package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
  128. package/dist/workspace/conformance/broker.d.ts +202 -0
  129. package/dist/workspace/conformance/broker.d.ts.map +1 -0
  130. package/dist/workspace/conformance/index.d.ts +2 -1
  131. package/dist/workspace/conformance/index.d.ts.map +1 -1
  132. package/dist/workspace/conformance/vitest.d.ts +9 -2
  133. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  134. package/dist/workspace/conformance/writer.d.ts +108 -14
  135. package/dist/workspace/conformance/writer.d.ts.map +1 -1
  136. package/dist/workspace/decide.d.ts +126 -12
  137. package/dist/workspace/decide.d.ts.map +1 -1
  138. package/dist/workspace/decision-points.schema.json +11 -4
  139. package/dist/workspace/declaration.d.ts +65 -0
  140. package/dist/workspace/declaration.d.ts.map +1 -1
  141. package/dist/workspace/declaration.schema.json +153 -4
  142. package/dist/workspace/environments.d.ts +36 -10
  143. package/dist/workspace/environments.d.ts.map +1 -1
  144. package/dist/workspace/factory-rules.d.ts +180 -0
  145. package/dist/workspace/factory-rules.d.ts.map +1 -0
  146. package/dist/workspace/init.d.ts +21 -0
  147. package/dist/workspace/init.d.ts.map +1 -1
  148. package/dist/workspace/intent-cli.d.ts +1 -1
  149. package/dist/workspace/intent-cli.d.ts.map +1 -1
  150. package/dist/workspace/intent-record.d.ts +28 -1
  151. package/dist/workspace/intent-record.d.ts.map +1 -1
  152. package/dist/workspace/intent.d.ts +102 -5
  153. package/dist/workspace/intent.d.ts.map +1 -1
  154. package/dist/workspace/json-edit.d.ts +24 -0
  155. package/dist/workspace/json-edit.d.ts.map +1 -0
  156. package/dist/workspace/jsonc.d.ts +11 -0
  157. package/dist/workspace/jsonc.d.ts.map +1 -1
  158. package/dist/workspace/kinds.d.ts +48 -0
  159. package/dist/workspace/kinds.d.ts.map +1 -1
  160. package/dist/workspace/lexicon-symbols.d.ts +29 -0
  161. package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
  162. package/dist/workspace/lineage-upgrade.d.ts +31 -2
  163. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  164. package/dist/workspace/point-answer.schema.json +42 -2
  165. package/dist/workspace/points-cli.d.ts +10 -3
  166. package/dist/workspace/points-cli.d.ts.map +1 -1
  167. package/dist/workspace/points.d.ts +66 -5
  168. package/dist/workspace/points.d.ts.map +1 -1
  169. package/dist/workspace/reason-codes.d.ts +33 -4
  170. package/dist/workspace/reason-codes.d.ts.map +1 -1
  171. package/dist/workspace/record-sessions.d.ts +5 -7
  172. package/dist/workspace/record-sessions.d.ts.map +1 -1
  173. package/dist/workspace/records-cli.d.ts +7 -0
  174. package/dist/workspace/records-cli.d.ts.map +1 -1
  175. package/dist/workspace/records-close.d.ts +7 -3
  176. package/dist/workspace/records-close.d.ts.map +1 -1
  177. package/dist/workspace/records-write.d.ts +66 -6
  178. package/dist/workspace/records-write.d.ts.map +1 -1
  179. package/dist/workspace/records.d.ts +28 -9
  180. package/dist/workspace/records.d.ts.map +1 -1
  181. package/dist/workspace/runs-cli.d.ts +138 -6
  182. package/dist/workspace/runs-cli.d.ts.map +1 -1
  183. package/dist/workspace/runs.d.ts +132 -11
  184. package/dist/workspace/runs.d.ts.map +1 -1
  185. package/dist/workspace/session-kinds.d.ts +1 -1
  186. package/dist/workspace/session-kinds.d.ts.map +1 -1
  187. package/dist/workspace/squash.d.ts +80 -0
  188. package/dist/workspace/squash.d.ts.map +1 -0
  189. package/dist/workspace/status-gates.d.ts +2 -0
  190. package/dist/workspace/status-gates.d.ts.map +1 -1
  191. package/dist/workspace/status.d.ts +54 -0
  192. package/dist/workspace/status.d.ts.map +1 -1
  193. package/dist/workspace/symbols.d.ts +16 -5
  194. package/dist/workspace/symbols.d.ts.map +1 -1
  195. package/dist/workspace/trust/run-commits.d.ts +46 -0
  196. package/dist/workspace/trust/run-commits.d.ts.map +1 -0
  197. package/dist/workspace/trust/run-statement.d.ts +128 -0
  198. package/dist/workspace/trust/run-statement.d.ts.map +1 -0
  199. package/dist/workspace/trust/seal.d.ts +6 -1
  200. package/dist/workspace/trust/seal.d.ts.map +1 -1
  201. package/dist/workspace/trust/verify-cli.d.ts +1 -1
  202. package/dist/workspace/trust/verify.d.ts +16 -2
  203. package/dist/workspace/trust/verify.d.ts.map +1 -1
  204. package/dist/workspace/wip-cli.d.ts +21 -0
  205. package/dist/workspace/wip-cli.d.ts.map +1 -0
  206. package/dist/workspace/wip.d.ts +268 -0
  207. package/dist/workspace/wip.d.ts.map +1 -0
  208. package/dist/workspace/work-cli.d.ts.map +1 -1
  209. package/dist/workspace/work-evidence.d.ts +1 -1
  210. package/dist/workspace/work-evidence.d.ts.map +1 -1
  211. package/dist/workspace/work-size.d.ts +55 -0
  212. package/dist/workspace/work-size.d.ts.map +1 -0
  213. package/dist/workspace/workspace-kinds.schema.json +122 -0
  214. package/dist/workspace/write-lock-cli.d.ts +60 -0
  215. package/dist/workspace/write-lock-cli.d.ts.map +1 -0
  216. package/dist/workspace/write-lock.d.ts +152 -0
  217. package/dist/workspace/write-lock.d.ts.map +1 -0
  218. package/dist/workspace/write-scope.d.ts +6 -2
  219. package/dist/workspace/write-scope.d.ts.map +1 -1
  220. package/package.json +6 -1
  221. package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
  222. package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
  223. package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
  224. package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
  225. package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
  226. package/src/__fixtures__/change-set/warden.plan.json +44 -0
  227. package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
  228. package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
  229. package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
  230. package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
  231. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
  232. package/src/audit/core.test.ts +15 -0
  233. package/src/audit/core.ts +8 -2
  234. package/src/audit/discover.test.ts +11 -0
  235. package/src/audit/discover.ts +7 -2
  236. package/src/change-set-bundle.test.ts +57 -0
  237. package/src/change-set.test.ts +215 -0
  238. package/src/change-set.ts +445 -0
  239. package/src/cli/commands/audit-opt-in.test.ts +54 -0
  240. package/src/cli/commands/audit.ts +29 -2
  241. package/src/cli/commands/build.test.ts +19 -0
  242. package/src/cli/commands/build.ts +3 -2
  243. package/src/cli/handlers/build.ts +13 -1
  244. package/src/cli/handlers/change-set.test.ts +56 -0
  245. package/src/cli/handlers/change-set.ts +44 -0
  246. package/src/cli/handlers/fan-out.test.ts +221 -2
  247. package/src/cli/handlers/fan-out.ts +165 -75
  248. package/src/cli/handlers/graph.ts +3 -0
  249. package/src/cli/handlers/operator.test.ts +21 -0
  250. package/src/cli/handlers/operator.ts +34 -3
  251. package/src/cli/handlers/pr.ts +373 -0
  252. package/src/cli/handlers/run.ts +1 -0
  253. package/src/cli/main.test.ts +20 -0
  254. package/src/cli/main.ts +185 -23
  255. package/src/cli/mcp/never-over-mcp.test.ts +101 -0
  256. package/src/cli/mcp/op-tools.ts +24 -2
  257. package/src/cli/mcp/workspace-tools.test.ts +1 -1
  258. package/src/cli/mcp/workspace-tools.ts +34 -6
  259. package/src/cli/registry.ts +63 -1
  260. package/src/components/auto-release.ts +1 -1
  261. package/src/components/capability.ts +55 -0
  262. package/src/components/changed-files.ts +66 -0
  263. package/src/components/cli-support.ts +28 -5
  264. package/src/components/component.schema.json +17 -0
  265. package/src/components/component.ts +25 -0
  266. package/src/components/deploy-units.ts +3 -0
  267. package/src/components/driver.test.ts +52 -0
  268. package/src/components/driver.ts +31 -13
  269. package/src/components/fan-out-output.ts +42 -1
  270. package/src/components/fan-out-record.ts +69 -0
  271. package/src/components/fan-out-run.test.ts +17 -0
  272. package/src/components/fan-out-run.ts +304 -1
  273. package/src/components/fan-out-support.ts +3 -0
  274. package/src/components/fan-out-waves.test.ts +375 -0
  275. package/src/components/fan-out.ts +18 -17
  276. package/src/components/gated-wave-pipeline.ts +82 -0
  277. package/src/components/index.ts +1 -0
  278. package/src/components/layers.ts +30 -0
  279. package/src/components/pr-pipeline.ts +91 -0
  280. package/src/components/pr-run.test.ts +205 -0
  281. package/src/components/pr-run.ts +280 -0
  282. package/src/components/promote.ts +5 -0
  283. package/src/components/verbs/shell.test.ts +11 -0
  284. package/src/components/verbs/shell.ts +7 -3
  285. package/src/components/wave-plan.ts +124 -0
  286. package/src/discovery/sandbox/bundle.ts +1 -1
  287. package/src/gated-waves-bundle.test.ts +62 -0
  288. package/src/gated-waves.test.ts +118 -0
  289. package/src/gated-waves.ts +240 -0
  290. package/src/index.ts +1 -0
  291. package/src/lexicon.ts +51 -0
  292. package/src/lifecycle/gate-ledger.ts +7 -0
  293. package/src/lifecycle/git.ts +102 -16
  294. package/src/lint/post-synth.test.ts +19 -0
  295. package/src/lint/post-synth.ts +19 -1
  296. package/src/meta/declared-imports.test.ts +1 -1
  297. package/src/op/activities/activity-contracts.ts +84 -0
  298. package/src/op/activities/change-set.ts +114 -0
  299. package/src/op/activities/decide.ts +3 -0
  300. package/src/op/activities/factory.ts +675 -0
  301. package/src/op/activities/index.ts +6 -0
  302. package/src/op/activity-contract.test.ts +21 -1
  303. package/src/op/activity-contract.ts +19 -9
  304. package/src/op/builders.ts +23 -0
  305. package/src/op/decide-backend.ts +5 -1
  306. package/src/op/decide-read-inputs.ts +33 -5
  307. package/src/op/factory.e2e.test.ts +232 -0
  308. package/src/op/factory.ts +118 -0
  309. package/src/op/index.ts +9 -1
  310. package/src/op/op-ir.test.ts +9 -0
  311. package/src/op/op-ir.ts +4 -0
  312. package/src/op/op-verb-class.ts +3 -0
  313. package/src/op/types.ts +8 -0
  314. package/src/op/work-lease-run.ts +3 -0
  315. package/src/plan-summary.test.ts +263 -0
  316. package/src/plan-summary.ts +845 -0
  317. package/src/pr-forge.test.ts +237 -0
  318. package/src/pr-forge.ts +332 -0
  319. package/src/pr-loop-bundle.test.ts +46 -0
  320. package/src/pr-loop.test.ts +171 -0
  321. package/src/pr-loop.ts +324 -0
  322. package/src/telemetry-attribution.test.ts +23 -1
  323. package/src/telemetry-attribution.ts +51 -0
  324. package/src/terraform/hcl2json.test.ts +21 -0
  325. package/src/terraform/hcl2json.ts +81 -0
  326. package/src/terraform/parse.ts +11 -74
  327. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  328. package/src/workspace/app-kind.test.ts +196 -0
  329. package/src/workspace/box-cli.ts +113 -0
  330. package/src/workspace/box-factory.test.ts +4 -1
  331. package/src/workspace/box-factory.ts +37 -4
  332. package/src/workspace/box-listing-write.schema.json +204 -0
  333. package/src/workspace/box-listing.test.ts +234 -0
  334. package/src/workspace/box-listing.ts +324 -0
  335. package/src/workspace/box-publish.schema.json +498 -0
  336. package/src/workspace/box-publish.test.ts +204 -0
  337. package/src/workspace/box-publish.ts +420 -0
  338. package/src/workspace/box-ship.test.ts +96 -0
  339. package/src/workspace/broker-protocol.schema.json +186 -0
  340. package/src/workspace/broker-protocol.test.ts +122 -0
  341. package/src/workspace/broker-protocol.ts +317 -0
  342. package/src/workspace/builder-tiers.test.ts +146 -0
  343. package/src/workspace/change-set.schema.json +460 -0
  344. package/src/workspace/chant-migrations.e2e.test.ts +17 -5
  345. package/src/workspace/checks/boxes.test.ts +3 -0
  346. package/src/workspace/checks.ts +10 -3
  347. package/src/workspace/component-environments.test.ts +105 -0
  348. package/src/workspace/composites.schema.json +23 -4
  349. package/src/workspace/composites.test.ts +8 -8
  350. package/src/workspace/composites.ts +13 -2
  351. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
  352. package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
  353. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
  354. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
  355. package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  356. package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  357. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
  358. package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  359. package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
  361. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
  362. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
  363. package/src/workspace/conformance/broker.test.ts +122 -0
  364. package/src/workspace/conformance/broker.ts +792 -0
  365. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  366. package/src/workspace/conformance/index.mjs +22 -1
  367. package/src/workspace/conformance/index.ts +5 -1
  368. package/src/workspace/conformance/vitest.ts +33 -1
  369. package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
  370. package/src/workspace/conformance/writer.test.ts +32 -2
  371. package/src/workspace/conformance/writer.ts +339 -28
  372. package/src/workspace/decide.test.ts +208 -4
  373. package/src/workspace/decide.ts +427 -73
  374. package/src/workspace/decision-points.schema.json +11 -4
  375. package/src/workspace/declaration.schema.json +153 -4
  376. package/src/workspace/declaration.ts +141 -0
  377. package/src/workspace/environments.ts +66 -14
  378. package/src/workspace/factory-rules.test.ts +62 -0
  379. package/src/workspace/factory-rules.ts +218 -0
  380. package/src/workspace/graph-cli.ts +2 -2
  381. package/src/workspace/graph.schema.json +67 -0
  382. package/src/workspace/identity.test.ts +23 -0
  383. package/src/workspace/init.ts +82 -2
  384. package/src/workspace/intent-cli.ts +26 -9
  385. package/src/workspace/intent-patch-id.test.ts +162 -0
  386. package/src/workspace/intent-record.schema.json +130 -3
  387. package/src/workspace/intent-record.ts +74 -28
  388. package/src/workspace/intent-reviews.test.ts +76 -0
  389. package/src/workspace/intent-squash.test.ts +213 -0
  390. package/src/workspace/intent-why.test.ts +5 -5
  391. package/src/workspace/intent.schema.json +234 -4
  392. package/src/workspace/intent.ts +260 -66
  393. package/src/workspace/json-edit.test.ts +121 -0
  394. package/src/workspace/json-edit.ts +142 -0
  395. package/src/workspace/jsonc.ts +20 -0
  396. package/src/workspace/kinds.ts +157 -2
  397. package/src/workspace/lexicon-symbols.test.ts +148 -0
  398. package/src/workspace/lexicon-symbols.ts +125 -0
  399. package/src/workspace/lineage-upgrade.test.ts +63 -0
  400. package/src/workspace/lineage-upgrade.ts +146 -5
  401. package/src/workspace/plan-summary.schema.json +155 -0
  402. package/src/workspace/point-answer.schema.json +42 -2
  403. package/src/workspace/points-cli.ts +42 -16
  404. package/src/workspace/points-write.schema.json +162 -4
  405. package/src/workspace/points.schema.json +157 -2
  406. package/src/workspace/points.test.ts +13 -1
  407. package/src/workspace/points.ts +118 -12
  408. package/src/workspace/pr-report.schema.json +95 -0
  409. package/src/workspace/profiles.test.ts +159 -0
  410. package/src/workspace/read-contract.test.ts +2 -2
  411. package/src/workspace/reason-codes.test.ts +13 -1
  412. package/src/workspace/reason-codes.ts +38 -4
  413. package/src/workspace/record-sessions.ts +9 -8
  414. package/src/workspace/records-amend.schema.json +78 -0
  415. package/src/workspace/records-cli.ts +20 -6
  416. package/src/workspace/records-close.schema.json +78 -0
  417. package/src/workspace/records-close.ts +31 -7
  418. package/src/workspace/records-concurrent.e2e.test.ts +135 -0
  419. package/src/workspace/records-concurrent.test.ts +145 -0
  420. package/src/workspace/records-formats.test.ts +46 -10
  421. package/src/workspace/records-new.schema.json +7 -0
  422. package/src/workspace/records-review.schema.json +78 -0
  423. package/src/workspace/records-sessions-write.test.ts +69 -0
  424. package/src/workspace/records-sessions.test.ts +22 -0
  425. package/src/workspace/records-write.ts +192 -25
  426. package/src/workspace/records.schema.json +16 -0
  427. package/src/workspace/records.ts +50 -9
  428. package/src/workspace/reference-kinds/app/package.json +10 -0
  429. package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  430. package/src/workspace/run-statement.schema.json +622 -0
  431. package/src/workspace/runs-cli.ts +343 -14
  432. package/src/workspace/runs-statement.test.ts +239 -0
  433. package/src/workspace/runs-write.schema.json +416 -5
  434. package/src/workspace/runs.schema.json +451 -4
  435. package/src/workspace/runs.test.ts +39 -0
  436. package/src/workspace/runs.ts +296 -35
  437. package/src/workspace/session-kinds.ts +3 -3
  438. package/src/workspace/squash.ts +198 -0
  439. package/src/workspace/status-contract.test.ts +5 -2
  440. package/src/workspace/status-gates.ts +3 -1
  441. package/src/workspace/status.schema.json +126 -3
  442. package/src/workspace/status.ts +90 -1
  443. package/src/workspace/symbols.ts +20 -9
  444. package/src/workspace/trust/run-commits.ts +115 -0
  445. package/src/workspace/trust/run-statement.ts +226 -0
  446. package/src/workspace/trust/seal.ts +8 -2
  447. package/src/workspace/trust/verify-cli.ts +6 -5
  448. package/src/workspace/trust/verify.ts +39 -6
  449. package/src/workspace/wip-cli.ts +97 -0
  450. package/src/workspace/wip-write.schema.json +504 -0
  451. package/src/workspace/wip.schema.json +334 -0
  452. package/src/workspace/wip.test.ts +294 -0
  453. package/src/workspace/wip.ts +753 -0
  454. package/src/workspace/work-cli.ts +3 -0
  455. package/src/workspace/work-evidence.schema.json +3 -0
  456. package/src/workspace/work-size.test.ts +77 -0
  457. package/src/workspace/work-size.ts +93 -0
  458. package/src/workspace/workspace-kinds.schema.json +122 -0
  459. package/src/workspace/write-lock-cli.ts +131 -0
  460. package/src/workspace/write-lock.schema.json +172 -0
  461. package/src/workspace/write-lock.test.ts +189 -0
  462. package/src/workspace/write-lock.ts +474 -0
  463. package/src/workspace/write-scope.test.ts +16 -1
  464. package/src/workspace/write-scope.ts +33 -11
@@ -0,0 +1,31 @@
1
+ <!-- chant-pr:prod -->
2
+ ### chant apply for `prod`
3
+
4
+ Applied 2 members. Head `222222222222`, measured from `111111111111`.
5
+
6
+ | Member | Component | Status | Create | Update | Replace | Delete |
7
+ |---|---|---|---:|---:|---:|---:|
8
+ | `a` | a | applied | 0 | 1 | 0 | 0 |
9
+ | `app` | app | applied, inputs from a moved | 0 | 0 | 0 | 0 |
10
+
11
+ A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.
12
+
13
+ Plan digest: `jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65`
14
+
15
+ Approved by `github:alice` for this digest.
16
+
17
+ ### Plan summary
18
+
19
+ 2 members: 2 groups, 0 destroys or replacements.
20
+
21
+ #### Group e84467713bf6: 1 member (outlier), change
22
+
23
+ ```
24
+ ~ terraform_data.subnet: input
25
+ ```
26
+
27
+ Members: `a`
28
+
29
+ #### Group 49648f9475c7: 1 member (outlier), no changes
30
+
31
+ Members: `app`
@@ -0,0 +1,31 @@
1
+ <!-- chant-pr:prod -->
2
+ ### chant apply for `prod`
3
+
4
+ The apply refused. Head `222222222222`, measured from `111111111111`.
5
+
6
+ > The plan changed after review, so nothing was applied. approved: jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000063; planned now: jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65. Read the new plan, and if it is right, approve it: chant approve pr-12 pr-apply --plan jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65 --approver <you> --sign; then run the apply again.
7
+
8
+ | Member | Component | Status | Create | Update | Replace | Delete |
9
+ |---|---|---|---:|---:|---:|---:|
10
+ | `a` | a | planned | 0 | 1 | 0 | 0 |
11
+ | `app` | app | planned | 0 | 0 | 0 | 0 |
12
+
13
+ Plan digest: `jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65`
14
+
15
+ An approval stands for `jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000063`, not for this digest: the plan changed since it was approved.
16
+
17
+ ### Plan summary
18
+
19
+ 2 members: 2 groups, 0 destroys or replacements.
20
+
21
+ #### Group e84467713bf6: 1 member (outlier), change
22
+
23
+ ```
24
+ ~ terraform_data.subnet: input
25
+ ```
26
+
27
+ Members: `a`
28
+
29
+ #### Group 49648f9475c7: 1 member (outlier), no changes
30
+
31
+ Members: `app`
@@ -0,0 +1,127 @@
1
+ {
2
+ "$schema": "https://intentius.io/chant/schemas/workspace/pr-report/v1/pr-report.schema.json",
3
+ "contract": 1,
4
+ "stage": "plan",
5
+ "pr": 12,
6
+ "env": "prod",
7
+ "base": "1111111111111111111111111111111111111111",
8
+ "head": "2222222222222222222222222222222222222222",
9
+ "op": "pr-12",
10
+ "gate": "pr-apply",
11
+ "digest": "jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65",
12
+ "status": "planned",
13
+ "approval": {
14
+ "status": "pending"
15
+ },
16
+ "selection": {
17
+ "changed": [
18
+ "a"
19
+ ],
20
+ "dependents": [
21
+ "app"
22
+ ],
23
+ "unclaimed": [],
24
+ "indeterminate": [],
25
+ "waves": [
26
+ [
27
+ "a"
28
+ ],
29
+ [
30
+ "app"
31
+ ]
32
+ ]
33
+ },
34
+ "members": [
35
+ {
36
+ "member": "a",
37
+ "component": "a",
38
+ "planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000001",
39
+ "status": "planned",
40
+ "counts": {
41
+ "create": 0,
42
+ "update": 1,
43
+ "replace": 0,
44
+ "delete": 0
45
+ }
46
+ },
47
+ {
48
+ "member": "app",
49
+ "component": "app",
50
+ "planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000002",
51
+ "status": "planned",
52
+ "counts": {
53
+ "create": 0,
54
+ "update": 0,
55
+ "replace": 0,
56
+ "delete": 0
57
+ }
58
+ }
59
+ ],
60
+ "changeSet": {
61
+ "$schema": "https://intentius.io/chant/schemas/workspace/change-set/v1/change-set.schema.json",
62
+ "contract": 1,
63
+ "digest": "jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65",
64
+ "members": [
65
+ {
66
+ "member": "a",
67
+ "lexicon": "terraform",
68
+ "planner": "tofu",
69
+ "status": "planned",
70
+ "planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000001",
71
+ "holes": []
72
+ },
73
+ {
74
+ "member": "app",
75
+ "lexicon": "terraform",
76
+ "planner": "tofu",
77
+ "status": "planned",
78
+ "planDigest": "jcs1-sha256:0000000000000000000000000000000000000000000000000000000000000002",
79
+ "holes": []
80
+ }
81
+ ],
82
+ "entries": [
83
+ {
84
+ "member": "a",
85
+ "lexicon": "terraform",
86
+ "planner": "tofu",
87
+ "address": "terraform_data.subnet",
88
+ "type": "terraform_data",
89
+ "action": "update",
90
+ "attributes": [
91
+ {
92
+ "path": "input",
93
+ "before": "v1",
94
+ "after": "v2"
95
+ }
96
+ ]
97
+ }
98
+ ],
99
+ "summary": {
100
+ "members": 2,
101
+ "entries": 1,
102
+ "actions": {
103
+ "create": 0,
104
+ "update": 1,
105
+ "replace": 0,
106
+ "delete": 0,
107
+ "read": 0,
108
+ "no-op": 0,
109
+ "forget": 0
110
+ },
111
+ "types": {
112
+ "terraform_data": {
113
+ "update": 1
114
+ }
115
+ },
116
+ "byMember": {
117
+ "a": {
118
+ "update": 1
119
+ }
120
+ },
121
+ "deletes": [],
122
+ "replacements": [],
123
+ "failed": [],
124
+ "holes": 0
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,37 @@
1
+ <!-- chant-pr:prod -->
2
+ ### chant plan for `prod`
3
+
4
+ Planned 2 members. Head `222222222222`, measured from `111111111111`.
5
+
6
+ | Member | Component | Status | Create | Update | Replace | Delete |
7
+ |---|---|---|---:|---:|---:|---:|
8
+ | `a` | a | planned | 0 | 1 | 0 | 0 |
9
+ | `app` | app | planned | 0 | 0 | 0 | 0 |
10
+
11
+ Plan digest: `jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65`
12
+
13
+ Not approved yet.
14
+
15
+ A reviewer who approved this pull request approves the plan with:
16
+
17
+ ```
18
+ chant approve pr-12 pr-apply --plan jcs1-sha256:46bff52773a158dffa5c895b98f8760d601a2f9dc6af7dc235dbf65496f7bf65 --approver github:<your-login> --sign
19
+ ```
20
+
21
+ It applies on merge if the plan is still this one. If it moved, the apply refuses and names both digests.
22
+
23
+ ### Plan summary
24
+
25
+ 2 members: 2 groups, 0 destroys or replacements.
26
+
27
+ #### Group e84467713bf6: 1 member (outlier), change
28
+
29
+ ```
30
+ ~ terraform_data.subnet: input
31
+ ```
32
+
33
+ Members: `a`
34
+
35
+ #### Group 49648f9475c7: 1 member (outlier), no changes
36
+
37
+ Members: `app`
@@ -128,6 +128,21 @@ describe("auditFiles", () => {
128
128
  expect(findings.map((f) => f.file).sort()).toEqual(["a.yml", "b.yml"]);
129
129
  });
130
130
 
131
+ test("skips an auditOptIn check unless enabledRules names it (#3190)", async () => {
132
+ const optIn: PostSynthCheck = {
133
+ id: "OPT001",
134
+ description: "opt-in",
135
+ auditOptIn: true,
136
+ check: () => [{ checkId: "OPT001", severity: "warning", message: "hit" }],
137
+ };
138
+ const input = [{ path: "a.yml", content: "x", lexicon: "github" }];
139
+ const run = (enabledRules?: ReadonlySet<string>) =>
140
+ auditFiles(input, { checksProvider: async () => [optIn], enabledRules });
141
+ expect(await run()).toEqual([]);
142
+ expect(await run(new Set(["OTHER"]))).toEqual([]);
143
+ expect((await run(new Set(["OPT001"]))).map((f) => f.checkId)).toEqual(["OPT001"]);
144
+ });
145
+
131
146
  test("accepts a lexicon name outside the built-in set (open AuditLexicon)", async () => {
132
147
  const fake: PostSynthCheck = {
133
148
  id: "CUSTOM001",
package/src/audit/core.ts CHANGED
@@ -231,7 +231,13 @@ export interface SuppressionStats {
231
231
  */
232
232
  export async function auditFiles(
233
233
  inputs: AuditInput[],
234
- opts: { checksProvider?: ChecksProvider; entitiesProvider?: EntitiesProvider; suppressionStats?: SuppressionStats } = {},
234
+ opts: {
235
+ checksProvider?: ChecksProvider;
236
+ entitiesProvider?: EntitiesProvider;
237
+ suppressionStats?: SuppressionStats;
238
+ /** Check ids the project enabled with `lint.rules`; a check marked `auditOptIn` runs only when listed here (#3190). */
239
+ enabledRules?: ReadonlySet<string>;
240
+ } = {},
235
241
  ): Promise<AuditFinding[]> {
236
242
  const provider = opts.checksProvider ?? defaultChecksProvider;
237
243
  const entitiesProvider = opts.entitiesProvider ?? defaultEntitiesProvider;
@@ -246,7 +252,7 @@ export async function auditFiles(
246
252
  }
247
253
 
248
254
  for (const [lexicon, files] of byLexicon) {
249
- const checks = await provider(lexicon);
255
+ const checks = (await provider(lexicon)).filter((c) => !c.auditOptIn || opts.enabledRules?.has(c.id));
250
256
  if (checks.length === 0) continue;
251
257
  const parseEntities = await entitiesProvider(lexicon);
252
258
  findings.push(...(await auditLexicon(lexicon, files, checks, parseEntities, opts.suppressionStats)));
@@ -126,6 +126,17 @@ describe("discoverByDetection (unified, detectTemplate-driven)", () => {
126
126
  expect(found.some((i) => i.path.includes(".terraform"))).toBe(false);
127
127
  });
128
128
 
129
+ test("terragrunt.hcl and root.hcl join the terraform bundle, and a unit's source path does not make a module of its target (#3417)", () => {
130
+ const files = [
131
+ { path: "live/app/terragrunt.hcl", content: 'terraform {\n source = "../../modules/app"\n}\n' },
132
+ { path: "root.hcl", content: "remote_state {\n}\n" },
133
+ { path: "modules/app/main.tf", content: 'resource "x" "y" {}\n' },
134
+ ];
135
+ const found = classifyFiles(files, [terraform], { baseDir: "/repo" });
136
+ expect(targets(found)).toEqual(["terraform:.", "terraform:live/app", "terraform:modules/app"]);
137
+ expect(found.find((i) => i.path === "live/app")!.content).toContain("# file: terragrunt.hcl");
138
+ });
139
+
129
140
  test("without the terraform plugin, .tf files are classified into nothing (skipped, same as an unloaded helm)", () => {
130
141
  const found = discoverByDetection(fixture("audit-terraform"), []);
131
142
  expect(found).toEqual([]);
@@ -151,7 +151,9 @@ function isDockerfileName(name: string): boolean {
151
151
  }
152
152
 
153
153
  function isTerraformFileName(name: string): boolean {
154
- return /\.tf$/i.test(name);
154
+ // `terragrunt.hcl` and `root.hcl` ride in the terraform bundle (#3417): the
155
+ // terraform lexicon reads them into Terragrunt entities, apart from modules.
156
+ return /\.tf$/i.test(name) || name === "terragrunt.hcl" || name === "root.hcl";
155
157
  }
156
158
 
157
159
  /**
@@ -470,7 +472,10 @@ function calledAsLocalModule(byDir: Map<string, Record<string, string>>): Set<st
470
472
  const called = new Set<string>();
471
473
  for (const [dir, bundle] of byDir) {
472
474
  const targets = new Set<string>();
473
- for (const source of Object.values(bundle)) {
475
+ for (const [name, source] of Object.entries(bundle)) {
476
+ // A Terragrunt unit's `terraform { source = "../modules/x" }` is not a
477
+ // `module` call: the directory it names stays a root of its own (#3417).
478
+ if (name === "terragrunt.hcl" || name === "root.hcl") continue;
474
479
  for (const match of source.matchAll(LOCAL_MODULE_SOURCE)) {
475
480
  const target = resolveRepoDir(dir, match[1]);
476
481
  if (target === undefined || target === dir || !byDir.has(target)) continue;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The change-set modules bundle small (#3181, ruling #3421).
3
+ *
4
+ * terragucci runs its stages in customer CI as one bundled file with no
5
+ * TypeScript toolchain. So `@intentius/chant/change-set`, the grouped plan
6
+ * summary at `@intentius/chant/plan-summary` (#3188) and the terraform
7
+ * lexicon's `change-set` subpath must not reach `typescript`, esbuild, zod or
8
+ * chant's fold, lint and codegen modules. This bundles the three entry points
9
+ * the way terragucci would and reads esbuild's own list of what went in.
10
+ */
11
+
12
+ import { join } from "node:path";
13
+ import { build } from "esbuild";
14
+ import { describe, expect, test } from "vitest";
15
+ import { REPO } from "./workspace/__fixtures__/contract-repo";
16
+
17
+ /** What the three bundles may hold together at most. They are about 40 KB today (9 KB change-set, 21 KB plan-summary, 10 KB terraform). */
18
+ const BUDGET_BYTES = 64 * 1024;
19
+
20
+ const FORBIDDEN = [/node_modules\/typescript\//, /node_modules\/esbuild\//, /node_modules\/zod\//, /\/src\/fold\//, /\/src\/lint\//, /\/src\/codegen\//];
21
+
22
+ describe("the change-set bundle", () => {
23
+ test("holds the document, the adapters, the summary and the digest, and nothing heavier", async () => {
24
+ const result = await build({
25
+ entryPoints: [
26
+ join(REPO, "packages", "core", "src", "change-set.ts"),
27
+ join(REPO, "packages", "core", "src", "plan-summary.ts"),
28
+ join(REPO, "lexicons", "terraform", "src", "change-set.ts"),
29
+ ],
30
+ bundle: true,
31
+ platform: "node",
32
+ format: "esm",
33
+ write: false,
34
+ metafile: true,
35
+ outdir: "out",
36
+ logLevel: "silent",
37
+ });
38
+ const inputs = Object.keys(result.metafile.inputs);
39
+ for (const pattern of FORBIDDEN) {
40
+ expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
41
+ }
42
+ // The whole graph: the three entry points, the terraform plan digest, and core's digest code.
43
+ expect(inputs.map((i) => i.replace(/^.*?(packages|lexicons)\//, "$1/")).sort()).toEqual([
44
+ "lexicons/terraform/src/change-set.ts",
45
+ "lexicons/terraform/src/plan-digest.ts",
46
+ "packages/core/src/change-set.ts",
47
+ "packages/core/src/content-digest.ts",
48
+ "packages/core/src/declarable.ts",
49
+ "packages/core/src/effect-receipt.ts",
50
+ "packages/core/src/intrinsic.ts",
51
+ "packages/core/src/lifecycle/plan-digest.ts",
52
+ "packages/core/src/plan-summary.ts",
53
+ ]);
54
+ const bytes = result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0);
55
+ expect(bytes).toBeLessThan(BUDGET_BYTES);
56
+ });
57
+ });
@@ -0,0 +1,215 @@
1
+ /**
2
+ * The change-set document (#3181): its schema, the lifecycle and reconcile
3
+ * adapters, and the digest.
4
+ *
5
+ * `__fixtures__/change-set/lifecycle-plan.floci.json` is real `chant
6
+ * lifecycle plan dev --owned --json` output: `lifecycle-v1.ts.txt` deployed as
7
+ * CloudFormation stack `dev` to floci 1.5.34 and snapshotted, then
8
+ * `lifecycle-v2.ts.txt` planned. It has a create and no-ops; the aws lexicon
9
+ * reads stack resources, so out-of-band drift does not reach it as an
10
+ * update. The update, delete, effect and hole mappings are pinned on typed
11
+ * entries below.
12
+ *
13
+ * `warden.plan.json` is the reconcile change set chant's own
14
+ * `diffCollection` produces for a warden's teams cycle: one create, one
15
+ * update and one owned delete. github-warden prints its plan as text today,
16
+ * so this is the shape it would write.
17
+ *
18
+ * Goldens regenerate with `UPDATE_GOLDEN=1`.
19
+ */
20
+
21
+ import { readdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { describe, expect, test } from "vitest";
24
+ import {
25
+ CHANGE_SET_SCHEMA_ID,
26
+ changeSetDigest,
27
+ composeChangeSet,
28
+ lifecyclePlanPart,
29
+ reconcilePlanPart,
30
+ verifyChangeSetDigest,
31
+ type ChangeSetPart,
32
+ } from "./change-set";
33
+ import { computePlanDigest } from "./lifecycle/plan-digest";
34
+ import type { ChangeSet as LifecycleChangeSet } from "./lifecycle/change-set";
35
+ import { diffCollection, diffFields, type ChangeSet as ReconcileChangeSet, type ChangeSetEntry as ReconcileEntry } from "./reconcile";
36
+ import { contract, REPO, validSchema } from "./workspace/__fixtures__/contract-repo";
37
+ import schema from "./workspace/change-set.schema.json";
38
+
39
+ const DIR = join(import.meta.dirname, "__fixtures__", "change-set");
40
+ const TF_DIR = join(REPO, "lexicons", "terraform", "src", "__fixtures__", "change-set");
41
+ const read = (dir: string, name: string): unknown => JSON.parse(readFileSync(join(dir, name), "utf-8"));
42
+
43
+ function golden(name: string, value: unknown): void {
44
+ const path = join(DIR, name);
45
+ const text = JSON.stringify(value, null, 2) + "\n";
46
+ if (process.env.UPDATE_GOLDEN) writeFileSync(path, text);
47
+ expect(text).toBe(readFileSync(path, "utf-8"));
48
+ }
49
+
50
+ const { expectValid, validate } = contract(schema);
51
+
52
+ /** A warden's teams cycle, planned by the reconcile primitives it is built on. */
53
+ function wardenPlan(): ReconcileChangeSet {
54
+ type Team = { privacy: string; description: string };
55
+ const desired = new Map<string, Team>([
56
+ ["platform", { privacy: "closed", description: "Platform team" }],
57
+ ["release", { privacy: "secret", description: "Release managers" }],
58
+ ]);
59
+ const live = new Map<string, Team>([
60
+ ["platform", { privacy: "secret", description: "Platform team" }],
61
+ ["contractors", { privacy: "closed", description: "Old contractors" }],
62
+ ]);
63
+ const entries: ReconcileEntry[] = [];
64
+ diffCollection({
65
+ resourceType: "team",
66
+ desired,
67
+ live,
68
+ compareFields: (d, l) => diffFields(d, l),
69
+ opts: { isOwned: () => true },
70
+ out: entries,
71
+ });
72
+ return { org: "acme", entries, managedCount: live.size };
73
+ }
74
+
75
+ describe("the change-set schema", () => {
76
+ test("is a valid draft 2020-12 document at the read contract's version", () => {
77
+ expect(validSchema(schema)).toBe(true);
78
+ expect(schema.$id).toBe(CHANGE_SET_SCHEMA_ID);
79
+ expect(schema.properties.contract).toEqual({ const: 1 });
80
+ });
81
+
82
+ test("every golden document in both fixture directories validates", () => {
83
+ const docs = [
84
+ ...readdirSync(DIR).filter((f) => f.endsWith(".change-set.golden.json")).map((f) => read(DIR, f)),
85
+ ...readdirSync(TF_DIR).filter((f) => f.endsWith(".change-set.golden.json")).map((f) => read(TF_DIR, f)),
86
+ ...readdirSync(TF_DIR).filter((f) => f.endsWith(".part.golden.json")).map((f) => composeChangeSet([read(TF_DIR, f) as ChangeSetPart])),
87
+ ];
88
+ expect(docs.length).toBeGreaterThanOrEqual(5);
89
+ for (const doc of docs) expectValid(doc);
90
+ });
91
+
92
+ test("refuses an unknown action, a missing digest and a malformed plan digest", () => {
93
+ const doc = composeChangeSet([lifecyclePlanPart({ member: "delivery", plan: read(DIR, "lifecycle-plan.floci.json") as LifecycleChangeSet })]);
94
+ expect(validate({ ...doc, entries: [{ ...doc.entries[0], action: "teleport" }] })).toBe(false);
95
+ const { digest: _d, ...noDigest } = doc;
96
+ expect(validate(noDigest)).toBe(false);
97
+ expect(validate({ ...doc, members: [{ ...doc.members[0], planDigest: "sha256:abc" }] })).toBe(false);
98
+ });
99
+ });
100
+
101
+ describe("lifecyclePlanPart", () => {
102
+ test("a real lifecycle plan against floci matches its golden", () => {
103
+ const plan = read(DIR, "lifecycle-plan.floci.json") as LifecycleChangeSet;
104
+ const part = lifecyclePlanPart({ member: "delivery", plan });
105
+ expect(part.member.planDigest).toBe(computePlanDigest("lifecycle-plan", plan));
106
+ expect(part.entries.map((e) => [e.address, e.action])).toEqual([
107
+ ["alerts", "no-op"], ["dlq", "create"], ["events", "no-op"], ["jobs", "no-op"],
108
+ ]);
109
+ golden("lifecycle.change-set.golden.json", composeChangeSet([part]));
110
+ });
111
+
112
+ test("update, replace, delete, effect, holes, and what is left out", () => {
113
+ const plan: LifecycleChangeSet = {
114
+ env: "prod",
115
+ entries: [
116
+ { name: "web", type: "Deployment", lexicon: "k8s", action: "update", evidence: { declared: true, inSnapshot: true, live: true, observed: true }, ownership: "owned", deltas: [{ path: "replicas", oldValue: 2, newValue: 3 }], disruption: "rolling" },
117
+ { name: "db", type: "AWS::RDS::DBInstance", lexicon: "aws", action: "update", evidence: { declared: true, inSnapshot: true, live: true, observed: true }, ownership: "owned", deltas: [{ path: "Engine", oldValue: "mysql", newValue: "postgres" }], disruption: "destroy", disruptionBecause: ["Engine"] },
118
+ { name: "old", type: "AWS::SQS::Queue", lexicon: "aws", action: "delete", evidence: { declared: false, inSnapshot: true, live: true, observed: true }, ownership: "owned" },
119
+ { name: "notify-receipt", type: "Receipt", lexicon: "aws", action: "effect", effect: "notify", effectReason: "receipt-absent", evidence: { declared: true, inSnapshot: false, live: false, observed: true }, ownership: "owned" },
120
+ { name: "cache", type: "AWS::ElastiCache::CacheCluster", lexicon: "aws", action: "unobserved", unobservedReason: "read-failed", evidence: { declared: true, inSnapshot: false, live: false, observed: false }, ownership: "unknown" },
121
+ { name: "stray", lexicon: "aws", action: "adopt", evidence: { declared: false, inSnapshot: false, live: true, observed: true }, ownership: "unknown" },
122
+ { name: "web-abc12", lexicon: "k8s", action: "runtime", runtimeOwner: "web", evidence: { declared: false, inSnapshot: false, live: true, observed: true }, ownership: "unknown" },
123
+ ],
124
+ };
125
+ const { member, entries } = lifecyclePlanPart({ member: "delivery", plan });
126
+ expect(entries.map((e) => [e.address, e.action, e.disruption ?? null])).toEqual([
127
+ ["web", "update", "rolling"],
128
+ ["db", "replace", "destroy"],
129
+ ["old", "delete", "destroy"],
130
+ ["notify-receipt", "create", null],
131
+ ]);
132
+ expect(entries[1].attributes).toEqual([{ path: "Engine", before: "mysql", after: "postgres", forcesReplacement: true }]);
133
+ expect(member.holes).toEqual([{ address: "cache", type: "AWS::ElastiCache::CacheCluster", reason: "read-failed" }]);
134
+ expect(member.lexicon).toBe("chant");
135
+ const doc = composeChangeSet([{ member, entries }]);
136
+ expectValid(doc);
137
+ expect(doc.summary.holes).toBe(1);
138
+ expect(doc.summary.replacements).toEqual([{ member: "delivery", address: "db", type: "AWS::RDS::DBInstance", disruption: "destroy" }]);
139
+ });
140
+ });
141
+
142
+ describe("reconcilePlanPart", () => {
143
+ test("a warden's teams cycle, as chant's reconcile primitives plan it", () => {
144
+ const plan = wardenPlan();
145
+ golden("warden.plan.json", plan);
146
+ const part = reconcilePlanPart({ member: "warden", plan: read(DIR, "warden.plan.json") as ReconcileChangeSet });
147
+ expect(part.entries.map((e) => [e.address, e.action, e.disruption ?? null])).toEqual([
148
+ ["team.contractors", "delete", "destroy"],
149
+ ["team.platform", "update", "unknown"],
150
+ ["team.release", "create", null],
151
+ ]);
152
+ expect(part.entries[1].attributes).toEqual([{ path: "privacy", before: "secret", after: "closed" }]);
153
+ golden("warden.change-set.golden.json", composeChangeSet([part]));
154
+ });
155
+
156
+ test("the plan digest ignores the managed counts and the order of change sets", () => {
157
+ const a = wardenPlan();
158
+ const b: ReconcileChangeSet = { org: "beta", entries: [] };
159
+ const one = reconcilePlanPart({ member: "w", plan: [a, b] }).member.planDigest;
160
+ expect(reconcilePlanPart({ member: "w", plan: [b, { ...a, managedCount: 99 }] }).member.planDigest).toBe(one);
161
+ expect(reconcilePlanPart({ member: "w", plan: [b, { ...a, entries: a.entries.slice(1) }] }).member.planDigest).not.toBe(one);
162
+ });
163
+ });
164
+
165
+ describe("the digest", () => {
166
+ const lifecycle = () => lifecyclePlanPart({ member: "delivery", plan: read(DIR, "lifecycle-plan.floci.json") as LifecycleChangeSet });
167
+ const warden = () => reconcilePlanPart({ member: "warden", plan: wardenPlan() });
168
+ const tofu = () => read(TF_DIR, "tofu.part.golden.json") as ChangeSetPart;
169
+
170
+ test("is the set digest over { member, planDigest }, whatever order the parts come in", () => {
171
+ const doc = composeChangeSet([lifecycle(), warden(), tofu()]);
172
+ expect(composeChangeSet([tofu(), warden(), lifecycle()])).toEqual(doc);
173
+ const pairs = doc.members.map((m) => ({ member: m.member, planDigest: m.planDigest }));
174
+ expect(doc.digest).toBe(computePlanDigest("change-set", pairs));
175
+ expect(doc.digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
176
+ expect(verifyChangeSetDigest(doc)).toBe(true);
177
+ expect(verifyChangeSetDigest({ ...doc, digest: changeSetDigest(doc.members.slice(1)) })).toBe(false);
178
+ });
179
+
180
+ test("moves when one member's plan moves, and only then", () => {
181
+ const base = composeChangeSet([lifecycle(), warden()]).digest;
182
+ expect(composeChangeSet([lifecycle(), warden()], { chant: "9.9.9" }).digest).toBe(base);
183
+ const moved = wardenPlan();
184
+ moved.entries[0] = { ...moved.entries[0], after: { privacy: "closed", description: "Release engineers" } };
185
+ expect(composeChangeSet([lifecycle(), reconcilePlanPart({ member: "warden", plan: moved })]).digest).not.toBe(base);
186
+ // A wave is a subset of members: its digest is the same function over them.
187
+ expect(changeSetDigest([lifecycle().member])).toBe(composeChangeSet([lifecycle()]).digest);
188
+ });
189
+
190
+ test("leaves provisional members out, so approving the document never covers them", () => {
191
+ const provisional = tofu();
192
+ provisional.member = { ...provisional.member, provisional: true };
193
+ const doc = composeChangeSet([lifecycle(), warden(), provisional]);
194
+ expect(doc.members.find((m) => m.provisional)?.member).toBe(provisional.member.member);
195
+ expect(doc.digest).toBe(composeChangeSet([lifecycle(), warden()]).digest);
196
+ expect(verifyChangeSetDigest(doc)).toBe(true);
197
+ expectValid(doc);
198
+ });
199
+
200
+ test("refuses two parts for one member, and an entry naming another member", () => {
201
+ expect(() => composeChangeSet([warden(), warden()])).toThrow(/names member warden twice/);
202
+ const stray = warden();
203
+ stray.entries[0] = { ...stray.entries[0], member: "other" };
204
+ expect(() => composeChangeSet([stray])).toThrow(/names member other, inside member warden/);
205
+ });
206
+
207
+ test("the summary counts per action, type and member, and names every delete and replacement", () => {
208
+ const doc = composeChangeSet([lifecycle(), warden(), tofu()]);
209
+ expect(doc.summary.actions).toEqual({ create: 4, update: 3, replace: 1, delete: 2, read: 0, "no-op": 4, forget: 0 });
210
+ expect(doc.summary.byMember.warden).toEqual({ create: 1, update: 1, delete: 1 });
211
+ expect(doc.summary.types.terraform_data).toEqual({ create: 2, update: 2, replace: 1, delete: 1, "no-op": 1 });
212
+ expect(doc.summary.deletes.map((d) => `${d.member}:${d.address}`)).toEqual(["estate:terraform_data.old", "warden:team.contractors"]);
213
+ expect(doc.summary.replacements.map((d) => `${d.member}:${d.address}`)).toEqual(["estate:terraform_data.worker"]);
214
+ });
215
+ });