@intentius/chant 0.124.51 → 0.124.54

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 (593) hide show
  1. package/dist/audit/discover.d.ts.map +1 -1
  2. package/dist/audit/discover.js +3 -1
  3. package/dist/behaviour.d.ts +1 -1
  4. package/dist/behaviour.d.ts.map +1 -1
  5. package/dist/capabilities/templates.d.ts +65 -0
  6. package/dist/capabilities/templates.d.ts.map +1 -0
  7. package/dist/capabilities/templates.js +193 -0
  8. package/dist/capabilities/templates.json +145 -0
  9. package/dist/change-set-graph.d.ts +35 -3
  10. package/dist/change-set-graph.d.ts.map +1 -1
  11. package/dist/change-set-graph.js +60 -9
  12. package/dist/change-set.d.ts +65 -7
  13. package/dist/change-set.d.ts.map +1 -1
  14. package/dist/change-set.js +23 -12
  15. package/dist/ci-identity.d.ts +7 -2
  16. package/dist/ci-identity.d.ts.map +1 -1
  17. package/dist/ci-identity.js +4 -1
  18. package/dist/cli/commands/import-index.d.ts +52 -0
  19. package/dist/cli/commands/import-index.d.ts.map +1 -0
  20. package/dist/cli/commands/import-index.js +226 -0
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/lint.js +4 -2
  23. package/dist/cli/handlers/components.d.ts.map +1 -1
  24. package/dist/cli/handlers/components.js +11 -2
  25. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  26. package/dist/cli/handlers/fan-out.js +16 -3
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/graph.js +22 -4
  29. package/dist/cli/handlers/index-cloud.d.ts +14 -0
  30. package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
  31. package/dist/cli/handlers/index-cloud.js +22 -0
  32. package/dist/cli/handlers/index-diff.d.ts +18 -0
  33. package/dist/cli/handlers/index-diff.d.ts.map +1 -0
  34. package/dist/cli/handlers/index-diff.js +142 -0
  35. package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
  36. package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
  37. package/dist/cli/handlers/lifecycle-drift.js +64 -0
  38. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  39. package/dist/cli/handlers/lifecycle.js +188 -28
  40. package/dist/cli/handlers/misc.d.ts +9 -0
  41. package/dist/cli/handlers/misc.d.ts.map +1 -1
  42. package/dist/cli/handlers/misc.js +77 -0
  43. package/dist/cli/handlers/pr.d.ts.map +1 -1
  44. package/dist/cli/handlers/pr.js +17 -3
  45. package/dist/cli/handlers/promote.d.ts.map +1 -1
  46. package/dist/cli/handlers/promote.js +89 -15
  47. package/dist/cli/handlers/query.d.ts.map +1 -1
  48. package/dist/cli/handlers/query.js +28 -4
  49. package/dist/cli/handlers/scenario.d.ts.map +1 -1
  50. package/dist/cli/handlers/scenario.js +8 -7
  51. package/dist/cli/handlers/search.d.ts +34 -4
  52. package/dist/cli/handlers/search.d.ts.map +1 -1
  53. package/dist/cli/handlers/search.js +216 -36
  54. package/dist/cli/main.d.ts.map +1 -1
  55. package/dist/cli/main.js +55 -1
  56. package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
  57. package/dist/cli/mcp/lifecycle-tools.js +3 -2
  58. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  59. package/dist/cli/mcp/resource-handlers.js +4 -3
  60. package/dist/cli/mcp/workspace-tools.js +1 -1
  61. package/dist/cli/parse-args.d.ts.map +1 -1
  62. package/dist/cli/parse-args.js +55 -2
  63. package/dist/cli/registry.d.ts +19 -1
  64. package/dist/cli/registry.d.ts.map +1 -1
  65. package/dist/cloud-lister/aws.d.ts +35 -0
  66. package/dist/cloud-lister/aws.d.ts.map +1 -0
  67. package/dist/cloud-lister/aws.js +203 -0
  68. package/dist/cloud-lister/azure.d.ts +34 -0
  69. package/dist/cloud-lister/azure.d.ts.map +1 -0
  70. package/dist/cloud-lister/azure.js +129 -0
  71. package/dist/cloud-lister/common.d.ts +40 -0
  72. package/dist/cloud-lister/common.d.ts.map +1 -0
  73. package/dist/cloud-lister/common.js +83 -0
  74. package/dist/cloud-lister/credentials.d.ts +50 -0
  75. package/dist/cloud-lister/credentials.d.ts.map +1 -0
  76. package/dist/cloud-lister/credentials.js +86 -0
  77. package/dist/cloud-lister/fakes.d.ts +27 -0
  78. package/dist/cloud-lister/fakes.d.ts.map +1 -0
  79. package/dist/cloud-lister/fakes.js +50 -0
  80. package/dist/cloud-lister/gcp.d.ts +42 -0
  81. package/dist/cloud-lister/gcp.d.ts.map +1 -0
  82. package/dist/cloud-lister/gcp.js +143 -0
  83. package/dist/cloud-lister/guard.d.ts +49 -0
  84. package/dist/cloud-lister/guard.d.ts.map +1 -0
  85. package/dist/cloud-lister/guard.js +103 -0
  86. package/dist/cloud-lister/ids.d.ts +23 -0
  87. package/dist/cloud-lister/ids.d.ts.map +1 -0
  88. package/dist/cloud-lister/ids.js +42 -0
  89. package/dist/cloud-lister/index.d.ts +67 -0
  90. package/dist/cloud-lister/index.d.ts.map +1 -0
  91. package/dist/cloud-lister/index.js +102 -0
  92. package/dist/cloud-lister/kubernetes.d.ts +64 -0
  93. package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
  94. package/dist/cloud-lister/kubernetes.js +276 -0
  95. package/dist/cloud-lister/markers.d.ts +31 -0
  96. package/dist/cloud-lister/markers.d.ts.map +1 -0
  97. package/dist/cloud-lister/markers.js +73 -0
  98. package/dist/cloud-lister/mask.d.ts +31 -0
  99. package/dist/cloud-lister/mask.d.ts.map +1 -0
  100. package/dist/cloud-lister/mask.js +74 -0
  101. package/dist/cloud-lister/sigv4.d.ts +29 -0
  102. package/dist/cloud-lister/sigv4.d.ts.map +1 -0
  103. package/dist/cloud-lister/sigv4.js +54 -0
  104. package/dist/cloud-lister/transport.d.ts +55 -0
  105. package/dist/cloud-lister/transport.d.ts.map +1 -0
  106. package/dist/cloud-lister/transport.js +86 -0
  107. package/dist/cloud-lister/types.d.ts +121 -0
  108. package/dist/cloud-lister/types.d.ts.map +1 -0
  109. package/dist/cloud-lister/types.js +24 -0
  110. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  111. package/dist/components/capability-plugin-loader.js +6 -0
  112. package/dist/components/capability-plugin.d.ts +9 -0
  113. package/dist/components/capability-plugin.d.ts.map +1 -1
  114. package/dist/components/capability.d.ts +224 -0
  115. package/dist/components/capability.d.ts.map +1 -1
  116. package/dist/components/cli-support.d.ts +21 -0
  117. package/dist/components/cli-support.d.ts.map +1 -1
  118. package/dist/components/cli-support.js +58 -14
  119. package/dist/components/covered-round.d.ts +173 -0
  120. package/dist/components/covered-round.d.ts.map +1 -0
  121. package/dist/components/covered-round.js +200 -0
  122. package/dist/components/declared-reads.d.ts +38 -0
  123. package/dist/components/declared-reads.d.ts.map +1 -0
  124. package/dist/components/declared-reads.js +131 -0
  125. package/dist/components/deploy-units.d.ts.map +1 -1
  126. package/dist/components/deploy-units.js +4 -0
  127. package/dist/components/driver.d.ts +42 -1
  128. package/dist/components/driver.d.ts.map +1 -1
  129. package/dist/components/driver.js +74 -3
  130. package/dist/components/fan-out-run.d.ts +23 -1
  131. package/dist/components/fan-out-run.d.ts.map +1 -1
  132. package/dist/components/fan-out-run.js +82 -14
  133. package/dist/components/fan-out-support.d.ts +6 -0
  134. package/dist/components/fan-out-support.d.ts.map +1 -1
  135. package/dist/components/fan-out-support.js +4 -1
  136. package/dist/components/fan-out.d.ts +28 -0
  137. package/dist/components/fan-out.d.ts.map +1 -1
  138. package/dist/components/groups.d.ts +229 -0
  139. package/dist/components/groups.d.ts.map +1 -0
  140. package/dist/components/groups.js +760 -0
  141. package/dist/components/index.d.ts +7 -2
  142. package/dist/components/index.d.ts.map +1 -1
  143. package/dist/components/index.js +5 -0
  144. package/dist/components/plan-shape-conformance.d.ts +48 -0
  145. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  146. package/dist/components/plan-shape-conformance.js +122 -0
  147. package/dist/components/pr-run.d.ts +62 -7
  148. package/dist/components/pr-run.d.ts.map +1 -1
  149. package/dist/components/pr-run.js +354 -53
  150. package/dist/components/promote-rounds.d.ts +9 -0
  151. package/dist/components/promote-rounds.d.ts.map +1 -1
  152. package/dist/components/promote-rounds.js +23 -2
  153. package/dist/components/promote.d.ts +17 -0
  154. package/dist/components/promote.d.ts.map +1 -1
  155. package/dist/components/promote.js +36 -6
  156. package/dist/components/release-gate.d.ts +23 -1
  157. package/dist/components/release-gate.d.ts.map +1 -1
  158. package/dist/components/release-gate.js +97 -33
  159. package/dist/components/run-groups.d.ts +58 -0
  160. package/dist/components/run-groups.d.ts.map +1 -0
  161. package/dist/components/run-groups.js +78 -0
  162. package/dist/components/starter-plugin.d.ts +1 -0
  163. package/dist/components/starter-plugin.d.ts.map +1 -1
  164. package/dist/components/starter-plugin.js +6 -1
  165. package/dist/components/verbs/index.d.ts +1 -0
  166. package/dist/components/verbs/index.d.ts.map +1 -1
  167. package/dist/components/verbs/index.js +1 -0
  168. package/dist/components/verbs/member-outputs.d.ts +187 -0
  169. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  170. package/dist/components/verbs/member-outputs.js +218 -0
  171. package/dist/components/wave-plan.d.ts +2 -0
  172. package/dist/components/wave-plan.d.ts.map +1 -1
  173. package/dist/components/wave-plan.js +4 -1
  174. package/dist/config.d.ts +60 -0
  175. package/dist/config.d.ts.map +1 -1
  176. package/dist/config.js +27 -0
  177. package/dist/deep-observation.d.ts +1 -13
  178. package/dist/deep-observation.d.ts.map +1 -1
  179. package/dist/deep-observation.js +5 -22
  180. package/dist/drift-record.d.ts +542 -0
  181. package/dist/drift-record.d.ts.map +1 -0
  182. package/dist/drift-record.js +886 -0
  183. package/dist/engine-facts.d.ts +144 -0
  184. package/dist/engine-facts.d.ts.map +1 -0
  185. package/dist/engine-facts.js +14 -0
  186. package/dist/graph-ir.d.ts +153 -2
  187. package/dist/graph-ir.d.ts.map +1 -1
  188. package/dist/graph-ir.js +224 -6
  189. package/dist/graph-refs.d.ts +145 -0
  190. package/dist/graph-refs.d.ts.map +1 -1
  191. package/dist/graph-refs.js +305 -0
  192. package/dist/graph-schema/fixtures/adopt.json +15 -0
  193. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  194. package/dist/graph-schema/fixtures/index.json +68 -0
  195. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  196. package/dist/graph-schema/fixtures.d.ts +23 -0
  197. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  198. package/dist/graph-schema/fixtures.js +246 -0
  199. package/dist/graph-schema/generate.d.ts +15 -0
  200. package/dist/graph-schema/generate.d.ts.map +1 -0
  201. package/dist/graph-schema/generate.js +267 -0
  202. package/dist/graph-schema/graph.schema.json +1387 -0
  203. package/dist/health.schema.json +2 -1
  204. package/dist/identity.d.ts.map +1 -1
  205. package/dist/identity.js +2 -0
  206. package/dist/index.d.ts +1 -0
  207. package/dist/index.d.ts.map +1 -1
  208. package/dist/index.js +2 -0
  209. package/dist/instance-nodes.d.ts +159 -0
  210. package/dist/instance-nodes.d.ts.map +1 -0
  211. package/dist/instance-nodes.js +231 -0
  212. package/dist/lexicon.d.ts +72 -0
  213. package/dist/lexicon.d.ts.map +1 -1
  214. package/dist/lifecycle/adoption-file.d.ts +56 -0
  215. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  216. package/dist/lifecycle/adoption-file.js +123 -0
  217. package/dist/lifecycle/cloud-index.d.ts +186 -0
  218. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  219. package/dist/lifecycle/cloud-index.js +618 -0
  220. package/dist/lifecycle/derive-edges.d.ts +59 -0
  221. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  222. package/dist/lifecycle/derive-edges.js +126 -0
  223. package/dist/lifecycle/git.d.ts +10 -9
  224. package/dist/lifecycle/git.d.ts.map +1 -1
  225. package/dist/lifecycle/git.js +7 -1
  226. package/dist/lifecycle/identity-match.d.ts +133 -0
  227. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  228. package/dist/lifecycle/identity-match.js +147 -0
  229. package/dist/lifecycle/index-diff.d.ts +224 -0
  230. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  231. package/dist/lifecycle/index-diff.js +327 -0
  232. package/dist/lifecycle/index-document.d.ts +130 -0
  233. package/dist/lifecycle/index-document.d.ts.map +1 -0
  234. package/dist/lifecycle/index-document.js +189 -0
  235. package/dist/lifecycle/index-drift.d.ts +121 -0
  236. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  237. package/dist/lifecycle/index-drift.js +178 -0
  238. package/dist/lifecycle/index-import.d.ts +168 -0
  239. package/dist/lifecycle/index-import.d.ts.map +1 -0
  240. package/dist/lifecycle/index-import.js +515 -0
  241. package/dist/lifecycle/index-join.d.ts +129 -0
  242. package/dist/lifecycle/index-join.d.ts.map +1 -0
  243. package/dist/lifecycle/index-join.js +248 -0
  244. package/dist/lifecycle/index.d.ts +4 -0
  245. package/dist/lifecycle/index.d.ts.map +1 -1
  246. package/dist/lifecycle/index.js +4 -0
  247. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  248. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  249. package/dist/lifecycle/observation-baseline.js +144 -19
  250. package/dist/lifecycle/observe.d.ts.map +1 -1
  251. package/dist/lifecycle/observe.js +6 -1
  252. package/dist/lifecycle/query.d.ts +45 -4
  253. package/dist/lifecycle/query.d.ts.map +1 -1
  254. package/dist/lifecycle/query.js +89 -7
  255. package/dist/lifecycle/replay.d.ts +10 -3
  256. package/dist/lifecycle/replay.d.ts.map +1 -1
  257. package/dist/lifecycle/replay.js +10 -6
  258. package/dist/lifecycle/scenario.d.ts +4 -3
  259. package/dist/lifecycle/scenario.d.ts.map +1 -1
  260. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  261. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  263. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  264. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  265. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  266. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  267. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  268. package/dist/lifecycle/snapshot-store.js +832 -0
  269. package/dist/lifecycle/snapshot.d.ts +11 -0
  270. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  271. package/dist/lifecycle/snapshot.js +18 -5
  272. package/dist/lifecycle/type-listing.d.ts +148 -0
  273. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  274. package/dist/lifecycle/type-listing.js +166 -0
  275. package/dist/lint/component-checks.d.ts +15 -1
  276. package/dist/lint/component-checks.d.ts.map +1 -1
  277. package/dist/lint/component-checks.js +1 -0
  278. package/dist/lint/presets/relaxed.json +2 -1
  279. package/dist/lint/presets/strict.json +2 -1
  280. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  281. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  282. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  283. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  284. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  285. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  286. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  287. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  288. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  289. package/dist/lint/rules/comp/index.d.ts +2 -1
  290. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  291. package/dist/lint/rules/comp/index.js +4 -1
  292. package/dist/observation.d.ts +19 -2
  293. package/dist/observation.d.ts.map +1 -1
  294. package/dist/observation.js +18 -1
  295. package/dist/op/activities/activity-contracts.d.ts +1 -1
  296. package/dist/op/activities/activity-contracts.js +1 -1
  297. package/dist/op/activities/change-set.d.ts +6 -4
  298. package/dist/op/activities/change-set.d.ts.map +1 -1
  299. package/dist/op/activities/change-set.js +4 -2
  300. package/dist/op/activity-failure.d.ts +27 -0
  301. package/dist/op/activity-failure.d.ts.map +1 -1
  302. package/dist/op/activity-failure.js +31 -0
  303. package/dist/op/index.d.ts +2 -1
  304. package/dist/op/index.d.ts.map +1 -1
  305. package/dist/op/index.js +1 -1
  306. package/dist/op/local-executor.d.ts +5 -1
  307. package/dist/op/local-executor.d.ts.map +1 -1
  308. package/dist/op/local-executor.js +22 -8
  309. package/dist/pr-loop.d.ts +92 -3
  310. package/dist/pr-loop.d.ts.map +1 -1
  311. package/dist/pr-loop.js +62 -3
  312. package/dist/pulumi-urn.d.ts +101 -0
  313. package/dist/pulumi-urn.d.ts.map +1 -0
  314. package/dist/pulumi-urn.js +131 -0
  315. package/dist/release-floors.d.ts +23 -0
  316. package/dist/release-floors.d.ts.map +1 -0
  317. package/dist/release-floors.js +22 -0
  318. package/dist/sensitive-key.d.ts +23 -0
  319. package/dist/sensitive-key.d.ts.map +1 -0
  320. package/dist/sensitive-key.js +31 -0
  321. package/dist/wave-runtime.mjs +234 -234
  322. package/dist/workspace/adoption-report.d.ts +53 -0
  323. package/dist/workspace/adoption-report.d.ts.map +1 -0
  324. package/dist/workspace/adoption-report.js +76 -0
  325. package/dist/workspace/capabilities-contract.d.ts +7 -0
  326. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  327. package/dist/workspace/capabilities-contract.js +2 -1
  328. package/dist/workspace/capabilities.schema.json +98 -0
  329. package/dist/workspace/change-set.schema.json +42 -3
  330. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  331. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  332. package/dist/workspace/ci-cloud-identity.js +30 -1
  333. package/dist/workspace/compose-graph.d.ts +10 -1
  334. package/dist/workspace/compose-graph.d.ts.map +1 -1
  335. package/dist/workspace/compose-graph.js +6 -1
  336. package/dist/workspace/conformance/index.d.ts +1 -1
  337. package/dist/workspace/conformance/index.d.ts.map +1 -1
  338. package/dist/workspace/conformance/index.js +6 -1
  339. package/dist/workspace/drift-cli.d.ts +81 -0
  340. package/dist/workspace/drift-cli.d.ts.map +1 -0
  341. package/dist/workspace/drift-cli.js +317 -0
  342. package/dist/workspace/drift.schema.json +672 -0
  343. package/dist/workspace/graph-cli.d.ts +2 -0
  344. package/dist/workspace/graph-cli.d.ts.map +1 -1
  345. package/dist/workspace/graph-cli.js +3 -0
  346. package/dist/workspace/graph.schema.json +72 -0
  347. package/dist/workspace/init.d.ts +5 -1
  348. package/dist/workspace/init.d.ts.map +1 -1
  349. package/dist/workspace/init.js +55 -9
  350. package/dist/workspace/ledger.schema.json +1 -1
  351. package/dist/workspace/nested-graph.d.ts.map +1 -1
  352. package/dist/workspace/nested-graph.js +2 -0
  353. package/dist/workspace/pr-report.schema.json +76 -3
  354. package/dist/workspace/promote-waves.d.ts +112 -0
  355. package/dist/workspace/promote-waves.d.ts.map +1 -0
  356. package/dist/workspace/promote-waves.js +290 -0
  357. package/dist/workspace/promote.d.ts +84 -2
  358. package/dist/workspace/promote.d.ts.map +1 -1
  359. package/dist/workspace/promote.js +347 -61
  360. package/dist/workspace/reason-codes.d.ts +12 -0
  361. package/dist/workspace/reason-codes.d.ts.map +1 -1
  362. package/dist/workspace/reason-codes.js +14 -0
  363. package/dist/workspace/search-contract.d.ts +3 -3
  364. package/dist/workspace/search-contract.d.ts.map +1 -1
  365. package/dist/workspace/search-contract.js +2 -0
  366. package/dist/workspace/search.schema.json +103 -5
  367. package/package.json +14 -1
  368. package/src/__fixtures__/drift-record/README.md +58 -0
  369. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  370. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  371. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  372. package/src/__fixtures__/drift-record/component-status.json +91 -0
  373. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  374. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  375. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  376. package/src/audit/discover.e2e.test.ts +14 -0
  377. package/src/audit/discover.ts +3 -1
  378. package/src/behaviour.ts +1 -1
  379. package/src/capabilities/templates.json +145 -0
  380. package/src/capabilities/templates.test.ts +212 -0
  381. package/src/capabilities/templates.ts +204 -0
  382. package/src/change-set-bundle.test.ts +83 -0
  383. package/src/change-set-graph.ts +80 -9
  384. package/src/change-set.test.ts +24 -0
  385. package/src/change-set.ts +77 -14
  386. package/src/ci-identity-targets.test.ts +23 -0
  387. package/src/ci-identity.ts +10 -4
  388. package/src/cli/commands/import-index.test.ts +117 -0
  389. package/src/cli/commands/import-index.ts +265 -0
  390. package/src/cli/commands/lint.ts +4 -2
  391. package/src/cli/handlers/components.test.ts +9 -0
  392. package/src/cli/handlers/components.ts +12 -2
  393. package/src/cli/handlers/fan-out.ts +17 -3
  394. package/src/cli/handlers/graph.test.ts +42 -0
  395. package/src/cli/handlers/graph.ts +26 -4
  396. package/src/cli/handlers/index-cloud.ts +32 -0
  397. package/src/cli/handlers/index-diff.test.ts +68 -0
  398. package/src/cli/handlers/index-diff.ts +146 -0
  399. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  400. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  401. package/src/cli/handlers/lifecycle.test.ts +33 -11
  402. package/src/cli/handlers/lifecycle.ts +205 -30
  403. package/src/cli/handlers/misc.ts +73 -0
  404. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  405. package/src/cli/handlers/pr.ts +16 -3
  406. package/src/cli/handlers/promote.test.ts +64 -0
  407. package/src/cli/handlers/promote.ts +81 -6
  408. package/src/cli/handlers/query.ts +27 -4
  409. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  410. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  411. package/src/cli/handlers/scenario.test.ts +13 -0
  412. package/src/cli/handlers/scenario.ts +8 -7
  413. package/src/cli/handlers/search-index.test.ts +255 -0
  414. package/src/cli/handlers/search-json.test.ts +1 -1
  415. package/src/cli/handlers/search-ownership.test.ts +194 -0
  416. package/src/cli/handlers/search.ts +271 -38
  417. package/src/cli/main.test.ts +7 -0
  418. package/src/cli/main.ts +56 -1
  419. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  420. package/src/cli/mcp/resource-handlers.ts +4 -3
  421. package/src/cli/mcp/workspace-tools.ts +1 -1
  422. package/src/cli/parse-args.ts +40 -2
  423. package/src/cli/registry.ts +19 -1
  424. package/src/cloud-lister/aws.test.ts +202 -0
  425. package/src/cloud-lister/aws.ts +226 -0
  426. package/src/cloud-lister/azure.test.ts +132 -0
  427. package/src/cloud-lister/azure.ts +142 -0
  428. package/src/cloud-lister/common.ts +115 -0
  429. package/src/cloud-lister/credentials.ts +110 -0
  430. package/src/cloud-lister/fakes.ts +66 -0
  431. package/src/cloud-lister/gcp.test.ts +121 -0
  432. package/src/cloud-lister/gcp.ts +159 -0
  433. package/src/cloud-lister/guard.test.ts +198 -0
  434. package/src/cloud-lister/guard.ts +111 -0
  435. package/src/cloud-lister/ids.ts +54 -0
  436. package/src/cloud-lister/index.ts +134 -0
  437. package/src/cloud-lister/kubernetes.test.ts +160 -0
  438. package/src/cloud-lister/kubernetes.ts +284 -0
  439. package/src/cloud-lister/markers.ts +74 -0
  440. package/src/cloud-lister/mask.ts +77 -0
  441. package/src/cloud-lister/shape.test.ts +81 -0
  442. package/src/cloud-lister/sigv4.ts +70 -0
  443. package/src/cloud-lister/transport.ts +118 -0
  444. package/src/cloud-lister/types.ts +135 -0
  445. package/src/components/capability-plugin-loader.ts +4 -0
  446. package/src/components/capability-plugin.ts +10 -0
  447. package/src/components/capability.ts +238 -0
  448. package/src/components/cli-support.ts +70 -14
  449. package/src/components/covered-round.test.ts +166 -0
  450. package/src/components/covered-round.ts +293 -0
  451. package/src/components/declared-reads.test.ts +103 -0
  452. package/src/components/declared-reads.ts +134 -0
  453. package/src/components/deploy-units.ts +4 -0
  454. package/src/components/driver.ts +101 -4
  455. package/src/components/fan-out-run.ts +95 -15
  456. package/src/components/fan-out-support.ts +10 -1
  457. package/src/components/fan-out.ts +30 -0
  458. package/src/components/groups.test.ts +320 -0
  459. package/src/components/groups.ts +880 -0
  460. package/src/components/index.ts +59 -0
  461. package/src/components/plan-shape-conformance.ts +130 -0
  462. package/src/components/pr-env-run-bundle.test.ts +2 -0
  463. package/src/components/pr-run.test.ts +728 -4
  464. package/src/components/pr-run.ts +395 -55
  465. package/src/components/promote-rounds.ts +25 -2
  466. package/src/components/promote.ts +55 -5
  467. package/src/components/registry.test.ts +1 -1
  468. package/src/components/release-gate.ts +115 -30
  469. package/src/components/release-groups.test.ts +362 -0
  470. package/src/components/run-groups.ts +113 -0
  471. package/src/components/starter-plugin.ts +6 -0
  472. package/src/components/verbs/index.ts +1 -0
  473. package/src/components/verbs/member-outputs.test.ts +205 -0
  474. package/src/components/verbs/member-outputs.ts +308 -0
  475. package/src/components/wave-plan.ts +5 -2
  476. package/src/config.ts +44 -0
  477. package/src/deep-observation.ts +5 -23
  478. package/src/drift-record.test.ts +596 -0
  479. package/src/drift-record.ts +1206 -0
  480. package/src/engine-facts.guard.test.ts +111 -0
  481. package/src/engine-facts.test.ts +178 -0
  482. package/src/engine-facts.ts +154 -0
  483. package/src/gated-waves-bundle.test.ts +2 -0
  484. package/src/graph-ir-live.test.ts +92 -0
  485. package/src/graph-ir.ts +317 -7
  486. package/src/graph-refs.ts +416 -0
  487. package/src/graph-schema/fixtures/adopt.json +15 -0
  488. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  489. package/src/graph-schema/fixtures/index.json +68 -0
  490. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  491. package/src/graph-schema/fixtures.ts +267 -0
  492. package/src/graph-schema/generate.ts +263 -0
  493. package/src/graph-schema/graph-schema.test.ts +153 -0
  494. package/src/graph-schema/graph.schema.json +1387 -0
  495. package/src/health.schema.json +2 -1
  496. package/src/identity.ts +2 -0
  497. package/src/index.ts +2 -0
  498. package/src/instance-nodes.ts +288 -0
  499. package/src/lexicon.ts +76 -0
  500. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  501. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  502. package/src/lifecycle/adoption-file.test.ts +100 -0
  503. package/src/lifecycle/adoption-file.ts +154 -0
  504. package/src/lifecycle/cloud-index.test.ts +517 -0
  505. package/src/lifecycle/cloud-index.ts +736 -0
  506. package/src/lifecycle/derive-edges.test.ts +193 -0
  507. package/src/lifecycle/derive-edges.ts +182 -0
  508. package/src/lifecycle/git.ts +10 -4
  509. package/src/lifecycle/identity-match.test.ts +308 -0
  510. package/src/lifecycle/identity-match.ts +257 -0
  511. package/src/lifecycle/index-diff.test.ts +198 -0
  512. package/src/lifecycle/index-diff.ts +476 -0
  513. package/src/lifecycle/index-document.test.ts +64 -0
  514. package/src/lifecycle/index-document.ts +299 -0
  515. package/src/lifecycle/index-drift.test.ts +191 -0
  516. package/src/lifecycle/index-drift.ts +304 -0
  517. package/src/lifecycle/index-import.test.ts +255 -0
  518. package/src/lifecycle/index-import.ts +634 -0
  519. package/src/lifecycle/index-join.test.ts +250 -0
  520. package/src/lifecycle/index-join.ts +304 -0
  521. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  522. package/src/lifecycle/index.ts +4 -0
  523. package/src/lifecycle/member-ledger.test.ts +25 -7
  524. package/src/lifecycle/observation-baseline.test.ts +72 -1
  525. package/src/lifecycle/observation-baseline.ts +190 -27
  526. package/src/lifecycle/observe.ts +6 -1
  527. package/src/lifecycle/query.test.ts +69 -1
  528. package/src/lifecycle/query.ts +103 -10
  529. package/src/lifecycle/replay.test.ts +9 -5
  530. package/src/lifecycle/replay.ts +9 -5
  531. package/src/lifecycle/scenario.ts +4 -3
  532. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  533. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  534. package/src/lifecycle/snapshot-store.test.ts +385 -0
  535. package/src/lifecycle/snapshot-store.ts +981 -0
  536. package/src/lifecycle/snapshot.test.ts +13 -0
  537. package/src/lifecycle/snapshot.ts +23 -6
  538. package/src/lifecycle/type-listing.test.ts +166 -0
  539. package/src/lifecycle/type-listing.ts +309 -0
  540. package/src/lint/component-checks.ts +17 -1
  541. package/src/lint/config.test.ts +3 -0
  542. package/src/lint/presets/relaxed.json +2 -1
  543. package/src/lint/presets/strict.json +2 -1
  544. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  545. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  546. package/src/lint/rules/comp/comp.test.ts +2 -1
  547. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  548. package/src/lint/rules/comp/comp008.test.ts +77 -0
  549. package/src/lint/rules/comp/index.ts +4 -1
  550. package/src/observation-edges.test.ts +33 -0
  551. package/src/observation.ts +36 -1
  552. package/src/op/activities/activity-contracts.ts +1 -1
  553. package/src/op/activities/change-set.ts +8 -5
  554. package/src/op/activity-failure.ts +36 -0
  555. package/src/op/index.ts +2 -1
  556. package/src/op/local-executor.test.ts +80 -0
  557. package/src/op/local-executor.ts +27 -9
  558. package/src/pr-loop-bundle.test.ts +5 -2
  559. package/src/pr-loop.test.ts +57 -1
  560. package/src/pr-loop.ts +154 -6
  561. package/src/pulumi-urn.ts +151 -0
  562. package/src/release-floors.ts +22 -0
  563. package/src/sensitive-key.ts +34 -0
  564. package/src/workspace/adoption-report.test.ts +61 -0
  565. package/src/workspace/adoption-report.ts +103 -0
  566. package/src/workspace/capabilities-contract.test.ts +4 -0
  567. package/src/workspace/capabilities-contract.ts +9 -1
  568. package/src/workspace/capabilities.schema.json +98 -0
  569. package/src/workspace/change-set.schema.json +42 -3
  570. package/src/workspace/ci-cloud-identity.ts +31 -1
  571. package/src/workspace/compose-graph.test.ts +12 -0
  572. package/src/workspace/compose-graph.ts +12 -1
  573. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  574. package/src/workspace/conformance/index.ts +6 -1
  575. package/src/workspace/drift-cli.test.ts +65 -0
  576. package/src/workspace/drift-cli.ts +365 -0
  577. package/src/workspace/drift.schema.json +672 -0
  578. package/src/workspace/graph-cli.ts +5 -0
  579. package/src/workspace/graph.schema.json +72 -0
  580. package/src/workspace/init.test.ts +38 -1
  581. package/src/workspace/init.ts +50 -8
  582. package/src/workspace/intent-gaps.test.ts +18 -2
  583. package/src/workspace/ledger.schema.json +1 -1
  584. package/src/workspace/nested-graph.ts +1 -0
  585. package/src/workspace/pr-report.schema.json +76 -3
  586. package/src/workspace/promote-waves.test.ts +296 -0
  587. package/src/workspace/promote-waves.ts +340 -0
  588. package/src/workspace/promote.test.ts +158 -4
  589. package/src/workspace/promote.ts +398 -57
  590. package/src/workspace/reason-codes.test.ts +4 -1
  591. package/src/workspace/reason-codes.ts +14 -0
  592. package/src/workspace/search-contract.ts +2 -0
  593. package/src/workspace/search.schema.json +103 -5
@@ -0,0 +1,10 @@
1
+ import type { Component } from "../../../../../../components/component";
2
+ import { phase } from "../../../../../../components/component";
3
+
4
+ /** COMP008 pass case: the component deploys only its own root, "dns". */
5
+ export const dns: Component = {
6
+ name: "dns",
7
+ archetype: "infra",
8
+ dependsOn: [],
9
+ deploy: [phase("Apply", [{ kind: "terraform-apply", root: "dns" }])],
10
+ };
@@ -54,7 +54,7 @@ describe("COMP000: component discovery errors", () => {
54
54
  });
55
55
 
56
56
  describe("loadComponentChecks()", () => {
57
- it("returns all seven COMP* checks, sorted by id, each error or warning severity", () => {
57
+ it("returns all eight COMP* checks, sorted by id, each error or warning severity", () => {
58
58
  expect(checks.map((c) => c.id)).toEqual([
59
59
  "COMP001",
60
60
  "COMP002",
@@ -63,6 +63,7 @@ describe("loadComponentChecks()", () => {
63
63
  "COMP005",
64
64
  "COMP006",
65
65
  "COMP007",
66
+ "COMP008",
66
67
  ]);
67
68
  for (const check of checks) {
68
69
  expect(["error", "warning"]).toContain(check.severity);
@@ -0,0 +1,121 @@
1
+ /**
2
+ * COMP008: applies-signal-only-root (#4490)
3
+ *
4
+ * Flags a component whose `terraform-apply` step deploys a root the project
5
+ * marks `signalOnly: true` in `terraform.roots` (#4484). Such a root belongs
6
+ * to another workspace member and is declared here only so a change to it
7
+ * brings this project into the change: the build skips it, so this
8
+ * project's graph does not draw it, and the live reads skip it too. A
9
+ * component that applies it would apply a root chant otherwise treats as
10
+ * the other member's, so the warning names the root and the member that
11
+ * owns it (the workspace member whose directory holds the root's `dir`).
12
+ *
13
+ * The roots come from the project's chant config, resolved by `chant lint`
14
+ * ({@link signalOnlyRootsOf}) and handed to the check on
15
+ * `ComponentCheckContext.signalOnlyRoots`. A check run without them (a
16
+ * direct unit test, a project with no terraform roots) flags nothing.
17
+ *
18
+ * Triggers on: `{ kind: "terraform-apply", root: "azure" }` where
19
+ * `terraform.roots.azure.signalOnly` is `true`.
20
+ * OK: the same step on a root without the marker, or no `terraform-apply`
21
+ * step naming the marked root.
22
+ */
23
+
24
+ import { isAbsolute, relative, resolve } from "node:path";
25
+ import type { ComponentCheck, ComponentCheckContext, ComponentCheckDiagnostic, SignalOnlyRoot } from "../../component-checks";
26
+ import { walkComponent } from "./support";
27
+
28
+ export const comp008AppliesSignalOnlyRootRule: ComponentCheck = {
29
+ id: "COMP008",
30
+ severity: "warning",
31
+ category: "correctness",
32
+ description: "A terraform-apply step deploys a root marked signalOnly, which another workspace member owns",
33
+ check(ctx: ComponentCheckContext): ComponentCheckDiagnostic[] {
34
+ const roots = ctx.signalOnlyRoots;
35
+ if (!roots || roots.size === 0) return [];
36
+ const diagnostics: ComponentCheckDiagnostic[] = [];
37
+
38
+ for (const [name, { component, filePath }] of ctx.components) {
39
+ const seen = new Set<string>();
40
+ for (const { step, phaseName } of walkComponent(component).steps) {
41
+ if (step.kind !== "terraform-apply") continue;
42
+ const root = (step as { root?: unknown }).root;
43
+ if (typeof root !== "string" || seen.has(root)) continue;
44
+ const marked = roots.get(root);
45
+ if (!marked) continue;
46
+ seen.add(root);
47
+ const owner = marked.owner
48
+ ? `workspace member "${marked.owner}" owns it`
49
+ : "no workspace member was found to own it";
50
+ diagnostics.push({
51
+ checkId: "COMP008",
52
+ severity: "warning",
53
+ component: name,
54
+ file: filePath,
55
+ message:
56
+ `Component "${name}": the "terraform-apply" step (phase "${phaseName}") deploys root "${root}" ` +
57
+ `(${marked.dir}), which terraform.roots marks signalOnly; ${owner}. This project declares the root ` +
58
+ `only so a change to it brings the project into the change, and its build and live reads skip it. ` +
59
+ `Deploy the root from ${marked.owner ? `"${marked.owner}"` : "the member that owns it"}, or drop signalOnly ` +
60
+ `if this project owns it.`,
61
+ });
62
+ }
63
+ }
64
+
65
+ return diagnostics;
66
+ },
67
+ };
68
+
69
+ /**
70
+ * The roots a project's `terraform.roots` marks `signalOnly`, keyed by root
71
+ * name, each with its `dir` as written and the workspace member whose
72
+ * directory holds it. `projectDir` is the directory holding the chant
73
+ * config. Tolerant: a config that does not load, or no marked root, gives an
74
+ * empty map; a workspace declaration that does not read leaves `owner` off.
75
+ */
76
+ export async function signalOnlyRootsOf(projectDir: string): Promise<Map<string, SignalOnlyRoot>> {
77
+ const out = new Map<string, SignalOnlyRoot>();
78
+ let roots: Record<string, unknown> | undefined;
79
+ try {
80
+ const { loadChantConfig } = await import("../../../config");
81
+ const loaded = await loadChantConfig(projectDir);
82
+ roots = (loaded.config as { terraform?: { roots?: Record<string, unknown> } }).terraform?.roots;
83
+ } catch {
84
+ return out;
85
+ }
86
+ const marked: Array<[string, string]> = [];
87
+ for (const [name, entry] of Object.entries(roots ?? {})) {
88
+ if (typeof entry !== "object" || entry === null) continue;
89
+ const { dir, signalOnly } = entry as { dir?: unknown; signalOnly?: unknown };
90
+ if (signalOnly !== true || typeof dir !== "string") continue;
91
+ marked.push([name, dir]);
92
+ }
93
+ if (marked.length === 0) return out;
94
+
95
+ const ownerOfDir = await workspaceOwnerLookup(projectDir);
96
+ for (const [name, dir] of marked) {
97
+ const owner = ownerOfDir?.(resolve(projectDir, dir));
98
+ out.set(name, { dir, ...(owner ? { owner } : {}) });
99
+ }
100
+ return out;
101
+ }
102
+
103
+ /** A lookup from an absolute directory to the workspace member owning it, or undefined outside a readable workspace. */
104
+ async function workspaceOwnerLookup(projectDir: string): Promise<((abs: string) => string | undefined) | undefined> {
105
+ try {
106
+ const { findWorkspaceRoot } = await import("../../../project-root");
107
+ const found = findWorkspaceRoot(projectDir);
108
+ if (!found) return undefined;
109
+ const { readDeclaration, ownerOf } = await import("../../../workspace/declaration");
110
+ const { workingTree } = await import("../../../workspace/tree");
111
+ const declaration = readDeclaration(workingTree(found.dir));
112
+ return (abs: string) => {
113
+ const rel = relative(found.dir, abs);
114
+ if (rel.startsWith("..") || isAbsolute(rel)) return undefined;
115
+ const owner = ownerOf(declaration, [], rel === "" ? "." : rel.split("\\").join("/"));
116
+ return owner && "member" in owner ? owner.member.name : undefined;
117
+ };
118
+ } catch {
119
+ return undefined;
120
+ }
121
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * COMP008 (#4490): a component's terraform-apply step deploys a root the
3
+ * project marks signalOnly. The check runs over fixture components with the
4
+ * marked roots handed in, the way `chant lint` hands them; the config read
5
+ * (`signalOnlyRootsOf`) runs over a temporary workspace.
6
+ */
7
+
8
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterAll, describe, expect, it } from "vitest";
12
+ import { runComponentChecks, type SignalOnlyRoot } from "../../component-checks";
13
+ import { comp008AppliesSignalOnlyRootRule, signalOnlyRootsOf } from "./comp008-applies-signal-only-root";
14
+
15
+ const FIXTURES = join(import.meta.dirname, "..", "__fixtures__", "comp", "comp008");
16
+ const MARKED = new Map<string, SignalOnlyRoot>([["azure", { dir: "../azure", owner: "azure" }]]);
17
+
18
+ describe("COMP008: applies-signal-only-root", () => {
19
+ it("warns on a terraform-apply step deploying a signalOnly root, naming the root and its owner", async () => {
20
+ const diagnostics = await runComponentChecks(join(FIXTURES, "fail"), [comp008AppliesSignalOnlyRootRule], { signalOnlyRoots: MARKED });
21
+ expect(diagnostics).toHaveLength(1);
22
+ const [d] = diagnostics;
23
+ expect(d.checkId).toBe("COMP008");
24
+ expect(d.severity).toBe("warning");
25
+ expect(d.component).toBe("dns");
26
+ expect(d.message).toContain('root "azure"');
27
+ expect(d.message).toContain('workspace member "azure" owns it');
28
+ });
29
+
30
+ it("says no owner was found when the workspace names none", async () => {
31
+ const roots = new Map<string, SignalOnlyRoot>([["azure", { dir: "../azure" }]]);
32
+ const [d] = await runComponentChecks(join(FIXTURES, "fail"), [comp008AppliesSignalOnlyRootRule], { signalOnlyRoots: roots });
33
+ expect(d.message).toContain("no workspace member was found to own it");
34
+ });
35
+
36
+ it("passes a component that deploys only its own roots", async () => {
37
+ const diagnostics = await runComponentChecks(join(FIXTURES, "pass"), [comp008AppliesSignalOnlyRootRule], { signalOnlyRoots: MARKED });
38
+ expect(diagnostics).toHaveLength(0);
39
+ });
40
+
41
+ it("flags nothing when no marked roots were resolved", async () => {
42
+ expect(await runComponentChecks(join(FIXTURES, "fail"), [comp008AppliesSignalOnlyRootRule])).toHaveLength(0);
43
+ });
44
+ });
45
+
46
+ describe("signalOnlyRootsOf", () => {
47
+ const ws = mkdtempSync(join(tmpdir(), "comp008-"));
48
+ afterAll(() => rmSync(ws, { recursive: true, force: true }));
49
+ // findWorkspaceRoot walks up to the git root, so the workspace root holds a .git.
50
+ mkdirSync(join(ws, ".git"));
51
+ mkdirSync(join(ws, "dns"));
52
+ mkdirSync(join(ws, "azure"));
53
+ writeFileSync(
54
+ join(ws, "chant.workspace.json"),
55
+ JSON.stringify({
56
+ name: "estate",
57
+ schema: 1,
58
+ members: [
59
+ { name: "dns", dir: "dns", kind: "chant", because: "the dns zone" },
60
+ { name: "azure", dir: "azure", kind: "chant", because: "the azure estate" },
61
+ ],
62
+ }),
63
+ );
64
+ writeFileSync(
65
+ join(ws, "dns", "chant.config.json"),
66
+ JSON.stringify({ terraform: { roots: { dns: { dir: "./estate" }, azure: { dir: "../azure", signalOnly: true } } } }),
67
+ );
68
+
69
+ it("lists only the marked roots, with the member whose directory holds each", async () => {
70
+ const roots = await signalOnlyRootsOf(join(ws, "dns"));
71
+ expect([...roots]).toEqual([["azure", { dir: "../azure", owner: "azure" }]]);
72
+ });
73
+
74
+ it("gives an empty map for a project with no config", async () => {
75
+ expect((await signalOnlyRootsOf(join(ws, "azure"))).size).toBe(0);
76
+ });
77
+ });
@@ -14,6 +14,7 @@ import { comp004GateNeedsDurableRuntimeRule } from "./comp004-gate-needs-durable
14
14
  import { comp005CapabilityKindIsNounRule } from "./comp005-capability-kind-is-noun";
15
15
  import { comp006ShellNeedsReasonRule } from "./comp006-shell-needs-reason";
16
16
  import { comp007CompositionSprawlRule } from "./comp007-composition-sprawl";
17
+ import { comp008AppliesSignalOnlyRootRule } from "./comp008-applies-signal-only-root";
17
18
 
18
19
  export { comp001PublishesNeverAppliesRule } from "./comp001-publishes-never-applies";
19
20
  export { comp002DanglingWiringRefRule } from "./comp002-dangling-wiring-ref";
@@ -22,8 +23,9 @@ export { comp004GateNeedsDurableRuntimeRule } from "./comp004-gate-needs-durable
22
23
  export { comp005CapabilityKindIsNounRule } from "./comp005-capability-kind-is-noun";
23
24
  export { comp006ShellNeedsReasonRule } from "./comp006-shell-needs-reason";
24
25
  export { comp007CompositionSprawlRule } from "./comp007-composition-sprawl";
26
+ export { comp008AppliesSignalOnlyRootRule, signalOnlyRootsOf } from "./comp008-applies-signal-only-root";
25
27
 
26
- /** All seven COMP* checks, sorted by id. */
28
+ /** All eight COMP* checks, sorted by id. */
27
29
  export function loadComponentChecks(): ComponentCheck[] {
28
30
  return [
29
31
  comp001PublishesNeverAppliesRule,
@@ -33,5 +35,6 @@ export function loadComponentChecks(): ComponentCheck[] {
33
35
  comp005CapabilityKindIsNounRule,
34
36
  comp006ShellNeedsReasonRule,
35
37
  comp007CompositionSprawlRule,
38
+ comp008AppliesSignalOnlyRootRule,
36
39
  ];
37
40
  }
@@ -0,0 +1,33 @@
1
+ // An observation's own edges (chant#4528): the terraform lexicon's read of
2
+ // `choudoufu live-graph` returns block edges with its resources, and core
3
+ // carries them through the envelope to `LiveObservation.edges`.
4
+ import { describe, it, expect } from "vitest";
5
+ import { mergeObservations, normalizeObservation, observation } from "./observation";
6
+ import { buildLiveGraphIr, type IREdge } from "./graph-ir";
7
+
8
+ const block: IREdge = { from: "web/module.app/aws_instance.web", to: "web/aws_s3_bucket.logs", kind: "ref", viaAttr: "dependencies", grain: "block" };
9
+ const instance: IREdge = { ...block, grain: "instance" };
10
+
11
+ describe("observation edges (#4528)", () => {
12
+ it("the envelope carries edges, and leaves the field off when there are none", () => {
13
+ expect(observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [block]).edges).toEqual([block]);
14
+ expect("edges" in observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [])).toBe(false);
15
+ expect(normalizeObservation(observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [block])).edges).toEqual([block]);
16
+ expect(normalizeObservation({}).edges).toBeUndefined();
17
+ });
18
+
19
+ it("a merge keeps each edge once, and two grains between one pair as two", () => {
20
+ const part = normalizeObservation(observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [block, instance]));
21
+ expect(mergeObservations([part, part]).edges).toEqual([block, instance]);
22
+ expect(mergeObservations([normalizeObservation({})]).edges).toBeUndefined();
23
+ });
24
+
25
+ it("buildLiveGraphIr keeps an observation edge whose endpoints were both observed", () => {
26
+ const resources = {
27
+ "web/module.app/aws_instance.web": { type: "Terraform::Resource", status: "bound" },
28
+ "web/aws_s3_bucket.logs": { type: "Terraform::Resource", status: "bound" },
29
+ };
30
+ const ir = buildLiveGraphIr([{ lexicon: "terraform", resources, edges: [block, { ...block, to: "web/aws_sqs_queue.gone" }] }]);
31
+ expect(ir.edges).toEqual([block]);
32
+ });
33
+ });
@@ -33,6 +33,7 @@
33
33
  */
34
34
 
35
35
  import type { ResourceMetadata } from "./lexicon";
36
+ import type { IREdge } from "./graph-ir";
36
37
 
37
38
  /**
38
39
  * Why a declared entity was not observed. Total: a lexicon that cannot observe
@@ -49,13 +50,18 @@ import type { ResourceMetadata } from "./lexicon";
49
50
  * filter (`owned: true` against a resource carrying no chant marker). The
50
51
  * result deliberately says nothing about it, which is still not absence: a
51
52
  * declared resource that exists but is foreign must not classify as `create`.
53
+ * - `not-reached` — the read that would have answered never reported this
54
+ * entity (#4451): a drift stream settled without a row for an address its
55
+ * start line expected, or a read was never run for it at all. Says nothing
56
+ * about the entity, only that no answer arrived.
52
57
  */
53
58
  export type UnobservedReason =
54
59
  | "read-failed"
55
60
  | "no-credentials"
56
61
  | "no-binding"
57
62
  | "unsupported-kind"
58
- | "filtered";
63
+ | "filtered"
64
+ | "not-reached";
59
65
 
60
66
  /**
61
67
  * Every legal {@link UnobservedReason}, for validation and conformance checks.
@@ -79,6 +85,7 @@ const UNOBSERVED_REASON_WITNESS: Record<UnobservedReason, true> = {
79
85
  "no-binding": true,
80
86
  "unsupported-kind": true,
81
87
  filtered: true,
88
+ "not-reached": true,
82
89
  };
83
90
 
84
91
  export const UNOBSERVED_REASONS: readonly UnobservedReason[] = Object.keys(
@@ -192,6 +199,16 @@ export interface ObservationResult {
192
199
  * question. Values are already scrubbed of anything that looks secret.
193
200
  */
194
201
  stackExports?: Record<string, Record<string, unknown>>;
202
+ /**
203
+ * Relationships the read observed between the resources it returned
204
+ * (chant#4528): an ownership engine's graph, such as the block edges
205
+ * `choudoufu live-graph` prints, with their `grain`. Core carries them to
206
+ * `LiveObservation.edges` beside the ones `describeDependencies` reports;
207
+ * `buildLiveGraphIr` drops any whose endpoints were not both observed.
208
+ * `from`/`to` are node ids in the space of {@link resources} keys and the
209
+ * instance node ids built from them.
210
+ */
211
+ edges?: IREdge[];
195
212
  }
196
213
 
197
214
  /**
@@ -230,6 +247,8 @@ export interface NormalizedObservation {
230
247
  notes: string[];
231
248
  /** Per-stack exports (#1279), keyed by stack name. Absent when the lexicon reported none. */
232
249
  stackExports?: Record<string, Record<string, unknown>>;
250
+ /** Observed relationships (chant#4528). Absent when the lexicon reported none. */
251
+ edges?: IREdge[];
233
252
  }
234
253
 
235
254
  /** True when `value` is the versioned {@link ObservationResult} envelope. */
@@ -254,6 +273,7 @@ export function observation(
254
273
  sources?: Record<string, string>,
255
274
  annotations?: Record<string, Record<string, unknown>>,
256
275
  rootReads?: Record<string, RootRead>,
276
+ edges?: IREdge[],
257
277
  ): ObservationResult {
258
278
  return {
259
279
  observation: "v1",
@@ -265,6 +285,7 @@ export function observation(
265
285
  ...(rootReads && Object.keys(rootReads).length > 0 ? { rootReads } : {}),
266
286
  ...(notes && notes.length > 0 ? { notes } : {}),
267
287
  ...(stackExports && Object.keys(stackExports).length > 0 ? { stackExports } : {}),
288
+ ...(edges && edges.length > 0 ? { edges } : {}),
268
289
  };
269
290
  }
270
291
 
@@ -286,6 +307,7 @@ export function normalizeObservation(value: DescribeResourcesResult | undefined)
286
307
  ...(value.annotations && Object.keys(value.annotations).length > 0 ? { annotations: value.annotations } : {}),
287
308
  ...(value.rootReads && Object.keys(value.rootReads).length > 0 ? { rootReads: value.rootReads } : {}),
288
309
  ...(value.stackExports && Object.keys(value.stackExports).length > 0 ? { stackExports: value.stackExports } : {}),
310
+ ...(Array.isArray(value.edges) && value.edges.length > 0 ? { edges: value.edges } : {}),
289
311
  };
290
312
  }
291
313
  return { resources: value, unobserved: {}, queried: {}, sources: {}, notes: [] };
@@ -339,6 +361,10 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
339
361
  // Annotations (#4372) merge like `sources`: per entity, disjoint parts.
340
362
  const annotations: Record<string, Record<string, unknown>> = {};
341
363
  const rootReads: Record<string, RootRead> = {};
364
+ // Edges (#4528) concatenate, once each: the parts read disjoint entity sets,
365
+ // and two parts reporting the same edge is one edge.
366
+ const edges: IREdge[] = [];
367
+ const edgeKeys = new Set<string>();
342
368
  for (const part of parts) {
343
369
  Object.assign(resources, part.resources);
344
370
  Object.assign(unobserved, part.unobserved);
@@ -348,6 +374,12 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
348
374
  Object.assign(rootReads, part.rootReads ?? {});
349
375
  for (const n of part.notes) notes.add(n);
350
376
  Object.assign(stackExports, part.stackExports ?? {});
377
+ for (const e of part.edges ?? []) {
378
+ const key = `${e.from}\0${e.to}\0${e.viaAttr ?? ""}\0${e.toAttr ?? ""}\0${e.grain ?? ""}`;
379
+ if (edgeKeys.has(key)) continue;
380
+ edgeKeys.add(key);
381
+ edges.push(e);
382
+ }
351
383
  }
352
384
  // Present wins: a stack that could not be read does not un-observe a resource
353
385
  // another stack returned.
@@ -361,6 +393,7 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
361
393
  ...(Object.keys(annotations).length > 0 ? { annotations } : {}),
362
394
  ...(Object.keys(rootReads).length > 0 ? { rootReads } : {}),
363
395
  ...(Object.keys(stackExports).length > 0 ? { stackExports } : {}),
396
+ ...(edges.length > 0 ? { edges } : {}),
364
397
  };
365
398
  }
366
399
 
@@ -377,6 +410,8 @@ export function unobservedReasonText(reason: UnobservedReason): string {
377
410
  return "no reader for this resource kind";
378
411
  case "filtered":
379
412
  return "withheld by the --owned filter";
413
+ case "not-reached":
414
+ return "the read never reported it";
380
415
  }
381
416
  }
382
417
 
@@ -309,7 +309,7 @@ export const lifecyclePlanChangeSetContract = activityContract(
309
309
 
310
310
  export const readChangeSetPartContract = activityContract(
311
311
  "readChangeSetPart",
312
- z.strictObject({ member: z.string(), planner: z.enum(["chant", "warden", "sql"]), file: z.string(), lexicon: z.string().optional() }),
312
+ z.strictObject({ member: z.string(), planner: z.enum(["chant", "warden", "sql", "pulumi"]), file: z.string(), lexicon: z.string().optional() }),
313
313
  z.object({ part: changeSetPart }),
314
314
  );
315
315
 
@@ -90,10 +90,11 @@ export interface ReadChangeSetPartArgs {
90
90
  member: string;
91
91
  /**
92
92
  * Which planner wrote the file: `chant` (`lifecycle plan --json`), `warden`
93
- * (its reconcile change sets), or `sql` (a part the sql lexicon's
94
- * `change-set` adapters produced, written as JSON).
93
+ * (its reconcile change sets), `sql` (a part the sql lexicon's
94
+ * `change-set` adapters produced, written as JSON), or `pulumi` (a part
95
+ * the pulumi lexicon's `pulumiPreviewPart` produced, written as JSON).
95
96
  */
96
- planner: "chant" | "warden" | "sql";
97
+ planner: "chant" | "warden" | "sql" | "pulumi";
97
98
  /** The plan file, relative to the current directory. */
98
99
  file: string;
99
100
  /** The lexicon the entries belong to, when the plan does not say. */
@@ -104,7 +105,8 @@ export interface ReadChangeSetPartArgs {
104
105
  * A plan another step or job wrote to a file, as a member's part. A warden
105
106
  * plan is the reconcile `ChangeSet` (`@intentius/chant/reconcile`), or an
106
107
  * array of them, one per cycle and org. A sql plan is already a part
107
- * (`@intentius/chant-lexicon-sql/change-set`), renamed to `member` here.
108
+ * (`@intentius/chant-lexicon-sql/change-set`), renamed to `member` here,
109
+ * and so is a pulumi one (`@intentius/chant-lexicon-pulumi/change-set`).
108
110
  */
109
111
  export async function readChangeSetPart(args: ReadChangeSetPartArgs): Promise<ChangeSetPartResult> {
110
112
  const plan: unknown = JSON.parse(readFileSync(resolve(args.file), "utf-8"));
@@ -115,7 +117,8 @@ export async function readChangeSetPart(args: ReadChangeSetPartArgs): Promise<Ch
115
117
  case "warden":
116
118
  return { part: reconcilePlanPart({ member: args.member, plan: plan as ReconcileChangeSet | ReconcileChangeSet[], ...lexicon }) };
117
119
  case "sql":
118
- return { part: readChangeSetPartFile(plan, "sql", args.member, args.lexicon) };
120
+ case "pulumi":
121
+ return { part: readChangeSetPartFile(plan, args.planner, args.member, args.lexicon) };
119
122
  default:
120
123
  throw new Error(`readChangeSetPart: no adapter for planner ${String(args.planner)}; terraform-family plans come from terraformPlan's changeSet`);
121
124
  }
@@ -32,3 +32,39 @@ export class NonRetryableActivityError extends Error {
32
32
  export function nonRetryableFailure(message: string, type: string): NonRetryableActivityError {
33
33
  return new NonRetryableActivityError(message, type);
34
34
  }
35
+
36
+ /**
37
+ * The property a failing activity sets on the error it throws to publish
38
+ * outputs it already knew when it failed (chant#4512). terraformInit knows its
39
+ * root's directory once the root resolves and before `terraform init` runs, so
40
+ * a failed init still names the root it was for.
41
+ *
42
+ * It is a plain property, not a class, so a lexicon can set it without
43
+ * importing anything newer from core: an executor that predates it reads the
44
+ * error as any other failure.
45
+ */
46
+ export const KNOWN_OUTPUTS_KEY = "knownOutputs";
47
+
48
+ /** An activity error carrying the outputs it knew before it failed (chant#4512). */
49
+ export type ErrorWithKnownOutputs = Error & { [KNOWN_OUTPUTS_KEY]: Record<string, unknown> };
50
+
51
+ /**
52
+ * Attach `outputs` to `err` as the outputs the activity knew when it failed,
53
+ * and hand the error back for the activity to rethrow. A thrown non-Error is
54
+ * wrapped in one, so its message survives.
55
+ */
56
+ export function withKnownOutputs(err: unknown, outputs: Record<string, unknown>): ErrorWithKnownOutputs {
57
+ const e = err instanceof Error ? err : new Error(String(err));
58
+ return Object.assign(e, { [KNOWN_OUTPUTS_KEY]: outputs });
59
+ }
60
+
61
+ /**
62
+ * The outputs a failed activity published on its error (chant#4512), or
63
+ * undefined when it published none. An executor resolves a failed step's
64
+ * `outcomeAttribute` paths against these, as it does against a result.
65
+ */
66
+ export function knownOutputsOf(err: unknown): Record<string, unknown> | undefined {
67
+ if (typeof err !== "object" || err === null) return undefined;
68
+ const known = (err as Record<string, unknown>)[KNOWN_OUTPUTS_KEY];
69
+ return typeof known === "object" && known !== null && !Array.isArray(known) ? (known as Record<string, unknown>) : undefined;
70
+ }
package/src/op/index.ts CHANGED
@@ -123,7 +123,8 @@ export { loadActivityContracts, mergeActivityContracts } from "./activity-contra
123
123
  export type { LexiconActivityContractContributor } from "./activity-contract-registry";
124
124
  export { ACTIVITY_PROFILES, ACTIVITY_PROFILE_NAMES, MAX_STEP_TIMEOUT } from "./activity-profiles";
125
125
  export type { ActivityProfile, ActivityProfileName } from "./activity-profiles";
126
- export { NonRetryableActivityError, nonRetryableFailure } from "./activity-failure";
126
+ export { NonRetryableActivityError, nonRetryableFailure, KNOWN_OUTPUTS_KEY, withKnownOutputs, knownOutputsOf } from "./activity-failure";
127
+ export type { ErrorWithKnownOutputs } from "./activity-failure";
127
128
  export { runOpLocally, parseDuration, OpRunFailure } from "./local-executor";
128
129
  export type { StepRecord, OpRunResult, RunOpOptions } from "./local-executor";
129
130
  export { currentOpRun, withOpRunContext } from "./run-context";
@@ -9,6 +9,7 @@ import {
9
9
  import { memoryGateLedgerPort, type GateLedgerPort } from "./gate";
10
10
  import { computePlanDigest } from "../lifecycle/plan-digest";
11
11
  import { renderHuman } from "./local-output";
12
+ import { knownOutputsOf, withKnownOutputs } from "./activity-failure";
12
13
 
13
14
  // #3521: whether the workspace seals a gate is read from identity.gates in the
14
15
  // working directory; a test says which gates are sealed instead.
@@ -264,6 +265,85 @@ describe("runOpLocally — outcomeAttribute", () => {
264
265
  });
265
266
  });
266
267
 
268
+ describe("runOpLocally — a failed step records the outputs it knew (#4512)", () => {
269
+ /** The failed run's records, off the OpRunFailure it rejects with. */
270
+ async function failedRecords(config: OpConfig, activities: Map<string, ActivityFn>, profiles = PROFILES) {
271
+ const failure = await runOpLocally(config, activities, profiles).then(
272
+ () => undefined,
273
+ (err: unknown) => err,
274
+ );
275
+ expect(failure).toBeInstanceOf(OpRunFailure);
276
+ return (failure as OpRunFailure).result.records;
277
+ }
278
+
279
+ const initConfig = op({
280
+ phases: [
281
+ { name: "Init", steps: [{ kind: "activity", fn: "init", outcomeAttribute: [{ name: "TerraformRootPath", from: "rootPath" }, { name: "Workspace", from: "workspace" }] }] },
282
+ { name: "Plan", steps: [{ kind: "activity", fn: "plan" }] },
283
+ ],
284
+ });
285
+
286
+ test("a failed step's outcomes come from the known outputs on its error", async () => {
287
+ const init: ActivityFn = async () => {
288
+ throw withKnownOutputs(new Error("terraform init: backend unreachable"), { dir: "/w/estates/aws", rootPath: "estates/aws" });
289
+ };
290
+ let planRan = false;
291
+ const plan: ActivityFn = async () => {
292
+ planRan = true;
293
+ return {};
294
+ };
295
+ const records = await failedRecords(initConfig, new Map<string, ActivityFn>([["init", init], ["plan", plan]]));
296
+ expect(records[0]).toMatchObject({ phase: "Init", fn: "init", status: "fail", error: expect.stringContaining("backend unreachable") });
297
+ // Workspace is not among the known outputs, so it is left off.
298
+ expect(records[0].outcomes).toEqual([{ name: "TerraformRootPath", value: "estates/aws" }]);
299
+ expect(records[0].outcome).toEqual({ name: "TerraformRootPath", value: "estates/aws" });
300
+ // A failed phase ends the run there: Plan does not run, and a failure (unlike
301
+ // a gate, a decision point or a stopped claim) leaves no record for it.
302
+ expect(planRan).toBe(false);
303
+ expect(records.filter((r) => r.phase === "Plan")).toEqual([]);
304
+ });
305
+
306
+ test("a failed step with no known outputs records no outcome, as before", async () => {
307
+ const init: ActivityFn = async () => {
308
+ throw new Error("chant.config.ts failed to evaluate");
309
+ };
310
+ const records = await failedRecords(initConfig, new Map<string, ActivityFn>([["init", init], ["plan", async () => ({})]]));
311
+ expect(records[0].status).toBe("fail");
312
+ expect(records[0].outcome).toBeUndefined();
313
+ expect(records[0].outcomes).toBeUndefined();
314
+ });
315
+
316
+ test("a property set by hand, without core's helper, is read the same way", async () => {
317
+ const init: ActivityFn = async () => {
318
+ throw Object.assign(new Error("init failed"), { knownOutputs: { rootPath: "roots/app", workspace: "prod" } });
319
+ };
320
+ const records = await failedRecords(initConfig, new Map<string, ActivityFn>([["init", init], ["plan", async () => ({})]]));
321
+ expect(records[0].outcomes).toEqual([{ name: "TerraformRootPath", value: "roots/app" }, { name: "Workspace", value: "prod" }]);
322
+ });
323
+
324
+ test("after retries, the last attempt's known outputs are the ones recorded", async () => {
325
+ let calls = 0;
326
+ const init: ActivityFn = async () => {
327
+ calls++;
328
+ throw withKnownOutputs(new Error(`attempt ${calls}`), { rootPath: `attempt-${calls}` });
329
+ };
330
+ const config = op({
331
+ phases: [{ name: "Init", steps: [{ kind: "activity", fn: "init", outcomeAttribute: { name: "TerraformRootPath", from: "rootPath" } }] }],
332
+ });
333
+ const records = await failedRecords(config, new Map<string, ActivityFn>([["init", init]]));
334
+ expect(calls).toBe(3);
335
+ expect(records[0].outcomes).toEqual([{ name: "TerraformRootPath", value: "attempt-3" }]);
336
+ });
337
+
338
+ test("a thrown non-Error keeps its message when known outputs are attached", async () => {
339
+ expect(withKnownOutputs("plain string", { a: 1 }).message).toBe("plain string");
340
+ expect(knownOutputsOf(withKnownOutputs("plain string", { a: 1 }))).toEqual({ a: 1 });
341
+ expect(knownOutputsOf(new Error("none"))).toBeUndefined();
342
+ expect(knownOutputsOf(Object.assign(new Error("list"), { knownOutputs: ["x"] }))).toBeUndefined();
343
+ expect(knownOutputsOf(undefined)).toBeUndefined();
344
+ });
345
+ });
346
+
267
347
  describe("runOpLocally — onFailure", () => {
268
348
  test("runs compensation phases in reverse and rejects with status=fail", async () => {
269
349
  const order: string[] = [];