@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,886 @@
1
+ /**
2
+ * One drift record across engines (#4450, #4451).
3
+ *
4
+ * Drift had four vocabularies for one question: the converge observers'
5
+ * `in-sync | drifted | unknown` (`./lifecycle/symptoms.ts`), component
6
+ * status's `reconciled | unrecorded | stale | drifted | unknown`
7
+ * (`./lifecycle/status.ts`), the graph overlay's tones, and the live diff's
8
+ * buckets (`./lifecycle/live-diff.ts`). A renderer colouring a terraform, a
9
+ * choudoufu and a chant member side by side had to know all four, and behold
10
+ * read choudoufu's documents itself. This module is the one shape they all
11
+ * become: a {@link DriftRecord} per resource instance, and a
12
+ * {@link DriftDocument} (`drift: "v1"`) that holds them. Its schema is
13
+ * `./workspace/drift.schema.json`, `$id` {@link DRIFT_SCHEMA_ID}.
14
+ *
15
+ * A record says which read answered (`read`) and what it found (`status`).
16
+ * The two travel together because they are different claims: a `state` read
17
+ * says what the last apply recorded and can never say the account matches,
18
+ * so {@link driftRecord} refuses a state read whose status is anything but
19
+ * `recorded`, `pending` or `not-observed`. Every record goes through that
20
+ * constructor, so the rule holds for every adapter, and for a document read
21
+ * back with {@link parseDriftDocument}.
22
+ *
23
+ * `not-observed` is never absence. It always carries a reason from the
24
+ * observation contract's closed list (`./observation.ts`), and
25
+ * `not-reached` is the reason a stream gives an address it expected and
26
+ * never heard about ({@link driftSettle}).
27
+ *
28
+ * ## Redaction
29
+ *
30
+ * An attribute the planner marked sensitive keeps its path and loses both
31
+ * values (`sensitive: true`), as the change set spells it
32
+ * (`./change-set.ts`). A second pass redacts any path with a segment
33
+ * {@link isSensitiveKey} names, whatever the planner said, so an engine
34
+ * that marks nothing still never ships a `password` value. Values are
35
+ * optional: a card needs only the names.
36
+ *
37
+ * ## Not digest-bound
38
+ *
39
+ * A drift read authorizes nothing and goes stale on the next read, so a
40
+ * record is ephemeral. {@link driftDocumentDigest} exists for one case, a
41
+ * WatchOp writing its document into its own run ledger as evidence, and
42
+ * leaves out `observedAt` and `evidence`, which change on every read.
43
+ *
44
+ * ## Adapters
45
+ *
46
+ * The chant ones are here: `lifecycle diff --live` ({@link liveDiffDriftRecords}),
47
+ * deep observe ({@link deepDiffDriftRecords}, {@link mergeDeepDrift}) and
48
+ * component status ({@link componentStatusDriftRecords}). The terraform and
49
+ * choudoufu ones live in the terraform lexicon's `drift-record` subpath,
50
+ * beside its readers, and the Pulumi one (`refresh --preview-only`, #4453)
51
+ * in the pulumi lexicon's.
52
+ *
53
+ * ## The graph and the rollup
54
+ *
55
+ * A record's `node` and `instanceOf` come from the change set's own rule
56
+ * (`graphNodeFor`, #4454), and a block's status from core's one rollup
57
+ * (`rollupInstances`) over {@link DRIFT_INSTANCE_ROLLUP}. Neither is
58
+ * written a second time here.
59
+ *
60
+ * ## The import graph
61
+ *
62
+ * This module ships in the change-set bundle (`change-set-bundle.test.ts`,
63
+ * #3421): at run time it imports the plan digest, the observation
64
+ * contract's reason list, the change-set graph's node rule, the instance
65
+ * rollup and the sensitive-key list, and types only otherwise.
66
+ */
67
+ import { graphNodeFor } from "./change-set-graph.js";
68
+ import { rollupInstances } from "./instance-nodes.js";
69
+ import { computePlanDigest } from "./lifecycle/plan-digest.js";
70
+ import { isUnobservedReason } from "./observation.js";
71
+ import { isSensitiveKey } from "./sensitive-key.js";
72
+ /** The schema a drift document names in `$schema`. */
73
+ export const DRIFT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/drift/v1/drift.schema.json";
74
+ /** The document's version. */
75
+ export const DRIFT_VERSION = "v1";
76
+ /** The planner whose node rule places an engine's records: plunge's URNs go by `pulumi`'s (#4453). */
77
+ export function driftPlanner(engine) {
78
+ return engine === "plunge" ? "pulumi" : engine;
79
+ }
80
+ const DRIFT_ENGINE_WITNESS = {
81
+ terraform: true,
82
+ tofu: true,
83
+ choudoufu: true,
84
+ chant: true,
85
+ warden: true,
86
+ sql: true,
87
+ pulumi: true,
88
+ plunge: true,
89
+ };
90
+ /** Every engine, in the order the schema lists them. */
91
+ export const DRIFT_ENGINES = Object.keys(DRIFT_ENGINE_WITNESS);
92
+ const DRIFT_READ_WITNESS = { state: true, plan: true, refresh: true, "live-read": true, ownership: true };
93
+ /** Every read, in the order the schema lists them. */
94
+ export const DRIFT_READS = Object.keys(DRIFT_READ_WITNESS);
95
+ const DRIFT_STATUS_WITNESS = {
96
+ "in-sync": true,
97
+ drifted: true,
98
+ pending: true,
99
+ missing: true,
100
+ unclaimed: true,
101
+ undeclared: true,
102
+ runtime: true,
103
+ recorded: true,
104
+ "not-observed": true,
105
+ };
106
+ /** Every status, in the order the schema lists them. */
107
+ export const DRIFT_STATUSES = Object.keys(DRIFT_STATUS_WITNESS);
108
+ /** The only statuses a `state` read may give. It never says in-sync or drifted. */
109
+ export const STATE_READ_STATUSES = ["recorded", "pending", "not-observed"];
110
+ const OWNERSHIP_VERDICT_WITNESS = { owned: true, foreign: true, adoptable: true, unknown: true };
111
+ /** Every ownership verdict. */
112
+ export const DRIFT_OWNERSHIP_VERDICTS = Object.keys(OWNERSHIP_VERDICT_WITNESS);
113
+ const OWNERSHIP_BY_WITNESS = { marker: true, derived: true, record: true, cache: true, state: true, label: true };
114
+ /** Every ownership basis. */
115
+ export const DRIFT_OWNERSHIP_BY = Object.keys(OWNERSHIP_BY_WITNESS);
116
+ /** A record {@link driftRecord} refused, naming the record and the rule. */
117
+ export class DriftRecordError extends Error {
118
+ constructor(rule, message) {
119
+ super(message);
120
+ this.name = "DriftRecordError";
121
+ this.rule = rule;
122
+ }
123
+ }
124
+ const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
125
+ const isRecordObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
126
+ const nonEmpty = (v) => typeof v === "string" && v.length > 0;
127
+ /** RFC 3339 date-time, as JSON Schema's `date-time` format reads it. */
128
+ const RFC3339 = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
129
+ /**
130
+ * The key segments of an attribute path, indexes left out:
131
+ * `spec.env[0]["db_password"]` is `spec`, `env`, `db_password`. A keyed
132
+ * element (`Tags[#api-token-owner].Value`, the deep read's address for an
133
+ * array it can key) is a key too: it names the element the way a map key
134
+ * does, so an environment list keyed by `DB_PASSWORD` redacts like a map's.
135
+ */
136
+ export function attributePathKeys(path) {
137
+ const keys = [];
138
+ let current = "";
139
+ for (let i = 0; i < path.length; i++) {
140
+ const c = path[i];
141
+ if (c === ".") {
142
+ if (current)
143
+ keys.push(current);
144
+ current = "";
145
+ }
146
+ else if (c === "[") {
147
+ if (current)
148
+ keys.push(current);
149
+ current = "";
150
+ if (path[i + 1] === '"') {
151
+ let key = "";
152
+ let j = i + 2;
153
+ for (; j < path.length && path[j] !== '"'; j++) {
154
+ if (path[j] === "\\" && j + 1 < path.length)
155
+ j++;
156
+ key += path[j];
157
+ }
158
+ keys.push(key);
159
+ i = path.indexOf("]", j);
160
+ if (i < 0)
161
+ break;
162
+ }
163
+ else if (path[i + 1] === "#") {
164
+ const close = path.indexOf("]", i);
165
+ if (close < 0)
166
+ break;
167
+ keys.push(path.slice(i + 2, close));
168
+ i = close;
169
+ }
170
+ else {
171
+ const close = path.indexOf("]", i);
172
+ if (close < 0)
173
+ break;
174
+ i = close; // a numeric index: not a key
175
+ }
176
+ }
177
+ else
178
+ current += c;
179
+ }
180
+ if (current)
181
+ keys.push(current);
182
+ return keys;
183
+ }
184
+ /**
185
+ * One attribute, redacted: the planner's mark first, then any key segment
186
+ * {@link isSensitiveKey} names. A redacted attribute is `{ path, sensitive:
187
+ * true }` and nothing else.
188
+ */
189
+ export function redactDriftAttribute(attribute) {
190
+ if (attribute.sensitive === true || attributePathKeys(attribute.path).some(isSensitiveKey)) {
191
+ return { path: attribute.path, sensitive: true };
192
+ }
193
+ return {
194
+ path: attribute.path,
195
+ ...(attribute.before !== undefined ? { before: attribute.before } : {}),
196
+ ...(attribute.after !== undefined ? { after: attribute.after } : {}),
197
+ };
198
+ }
199
+ function checkUnobserved(value, where, field) {
200
+ if (!isRecordObject(value))
201
+ throw new DriftRecordError("unobserved-reason", `${where}: ${field} needs a reason`);
202
+ if (!isUnobservedReason(value.reason)) {
203
+ throw new DriftRecordError("unobserved-reason", `${where}: ${field}.reason ${JSON.stringify(value.reason)} is not an unobserved reason`);
204
+ }
205
+ return {
206
+ reason: value.reason,
207
+ ...(nonEmpty(value.detail) ? { detail: value.detail } : {}),
208
+ ...(nonEmpty(value.queried) ? { queried: value.queried } : {}),
209
+ };
210
+ }
211
+ function checkOwnership(value, where) {
212
+ if (!isRecordObject(value) || !DRIFT_OWNERSHIP_VERDICTS.includes(value.verdict)) {
213
+ throw new DriftRecordError("shape", `${where}: ownership.verdict must be one of ${DRIFT_OWNERSHIP_VERDICTS.join(", ")}`);
214
+ }
215
+ if (value.by !== undefined && !DRIFT_OWNERSHIP_BY.includes(value.by)) {
216
+ throw new DriftRecordError("shape", `${where}: ownership.by ${JSON.stringify(value.by)} is not one of ${DRIFT_OWNERSHIP_BY.join(", ")}`);
217
+ }
218
+ if (value.markerOnObject !== undefined && typeof value.markerOnObject !== "boolean") {
219
+ throw new DriftRecordError("shape", `${where}: ownership.markerOnObject must be a boolean`);
220
+ }
221
+ return {
222
+ verdict: value.verdict,
223
+ ...(value.by !== undefined ? { by: value.by } : {}),
224
+ ...(typeof value.markerOnObject === "boolean" ? { markerOnObject: value.markerOnObject } : {}),
225
+ ...(nonEmpty(value.ownedBy) ? { ownedBy: value.ownedBy } : {}),
226
+ ...(nonEmpty(value.adopt) ? { adopt: value.adopt } : {}),
227
+ };
228
+ }
229
+ /**
230
+ * Build one record, or refuse it. Every adapter and every reader goes
231
+ * through here, so these hold for every record that exists:
232
+ *
233
+ * - a `state` read's status is `recorded`, `pending` or `not-observed`;
234
+ * - `not-observed` carries `unobserved` with a legal reason, and no other
235
+ * status carries it;
236
+ * - `attributes` appear only on `drifted`, sorted by path, one per path,
237
+ * and redacted ({@link redactDriftAttribute});
238
+ * - `attributesUnobserved` appears only on `in-sync` or `drifted`.
239
+ *
240
+ * A refusal is a {@link DriftRecordError} naming the member, the address,
241
+ * the read and the status.
242
+ */
243
+ export function driftRecord(input) {
244
+ const raw = input;
245
+ if (!isRecordObject(raw))
246
+ throw new DriftRecordError("shape", "a drift record must be an object");
247
+ const where = `drift record ${JSON.stringify(raw.address)} in member ${JSON.stringify(raw.member)} (${String(raw.read)} read, ${String(raw.status)})`;
248
+ if (!nonEmpty(raw.member))
249
+ throw new DriftRecordError("shape", `${where}: member must be a non-empty string`);
250
+ if (!nonEmpty(raw.address))
251
+ throw new DriftRecordError("shape", `${where}: address must be a non-empty string`);
252
+ if (!DRIFT_ENGINES.includes(raw.engine)) {
253
+ throw new DriftRecordError("shape", `${where}: engine ${JSON.stringify(raw.engine)} is not one of ${DRIFT_ENGINES.join(", ")}`);
254
+ }
255
+ if (!DRIFT_READS.includes(raw.read)) {
256
+ throw new DriftRecordError("shape", `${where}: read ${JSON.stringify(raw.read)} is not one of ${DRIFT_READS.join(", ")}`);
257
+ }
258
+ if (!DRIFT_STATUSES.includes(raw.status)) {
259
+ throw new DriftRecordError("shape", `${where}: status ${JSON.stringify(raw.status)} is not one of ${DRIFT_STATUSES.join(", ")}`);
260
+ }
261
+ if (raw.node !== null && !nonEmpty(raw.node))
262
+ throw new DriftRecordError("shape", `${where}: node must be a graph id or null`);
263
+ if (typeof raw.observedAt !== "string" || !RFC3339.test(raw.observedAt)) {
264
+ throw new DriftRecordError("shape", `${where}: observedAt ${JSON.stringify(raw.observedAt)} is not an RFC 3339 date-time`);
265
+ }
266
+ if (raw.instanceOf !== undefined && (!nonEmpty(raw.instanceOf) || raw.node === null)) {
267
+ throw new DriftRecordError("shape", `${where}: instanceOf must be a block node id, beside a node`);
268
+ }
269
+ if (raw.fellBackFrom !== undefined && !nonEmpty(raw.fellBackFrom))
270
+ throw new DriftRecordError("shape", `${where}: fellBackFrom must be a non-empty string`);
271
+ if (raw.resourceType !== undefined && !nonEmpty(raw.resourceType))
272
+ throw new DriftRecordError("shape", `${where}: resourceType must be a non-empty string`);
273
+ if (!isRecordObject(raw.evidence) || !nonEmpty(raw.evidence.tool))
274
+ throw new DriftRecordError("shape", `${where}: evidence.tool must be a non-empty string`);
275
+ const read = raw.read;
276
+ const status = raw.status;
277
+ if (read === "state" && !STATE_READ_STATUSES.includes(status)) {
278
+ throw new DriftRecordError("state-read-status", `${where}: a state read says what the last apply recorded, so it can only be ${STATE_READ_STATUSES.join(", ")}, never ${status}`);
279
+ }
280
+ let unobserved;
281
+ if (status === "not-observed") {
282
+ if (raw.unobserved === undefined)
283
+ throw new DriftRecordError("unobserved-reason", `${where}: not-observed needs unobserved.reason`);
284
+ unobserved = checkUnobserved(raw.unobserved, where, "unobserved");
285
+ }
286
+ else if (raw.unobserved !== undefined) {
287
+ throw new DriftRecordError("unobserved-on-observed", `${where}: only not-observed carries unobserved`);
288
+ }
289
+ let attributes;
290
+ if (raw.attributes !== undefined) {
291
+ if (status !== "drifted")
292
+ throw new DriftRecordError("attributes-status", `${where}: only drifted carries attributes`);
293
+ if (!Array.isArray(raw.attributes))
294
+ throw new DriftRecordError("shape", `${where}: attributes must be an array`);
295
+ const byPath = new Map();
296
+ for (const [i, a] of raw.attributes.entries()) {
297
+ if (!isRecordObject(a) || !nonEmpty(a.path))
298
+ throw new DriftRecordError("shape", `${where}: attributes[${i}] needs a path`);
299
+ if (a.sensitive !== undefined && a.sensitive !== true)
300
+ throw new DriftRecordError("shape", `${where}: attributes[${i}].sensitive must be true when set`);
301
+ const redacted = redactDriftAttribute(a);
302
+ const earlier = byPath.get(redacted.path);
303
+ // Two rows at one path (a block's instances share none, so this is one
304
+ // instance reported twice): a redaction on either redacts the path.
305
+ byPath.set(redacted.path, earlier?.sensitive === true ? earlier : redacted);
306
+ }
307
+ attributes = [...byPath.values()].sort((a, b) => byString(a.path, b.path));
308
+ }
309
+ let attributesUnobserved;
310
+ if (raw.attributesUnobserved !== undefined) {
311
+ if (status !== "in-sync" && status !== "drifted") {
312
+ throw new DriftRecordError("attributes-unobserved-status", `${where}: only in-sync or drifted carries attributesUnobserved`);
313
+ }
314
+ attributesUnobserved = checkUnobserved(raw.attributesUnobserved, where, "attributesUnobserved");
315
+ }
316
+ const ev = raw.evidence;
317
+ return {
318
+ member: raw.member,
319
+ engine: raw.engine,
320
+ address: raw.address,
321
+ ...(raw.resourceType !== undefined ? { resourceType: raw.resourceType } : {}),
322
+ node: raw.node,
323
+ ...(raw.instanceOf !== undefined ? { instanceOf: raw.instanceOf } : {}),
324
+ observedAt: raw.observedAt,
325
+ read,
326
+ ...(raw.fellBackFrom !== undefined ? { fellBackFrom: raw.fellBackFrom } : {}),
327
+ status,
328
+ ...(unobserved ? { unobserved } : {}),
329
+ ...(attributes ? { attributes } : {}),
330
+ ...(attributesUnobserved ? { attributesUnobserved } : {}),
331
+ ...(raw.ownership !== undefined ? { ownership: checkOwnership(raw.ownership, where) } : {}),
332
+ evidence: {
333
+ tool: ev.tool,
334
+ ...(nonEmpty(ev.version) ? { version: ev.version } : {}),
335
+ ...(nonEmpty(ev.command) ? { command: ev.command } : {}),
336
+ ...(nonEmpty(ev.engineCode) ? { engineCode: ev.engineCode } : {}),
337
+ },
338
+ };
339
+ }
340
+ /** Read one record from parsed JSON. The same checks as {@link driftRecord}. */
341
+ export function parseDriftRecord(value) {
342
+ if (!isRecordObject(value))
343
+ throw new DriftRecordError("shape", "a drift record must be an object");
344
+ return driftRecord(value);
345
+ }
346
+ function recordKey(r) {
347
+ return `${r.member}\u0000${r.address}\u0000${r.read}`;
348
+ }
349
+ /** Counts per status, zero included. */
350
+ export function countDriftStatuses(records) {
351
+ const counts = Object.fromEntries(DRIFT_STATUSES.map((s) => [s, 0]));
352
+ for (const r of records)
353
+ counts[r.status]++;
354
+ return counts;
355
+ }
356
+ /**
357
+ * Join records into one document. A later record for the same `(member,
358
+ * address, read)` replaces the earlier one, as a stream's later line does.
359
+ * Records are sorted by member, address and read. Every record is checked
360
+ * with {@link driftRecord}.
361
+ */
362
+ export function driftDocument(records, options = {}) {
363
+ const latest = new Map();
364
+ for (const r of records) {
365
+ const checked = driftRecord(r);
366
+ latest.set(recordKey(checked), checked);
367
+ }
368
+ const sorted = [...latest.entries()].sort(([a], [b]) => byString(a, b)).map(([, r]) => r);
369
+ return {
370
+ $schema: DRIFT_SCHEMA_ID,
371
+ drift: DRIFT_VERSION,
372
+ ...(options.chant !== undefined ? { chant: options.chant } : {}),
373
+ records: sorted,
374
+ counts: countDriftStatuses(sorted),
375
+ };
376
+ }
377
+ /** Read a document from parsed JSON, checking every record. Counts are recomputed. */
378
+ export function parseDriftDocument(value) {
379
+ if (!isRecordObject(value))
380
+ throw new DriftRecordError("shape", "a drift document must be an object");
381
+ if (value.drift !== DRIFT_VERSION)
382
+ throw new DriftRecordError("shape", `a drift document says drift ${JSON.stringify(value.drift)}, not "${DRIFT_VERSION}"`);
383
+ if (!Array.isArray(value.records))
384
+ throw new DriftRecordError("shape", "a drift document needs a records array");
385
+ return driftDocument(value.records.map(parseDriftRecord), nonEmpty(value.chant) ? { chant: value.chant } : {});
386
+ }
387
+ /**
388
+ * The document's digest, for a WatchOp that keeps its document as evidence
389
+ * in its own run ledger: `computePlanDigest("drift-document", …)` over the
390
+ * records sorted as {@link driftDocument} sorts them, without `observedAt`
391
+ * and `evidence`, which change on every read. No gate binds it.
392
+ */
393
+ export function driftDocumentDigest(doc) {
394
+ const records = [...doc.records]
395
+ .sort((a, b) => byString(recordKey(a), recordKey(b)))
396
+ .map(({ observedAt: _at, evidence: _evidence, ...r }) => r);
397
+ return computePlanDigest("drift-document", records);
398
+ }
399
+ // ── the block rollup ───────────────────────────────────────────────────────────
400
+ /**
401
+ * The precedence a block's instance records roll up in, most significant
402
+ * first, for core's one rollup (`rollupInstances` in `./instance-nodes.ts`,
403
+ * #4454). Partial knowledge is not knowledge, so one not-observed instance
404
+ * makes the block not-observed, as `TERRAFORM_INSTANCE_ROLLUP` puts
405
+ * `unobserved` first. A difference outranks an absence, an absence
406
+ * outranks agreement, and `runtime` is last because a controller's children
407
+ * never decide a block.
408
+ */
409
+ export const DRIFT_INSTANCE_ROLLUP = [
410
+ "not-observed",
411
+ "drifted",
412
+ "missing",
413
+ "unclaimed",
414
+ "undeclared",
415
+ "pending",
416
+ "in-sync",
417
+ "recorded",
418
+ "runtime",
419
+ ];
420
+ /**
421
+ * Records grouped by block node (`instanceOf`, else `node`) and rolled up
422
+ * with `rollupInstances` over {@link DRIFT_INSTANCE_ROLLUP}. A record with a
423
+ * null `node` is on no block and is left out. Sorted by node.
424
+ */
425
+ export function rollupDriftByBlock(records) {
426
+ const groups = new Map();
427
+ for (const r of records) {
428
+ const block = r.instanceOf ?? r.node;
429
+ if (block === null)
430
+ continue;
431
+ const list = groups.get(block) ?? [];
432
+ list.push(r);
433
+ groups.set(block, list);
434
+ }
435
+ return [...groups.entries()]
436
+ .sort(([a], [b]) => byString(a, b))
437
+ .map(([node, list]) => ({
438
+ node,
439
+ status: rollupInstances(list.map((r) => ({ kind: r.status })), DRIFT_INSTANCE_ROLLUP).kind,
440
+ reads: [...new Set(list.map((r) => r.read))].sort(byString),
441
+ records: list,
442
+ }));
443
+ }
444
+ /**
445
+ * The document with each record's `node` and `instanceOf` set from `graph`
446
+ * by `graphNodeFor` (`./change-set-graph.ts`), the rule a change-set entry
447
+ * uses, so a drift row and a change-set entry for one instance name the
448
+ * same node: an instance of a `count` or `for_each` block is on
449
+ * `<blockNode>[0]` with `instanceOf` the block node, and an instance under
450
+ * an expanded module call is on `<root>/module.app[0]/<block>[1]` with the
451
+ * same `instanceOf` (#4486). A Pulumi URN is on
452
+ * `<member>/<key>` by the URN rule (`./pulumi-urn.ts`, #4456), through the
453
+ * `pulumi` planner's case, and so is a plunge URN ({@link driftPlanner}).
454
+ * A null `graph` gives every record `node: null`.
455
+ */
456
+ export function mapDriftToGraph(doc, graph) {
457
+ return {
458
+ ...doc,
459
+ records: doc.records.map(({ instanceOf: _i, ...r }) => {
460
+ if (graph === null)
461
+ return { ...r, node: null };
462
+ const placed = graphNodeFor({ member: r.member, planner: driftPlanner(r.engine), address: r.address }, graph);
463
+ return { ...r, node: placed.node, ...(placed.instanceOf !== undefined ? { instanceOf: placed.instanceOf } : {}) };
464
+ }),
465
+ };
466
+ }
467
+ /** A start line's expected entries, bare addresses made objects. */
468
+ export function driftExpected(start) {
469
+ return start.expected.map((e) => (typeof e === "string" ? { address: e } : e));
470
+ }
471
+ /** A record as its stream line. */
472
+ export function driftRecordLine(record) {
473
+ return { type: "drift-record", ...record };
474
+ }
475
+ /** One line of the stream, newline-terminated. */
476
+ export function encodeDriftLine(line) {
477
+ return `${JSON.stringify(line)}\n`;
478
+ }
479
+ /** The whole stream for one or more reads, in order. */
480
+ export function encodeDriftLines(lines) {
481
+ let out = "";
482
+ for (const line of lines)
483
+ out += encodeDriftLine(line);
484
+ return out;
485
+ }
486
+ function checkStreamHead(v, type) {
487
+ if (v.drift !== DRIFT_VERSION)
488
+ throw new DriftRecordError("shape", `a ${type} line says drift ${JSON.stringify(v.drift)}, not "${DRIFT_VERSION}"`);
489
+ if (!nonEmpty(v.member))
490
+ throw new DriftRecordError("shape", `a ${type} line needs a member`);
491
+ if (!DRIFT_ENGINES.includes(v.engine))
492
+ throw new DriftRecordError("shape", `a ${type} line for ${v.member} names engine ${JSON.stringify(v.engine)}`);
493
+ if (!DRIFT_READS.includes(v.read))
494
+ throw new DriftRecordError("shape", `a ${type} line for ${v.member} names read ${JSON.stringify(v.read)}`);
495
+ if (v.at !== undefined && (typeof v.at !== "string" || !RFC3339.test(v.at)))
496
+ throw new DriftRecordError("shape", `a ${type} line for ${v.member} has an at that is not RFC 3339`);
497
+ }
498
+ /** Read one line, checking it. A record line goes through {@link driftRecord}. Unknown fields are dropped. */
499
+ export function decodeDriftLine(text) {
500
+ let v;
501
+ try {
502
+ v = JSON.parse(text);
503
+ }
504
+ catch (err) {
505
+ throw new DriftRecordError("shape", `a drift line is not JSON: ${err.message}`);
506
+ }
507
+ if (!isRecordObject(v))
508
+ throw new DriftRecordError("shape", "a drift line must be an object");
509
+ switch (v.type) {
510
+ case "drift-start": {
511
+ checkStreamHead(v, "drift-start");
512
+ if (!Array.isArray(v.expected))
513
+ throw new DriftRecordError("shape", `the drift-start line for ${v.member} needs an expected array`);
514
+ const expected = v.expected.map((e, i) => {
515
+ if (nonEmpty(e))
516
+ return e;
517
+ if (!isRecordObject(e) || !nonEmpty(e.address))
518
+ throw new DriftRecordError("shape", `the drift-start line for ${v.member}: expected[${i}] needs an address`);
519
+ return {
520
+ address: e.address,
521
+ ...(e.node === null || nonEmpty(e.node) ? { node: e.node } : {}),
522
+ ...(nonEmpty(e.instanceOf) ? { instanceOf: e.instanceOf } : {}),
523
+ ...(nonEmpty(e.resourceType) ? { resourceType: e.resourceType } : {}),
524
+ };
525
+ });
526
+ if (v.evidence !== undefined && (!isRecordObject(v.evidence) || !nonEmpty(v.evidence.tool))) {
527
+ throw new DriftRecordError("shape", `the drift-start line for ${v.member} has evidence with no tool`);
528
+ }
529
+ return {
530
+ type: "drift-start",
531
+ drift: DRIFT_VERSION,
532
+ member: v.member,
533
+ engine: v.engine,
534
+ read: v.read,
535
+ ...(nonEmpty(v.resource) ? { resource: v.resource } : {}),
536
+ ...(nonEmpty(v.fellBackFrom) ? { fellBackFrom: v.fellBackFrom } : {}),
537
+ ...(v.at !== undefined ? { at: v.at } : {}),
538
+ expected,
539
+ ...(v.evidence !== undefined ? { evidence: v.evidence } : {}),
540
+ };
541
+ }
542
+ case "drift-record": {
543
+ const { type: _type, ...record } = v;
544
+ return driftRecordLine(parseDriftRecord(record));
545
+ }
546
+ case "drift-settle": {
547
+ checkStreamHead(v, "drift-settle");
548
+ if (!isRecordObject(v.counts))
549
+ throw new DriftRecordError("shape", `the drift-settle line for ${v.member} needs counts`);
550
+ const counts = {};
551
+ for (const [s, n] of Object.entries(v.counts)) {
552
+ if (!DRIFT_STATUSES.includes(s))
553
+ throw new DriftRecordError("shape", `the drift-settle line for ${v.member} counts status ${JSON.stringify(s)}`);
554
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 0)
555
+ throw new DriftRecordError("shape", `the drift-settle line for ${v.member}: counts.${s} must be a count`);
556
+ counts[s] = n;
557
+ }
558
+ if (v.notReached !== undefined && (!Array.isArray(v.notReached) || !v.notReached.every(nonEmpty))) {
559
+ throw new DriftRecordError("shape", `the drift-settle line for ${v.member} has a notReached that is not a list of addresses`);
560
+ }
561
+ if (v.reads !== undefined && !isRecordObject(v.reads))
562
+ throw new DriftRecordError("shape", `the drift-settle line for ${v.member} has reads that is not an object`);
563
+ return {
564
+ type: "drift-settle",
565
+ drift: DRIFT_VERSION,
566
+ member: v.member,
567
+ engine: v.engine,
568
+ read: v.read,
569
+ counts,
570
+ ...(v.reads !== undefined ? { reads: v.reads } : {}),
571
+ ...(v.at !== undefined ? { at: v.at } : {}),
572
+ ...(v.notReached !== undefined ? { notReached: [...v.notReached].sort(byString) } : {}),
573
+ };
574
+ }
575
+ default:
576
+ throw new DriftRecordError("shape", `a drift line has type ${JSON.stringify(v.type)}, not drift-start, drift-record or drift-settle`);
577
+ }
578
+ }
579
+ /** Every non-blank line of an NDJSON stream, decoded. */
580
+ export function decodeDriftLines(text) {
581
+ return text
582
+ .split("\n")
583
+ .map((l) => l.trim())
584
+ .filter((l) => l.length > 0)
585
+ .map(decodeDriftLine);
586
+ }
587
+ /** Open a read: the start line for a member's expected addresses, each once. */
588
+ export function driftStart(input) {
589
+ const seen = new Set();
590
+ const expected = input.expected.filter((e) => {
591
+ const address = typeof e === "string" ? e : e.address;
592
+ return !seen.has(address) && seen.add(address);
593
+ });
594
+ return { type: "drift-start", drift: DRIFT_VERSION, ...input, expected };
595
+ }
596
+ function notReachedRecord(head, expected, at) {
597
+ return driftRecord({
598
+ member: head.member,
599
+ engine: head.engine,
600
+ address: expected.address,
601
+ ...(expected.resourceType !== undefined ? { resourceType: expected.resourceType } : {}),
602
+ node: expected.node ?? null,
603
+ ...(expected.instanceOf !== undefined && expected.node ? { instanceOf: expected.instanceOf } : {}),
604
+ observedAt: at,
605
+ read: head.read,
606
+ ...(head.fellBackFrom !== undefined ? { fellBackFrom: head.fellBackFrom } : {}),
607
+ status: "not-observed",
608
+ unobserved: { reason: "not-reached", detail: `the ${head.read} read settled without reporting ${expected.address}` },
609
+ evidence: head.evidence ?? { tool: head.engine },
610
+ });
611
+ }
612
+ /**
613
+ * Close a read. `records` are the records sent since `start`, in order; a
614
+ * later one for an address and read replaces an earlier one, and records
615
+ * for another member are ignored. A record for an expected address answers
616
+ * it whatever its read, since one read can answer an address with another
617
+ * (a plan stream's unclaimed row is an `ownership` record). Every expected
618
+ * address with no record becomes `not-observed / not-reached` at `at`.
619
+ * Returns the settle line, which lists them in `notReached`, and the
620
+ * member's final records.
621
+ */
622
+ export function driftSettle(start, records, at) {
623
+ const mine = new Map();
624
+ const answered = new Set();
625
+ for (const r of records) {
626
+ if (r.member !== start.member)
627
+ continue;
628
+ const checked = driftRecord(r);
629
+ mine.set(recordKey(checked), checked);
630
+ answered.add(checked.address);
631
+ }
632
+ const notReached = [];
633
+ for (const e of driftExpected(start)) {
634
+ if (answered.has(e.address))
635
+ continue;
636
+ notReached.push(e.address);
637
+ const r = notReachedRecord(start, e, at);
638
+ mine.set(recordKey(r), r);
639
+ }
640
+ const final = [...mine.entries()].sort(([a], [b]) => byString(a, b)).map(([, r]) => r);
641
+ return {
642
+ line: {
643
+ type: "drift-settle",
644
+ drift: DRIFT_VERSION,
645
+ member: start.member,
646
+ engine: start.engine,
647
+ read: start.read,
648
+ counts: countDriftStatuses(final),
649
+ at,
650
+ notReached: notReached.sort(byString),
651
+ },
652
+ records: final,
653
+ };
654
+ }
655
+ /**
656
+ * Fold a stream into a document. Record lines replace earlier ones for the
657
+ * same `(member, address, read)`. At a settle line, every address its start
658
+ * expected that no record answered (by member and address, whatever the
659
+ * read) becomes `not-observed / not-reached`, with the node and type the
660
+ * start gave, at the settle's `at`, else the start's, else the latest
661
+ * record's `observedAt` in the stream; `notReached` addresses the start did
662
+ * not list are added the same way. A start with no settle yet leaves its
663
+ * unanswered addresses in `reading`, which a renderer shows as "reading"
664
+ * and nothing else.
665
+ */
666
+ export function foldDriftLines(lines, options = {}) {
667
+ const records = [];
668
+ const answered = new Set();
669
+ const open = new Map();
670
+ let latest;
671
+ const memberKey = (member, x) => `${member}\u0000${x}`;
672
+ for (const line of lines) {
673
+ if (line.type === "drift-start") {
674
+ open.set(memberKey(line.member, line.read), line);
675
+ }
676
+ else if (line.type === "drift-record") {
677
+ const { type: _type, ...record } = line;
678
+ records.push(record);
679
+ answered.add(memberKey(record.member, record.address));
680
+ if (latest === undefined || record.observedAt > latest)
681
+ latest = record.observedAt;
682
+ }
683
+ else {
684
+ const start = open.get(memberKey(line.member, line.read));
685
+ open.delete(memberKey(line.member, line.read));
686
+ const expected = new Map((start ? driftExpected(start) : []).map((e) => [e.address, e]));
687
+ for (const address of line.notReached ?? [])
688
+ if (!expected.has(address))
689
+ expected.set(address, { address });
690
+ const at = line.at ?? start?.at ?? latest ?? new Date(0).toISOString();
691
+ const head = start ?? { member: line.member, engine: line.engine, read: line.read };
692
+ for (const e of expected.values()) {
693
+ if (answered.has(memberKey(line.member, e.address)))
694
+ continue;
695
+ records.push(notReachedRecord(head, e, at));
696
+ answered.add(memberKey(line.member, e.address));
697
+ }
698
+ }
699
+ }
700
+ const reading = [];
701
+ for (const start of open.values()) {
702
+ for (const e of driftExpected(start)) {
703
+ if (!answered.has(memberKey(start.member, e.address))) {
704
+ reading.push({ member: start.member, read: start.read, address: e.address, node: e.node ?? null });
705
+ }
706
+ }
707
+ }
708
+ reading.sort((a, b) => byString(recordKey(a), recordKey(b)));
709
+ return { document: driftDocument(records, options), reading };
710
+ }
711
+ /**
712
+ * Where an adapter's record for `address` sits: `graphNodeFor` on the
713
+ * input's graph, plunge's by the `pulumi` rule ({@link driftPlanner}). Exported
714
+ * for the lexicons' adapters.
715
+ */
716
+ export function placeDriftRecord(input, engine, address) {
717
+ if (!input.graph)
718
+ return { node: null };
719
+ const placed = graphNodeFor({ member: input.member, planner: driftPlanner(engine), address }, input.graph);
720
+ return { node: placed.node, ...(placed.instanceOf !== undefined ? { instanceOf: placed.instanceOf } : {}) };
721
+ }
722
+ function base(input, engine, read, address) {
723
+ return {
724
+ member: input.member,
725
+ engine,
726
+ address,
727
+ ...placeDriftRecord(input, engine, address),
728
+ observedAt: input.observedAt,
729
+ read,
730
+ ...(input.fellBackFrom !== undefined ? { fellBackFrom: input.fellBackFrom } : {}),
731
+ };
732
+ }
733
+ function chantEvidence(input, command) {
734
+ return { tool: "chant", ...(input.version ? { version: input.version } : {}), command };
735
+ }
736
+ /**
737
+ * A chant member's records, from `lifecycle diff --live`: `unchanged` and
738
+ * `newlyObserved` are in-sync, `driftedSinceSnapshot` drifted (before is the
739
+ * snapshot, after the read), `missing` pending, `disappeared` missing,
740
+ * `orphan` undeclared, `runtimeChildren` runtime, `unobserved` not-observed.
741
+ * An entity in both `missing` and `disappeared` was deployed and is gone,
742
+ * so it is missing.
743
+ */
744
+ export function liveDiffDriftRecords(input) {
745
+ const { diff } = input;
746
+ const evidence = chantEvidence(input, "chant lifecycle diff --live");
747
+ const out = new Map();
748
+ const put = (name, rest, type) => {
749
+ if (out.has(name))
750
+ return;
751
+ const t = type ?? input.types?.[name];
752
+ out.set(name, driftRecord({ ...base(input, "chant", "live-read", name), ...(t ? { resourceType: t } : {}), evidence, ...rest }));
753
+ };
754
+ for (const u of diff.unobserved ?? []) {
755
+ put(u.name, {
756
+ status: "not-observed",
757
+ unobserved: { reason: u.reason, ...(u.detail ? { detail: u.detail } : {}), ...(u.queried ? { queried: u.queried } : {}) },
758
+ }, u.type);
759
+ }
760
+ for (const d of diff.driftedSinceSnapshot ?? []) {
761
+ put(d.name, {
762
+ status: "drifted",
763
+ attributes: d.changes.map((c) => ({ path: c.path, ...(c.oldValue !== undefined ? { before: c.oldValue } : {}), ...(c.newValue !== undefined ? { after: c.newValue } : {}) })),
764
+ }, d.type);
765
+ }
766
+ for (const name of diff.disappeared ?? [])
767
+ put(name, { status: "missing" });
768
+ for (const name of diff.missing ?? [])
769
+ put(name, { status: "pending" });
770
+ for (const name of diff.orphan ?? [])
771
+ put(name, { status: "undeclared" });
772
+ for (const r of diff.runtimeChildren ?? [])
773
+ put(r.name, { status: "runtime", ownership: { verdict: "foreign", ownedBy: r.owner } }, r.type);
774
+ for (const name of [...(diff.unchanged ?? []), ...(diff.newlyObserved ?? [])])
775
+ put(name, { status: "in-sync" });
776
+ return [...out.values()].sort((a, b) => byString(a.address, b.address));
777
+ }
778
+ /**
779
+ * A chant member's records from the deep diff alone: `drifted` with paths
780
+ * (before is the declared value, after the live one; an `absent` path has
781
+ * no after), `unchanged` in-sync, `unobserved` not-observed and
782
+ * `undeclaredEntities` undeclared. Accepted and held paths are not drift and
783
+ * are left out. To lay these over the thin read's records, so a deep hole
784
+ * stays on the attribute surface, use {@link mergeDeepDrift}.
785
+ */
786
+ export function deepDiffDriftRecords(input) {
787
+ const { diff } = input;
788
+ const evidence = chantEvidence(input, input.command ?? "chant lifecycle diff --live --deep");
789
+ const out = new Map();
790
+ const put = (name, rest) => {
791
+ if (!out.has(name))
792
+ out.set(name, driftRecord({ ...base(input, "chant", "live-read", name), evidence, ...rest }));
793
+ };
794
+ for (const u of diff.unobserved ?? []) {
795
+ put(u.name, {
796
+ ...(u.type ? { resourceType: u.type } : {}),
797
+ status: "not-observed",
798
+ unobserved: { reason: u.reason, ...(u.detail ? { detail: u.detail } : {}), ...(u.queried ? { queried: u.queried } : {}) },
799
+ });
800
+ }
801
+ for (const d of diff.drifted ?? []) {
802
+ put(d.name, {
803
+ resourceType: d.type,
804
+ status: "drifted",
805
+ attributes: d.changes.map((c) => ({
806
+ path: c.path,
807
+ ...(c.declared !== undefined ? { before: c.declared } : {}),
808
+ ...(c.kind === "changed" && c.live !== undefined ? { after: c.live } : {}),
809
+ })),
810
+ });
811
+ }
812
+ for (const name of diff.undeclaredEntities ?? [])
813
+ put(name, { status: "undeclared" });
814
+ for (const name of diff.unchanged ?? [])
815
+ put(name, { status: "in-sync" });
816
+ return [...out.values()].sort((a, b) => byString(a.address, b.address));
817
+ }
818
+ /**
819
+ * The thin read's records with the deep read laid over them, by member and
820
+ * address. A thin in-sync record the deep read found drifted becomes
821
+ * drifted with the deep paths. A deep not-observed record never changes the
822
+ * thin status: presence is known, so it becomes `attributesUnobserved` on an
823
+ * in-sync or drifted record and is dropped elsewhere. Deep records with no
824
+ * thin record are added as they are.
825
+ */
826
+ export function mergeDeepDrift(thin, deep) {
827
+ const key = (r) => `${r.member}\u0000${r.address}`;
828
+ const out = new Map();
829
+ for (const r of thin)
830
+ out.set(key(r), r);
831
+ for (const d of deep) {
832
+ const t = out.get(key(d));
833
+ if (!t) {
834
+ out.set(key(d), d);
835
+ continue;
836
+ }
837
+ if (d.status === "not-observed") {
838
+ if (t.status === "in-sync" || t.status === "drifted")
839
+ out.set(key(d), driftRecord({ ...t, attributesUnobserved: d.unobserved }));
840
+ }
841
+ else if (d.status === "drifted" && (t.status === "in-sync" || t.status === "drifted")) {
842
+ out.set(key(d), driftRecord({ ...t, status: "drifted", attributes: [...(t.attributes ?? []), ...(d.attributes ?? [])] }));
843
+ }
844
+ }
845
+ return [...out.values()].sort((a, b) => byString(key(a), key(b)));
846
+ }
847
+ /**
848
+ * A member's records from component status, one per component, addressed
849
+ * by component name: `reconciled` in-sync, `drifted` drifted, `stale`
850
+ * missing (recorded and no longer observed live), `unknown` not-observed
851
+ * with the row's reason, or `not-reached` when live status was never asked
852
+ * for. `unrecorded` is drifted when the component is live, since something
853
+ * runs that no release accounts for, pending when nothing is, and
854
+ * not-observed when live status was not asked for.
855
+ */
856
+ export function componentStatusDriftRecords(input) {
857
+ const evidence = chantEvidence(input, "chant components status --live");
858
+ return input.rows
859
+ .map((row) => {
860
+ const at = base(input, "chant", "live-read", row.component);
861
+ const common = { ...at, resourceType: "component", evidence };
862
+ switch (row.reconciliation) {
863
+ case "reconciled":
864
+ return driftRecord({ ...common, status: "in-sync" });
865
+ case "drifted":
866
+ return driftRecord({ ...common, status: "drifted" });
867
+ case "stale":
868
+ return driftRecord({ ...common, status: "missing" });
869
+ case "unrecorded":
870
+ if (row.live === true)
871
+ return driftRecord({ ...common, status: "drifted" });
872
+ if (row.live === false)
873
+ return driftRecord({ ...common, status: "pending" });
874
+ return driftRecord({ ...common, status: "not-observed", unobserved: { reason: "not-reached", detail: row.detail } });
875
+ case "unknown":
876
+ return driftRecord({
877
+ ...common,
878
+ status: "not-observed",
879
+ unobserved: row.unobserved
880
+ ? { reason: row.unobserved.reason, ...(row.unobserved.detail ? { detail: row.unobserved.detail } : {}) }
881
+ : { reason: "not-reached", detail: row.detail },
882
+ });
883
+ }
884
+ })
885
+ .sort((a, b) => byString(a.address, b.address));
886
+ }