@intentius/chant 0.124.52 → 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 (584) 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 +15 -2
  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 +215 -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/declared-reads.d.ts +38 -0
  120. package/dist/components/declared-reads.d.ts.map +1 -0
  121. package/dist/components/declared-reads.js +131 -0
  122. package/dist/components/deploy-units.d.ts.map +1 -1
  123. package/dist/components/deploy-units.js +4 -0
  124. package/dist/components/driver.d.ts +34 -1
  125. package/dist/components/driver.d.ts.map +1 -1
  126. package/dist/components/driver.js +66 -3
  127. package/dist/components/fan-out-run.d.ts +23 -1
  128. package/dist/components/fan-out-run.d.ts.map +1 -1
  129. package/dist/components/fan-out-run.js +82 -14
  130. package/dist/components/fan-out-support.d.ts +6 -0
  131. package/dist/components/fan-out-support.d.ts.map +1 -1
  132. package/dist/components/fan-out-support.js +4 -1
  133. package/dist/components/fan-out.d.ts +28 -0
  134. package/dist/components/fan-out.d.ts.map +1 -1
  135. package/dist/components/groups.d.ts +229 -0
  136. package/dist/components/groups.d.ts.map +1 -0
  137. package/dist/components/groups.js +760 -0
  138. package/dist/components/index.d.ts +6 -2
  139. package/dist/components/index.d.ts.map +1 -1
  140. package/dist/components/index.js +4 -0
  141. package/dist/components/plan-shape-conformance.d.ts +48 -0
  142. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  143. package/dist/components/plan-shape-conformance.js +122 -0
  144. package/dist/components/pr-run.d.ts +41 -4
  145. package/dist/components/pr-run.d.ts.map +1 -1
  146. package/dist/components/pr-run.js +209 -39
  147. package/dist/components/promote-rounds.d.ts +9 -0
  148. package/dist/components/promote-rounds.d.ts.map +1 -1
  149. package/dist/components/promote-rounds.js +23 -2
  150. package/dist/components/promote.d.ts +17 -0
  151. package/dist/components/promote.d.ts.map +1 -1
  152. package/dist/components/promote.js +36 -6
  153. package/dist/components/release-gate.d.ts +23 -1
  154. package/dist/components/release-gate.d.ts.map +1 -1
  155. package/dist/components/release-gate.js +97 -33
  156. package/dist/components/run-groups.d.ts +58 -0
  157. package/dist/components/run-groups.d.ts.map +1 -0
  158. package/dist/components/run-groups.js +78 -0
  159. package/dist/components/starter-plugin.d.ts +1 -0
  160. package/dist/components/starter-plugin.d.ts.map +1 -1
  161. package/dist/components/starter-plugin.js +6 -1
  162. package/dist/components/verbs/index.d.ts +1 -0
  163. package/dist/components/verbs/index.d.ts.map +1 -1
  164. package/dist/components/verbs/index.js +1 -0
  165. package/dist/components/verbs/member-outputs.d.ts +187 -0
  166. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  167. package/dist/components/verbs/member-outputs.js +218 -0
  168. package/dist/config.d.ts +60 -0
  169. package/dist/config.d.ts.map +1 -1
  170. package/dist/config.js +27 -0
  171. package/dist/deep-observation.d.ts +1 -13
  172. package/dist/deep-observation.d.ts.map +1 -1
  173. package/dist/deep-observation.js +5 -22
  174. package/dist/drift-record.d.ts +542 -0
  175. package/dist/drift-record.d.ts.map +1 -0
  176. package/dist/drift-record.js +886 -0
  177. package/dist/engine-facts.d.ts +144 -0
  178. package/dist/engine-facts.d.ts.map +1 -0
  179. package/dist/engine-facts.js +14 -0
  180. package/dist/graph-ir.d.ts +153 -2
  181. package/dist/graph-ir.d.ts.map +1 -1
  182. package/dist/graph-ir.js +224 -6
  183. package/dist/graph-refs.d.ts +145 -0
  184. package/dist/graph-refs.d.ts.map +1 -1
  185. package/dist/graph-refs.js +305 -0
  186. package/dist/graph-schema/fixtures/adopt.json +15 -0
  187. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  188. package/dist/graph-schema/fixtures/index.json +68 -0
  189. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  190. package/dist/graph-schema/fixtures.d.ts +23 -0
  191. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  192. package/dist/graph-schema/fixtures.js +246 -0
  193. package/dist/graph-schema/generate.d.ts +15 -0
  194. package/dist/graph-schema/generate.d.ts.map +1 -0
  195. package/dist/graph-schema/generate.js +267 -0
  196. package/dist/graph-schema/graph.schema.json +1387 -0
  197. package/dist/health.schema.json +2 -1
  198. package/dist/identity.d.ts.map +1 -1
  199. package/dist/identity.js +2 -0
  200. package/dist/index.d.ts +1 -0
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +2 -0
  203. package/dist/instance-nodes.d.ts +159 -0
  204. package/dist/instance-nodes.d.ts.map +1 -0
  205. package/dist/instance-nodes.js +231 -0
  206. package/dist/lexicon.d.ts +72 -0
  207. package/dist/lexicon.d.ts.map +1 -1
  208. package/dist/lifecycle/adoption-file.d.ts +56 -0
  209. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  210. package/dist/lifecycle/adoption-file.js +123 -0
  211. package/dist/lifecycle/cloud-index.d.ts +186 -0
  212. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  213. package/dist/lifecycle/cloud-index.js +618 -0
  214. package/dist/lifecycle/derive-edges.d.ts +59 -0
  215. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  216. package/dist/lifecycle/derive-edges.js +126 -0
  217. package/dist/lifecycle/git.d.ts +10 -9
  218. package/dist/lifecycle/git.d.ts.map +1 -1
  219. package/dist/lifecycle/git.js +7 -1
  220. package/dist/lifecycle/identity-match.d.ts +133 -0
  221. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  222. package/dist/lifecycle/identity-match.js +147 -0
  223. package/dist/lifecycle/index-diff.d.ts +224 -0
  224. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  225. package/dist/lifecycle/index-diff.js +327 -0
  226. package/dist/lifecycle/index-document.d.ts +130 -0
  227. package/dist/lifecycle/index-document.d.ts.map +1 -0
  228. package/dist/lifecycle/index-document.js +189 -0
  229. package/dist/lifecycle/index-drift.d.ts +121 -0
  230. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  231. package/dist/lifecycle/index-drift.js +178 -0
  232. package/dist/lifecycle/index-import.d.ts +168 -0
  233. package/dist/lifecycle/index-import.d.ts.map +1 -0
  234. package/dist/lifecycle/index-import.js +515 -0
  235. package/dist/lifecycle/index-join.d.ts +129 -0
  236. package/dist/lifecycle/index-join.d.ts.map +1 -0
  237. package/dist/lifecycle/index-join.js +248 -0
  238. package/dist/lifecycle/index.d.ts +4 -0
  239. package/dist/lifecycle/index.d.ts.map +1 -1
  240. package/dist/lifecycle/index.js +4 -0
  241. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  242. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  243. package/dist/lifecycle/observation-baseline.js +144 -19
  244. package/dist/lifecycle/observe.d.ts.map +1 -1
  245. package/dist/lifecycle/observe.js +6 -1
  246. package/dist/lifecycle/query.d.ts +45 -4
  247. package/dist/lifecycle/query.d.ts.map +1 -1
  248. package/dist/lifecycle/query.js +89 -7
  249. package/dist/lifecycle/replay.d.ts +10 -3
  250. package/dist/lifecycle/replay.d.ts.map +1 -1
  251. package/dist/lifecycle/replay.js +10 -6
  252. package/dist/lifecycle/scenario.d.ts +4 -3
  253. package/dist/lifecycle/scenario.d.ts.map +1 -1
  254. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  255. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  256. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  257. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  258. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  259. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  260. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  261. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store.js +832 -0
  263. package/dist/lifecycle/snapshot.d.ts +11 -0
  264. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  265. package/dist/lifecycle/snapshot.js +18 -5
  266. package/dist/lifecycle/type-listing.d.ts +148 -0
  267. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  268. package/dist/lifecycle/type-listing.js +166 -0
  269. package/dist/lint/component-checks.d.ts +15 -1
  270. package/dist/lint/component-checks.d.ts.map +1 -1
  271. package/dist/lint/component-checks.js +1 -0
  272. package/dist/lint/presets/relaxed.json +2 -1
  273. package/dist/lint/presets/strict.json +2 -1
  274. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  275. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  276. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  277. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  278. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  279. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  280. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  281. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  282. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  283. package/dist/lint/rules/comp/index.d.ts +2 -1
  284. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  285. package/dist/lint/rules/comp/index.js +4 -1
  286. package/dist/observation.d.ts +19 -2
  287. package/dist/observation.d.ts.map +1 -1
  288. package/dist/observation.js +18 -1
  289. package/dist/op/activities/activity-contracts.d.ts +1 -1
  290. package/dist/op/activities/activity-contracts.js +1 -1
  291. package/dist/op/activities/change-set.d.ts +6 -4
  292. package/dist/op/activities/change-set.d.ts.map +1 -1
  293. package/dist/op/activities/change-set.js +4 -2
  294. package/dist/op/activity-failure.d.ts +27 -0
  295. package/dist/op/activity-failure.d.ts.map +1 -1
  296. package/dist/op/activity-failure.js +31 -0
  297. package/dist/op/index.d.ts +2 -1
  298. package/dist/op/index.d.ts.map +1 -1
  299. package/dist/op/index.js +1 -1
  300. package/dist/op/local-executor.d.ts +5 -1
  301. package/dist/op/local-executor.d.ts.map +1 -1
  302. package/dist/op/local-executor.js +22 -8
  303. package/dist/pr-loop.d.ts +56 -1
  304. package/dist/pr-loop.d.ts.map +1 -1
  305. package/dist/pr-loop.js +51 -1
  306. package/dist/pulumi-urn.d.ts +101 -0
  307. package/dist/pulumi-urn.d.ts.map +1 -0
  308. package/dist/pulumi-urn.js +131 -0
  309. package/dist/release-floors.d.ts +23 -0
  310. package/dist/release-floors.d.ts.map +1 -0
  311. package/dist/release-floors.js +22 -0
  312. package/dist/sensitive-key.d.ts +23 -0
  313. package/dist/sensitive-key.d.ts.map +1 -0
  314. package/dist/sensitive-key.js +31 -0
  315. package/dist/wave-runtime.mjs +234 -234
  316. package/dist/workspace/adoption-report.d.ts +53 -0
  317. package/dist/workspace/adoption-report.d.ts.map +1 -0
  318. package/dist/workspace/adoption-report.js +76 -0
  319. package/dist/workspace/capabilities-contract.d.ts +7 -0
  320. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  321. package/dist/workspace/capabilities-contract.js +2 -1
  322. package/dist/workspace/capabilities.schema.json +98 -0
  323. package/dist/workspace/change-set.schema.json +42 -3
  324. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  325. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  326. package/dist/workspace/ci-cloud-identity.js +30 -1
  327. package/dist/workspace/compose-graph.d.ts +10 -1
  328. package/dist/workspace/compose-graph.d.ts.map +1 -1
  329. package/dist/workspace/compose-graph.js +6 -1
  330. package/dist/workspace/conformance/index.d.ts +1 -1
  331. package/dist/workspace/conformance/index.d.ts.map +1 -1
  332. package/dist/workspace/conformance/index.js +6 -1
  333. package/dist/workspace/drift-cli.d.ts +81 -0
  334. package/dist/workspace/drift-cli.d.ts.map +1 -0
  335. package/dist/workspace/drift-cli.js +317 -0
  336. package/dist/workspace/drift.schema.json +672 -0
  337. package/dist/workspace/graph-cli.d.ts +2 -0
  338. package/dist/workspace/graph-cli.d.ts.map +1 -1
  339. package/dist/workspace/graph-cli.js +3 -0
  340. package/dist/workspace/graph.schema.json +72 -0
  341. package/dist/workspace/init.d.ts +5 -1
  342. package/dist/workspace/init.d.ts.map +1 -1
  343. package/dist/workspace/init.js +55 -9
  344. package/dist/workspace/ledger.schema.json +1 -1
  345. package/dist/workspace/nested-graph.d.ts.map +1 -1
  346. package/dist/workspace/nested-graph.js +2 -0
  347. package/dist/workspace/pr-report.schema.json +51 -2
  348. package/dist/workspace/promote-waves.d.ts +112 -0
  349. package/dist/workspace/promote-waves.d.ts.map +1 -0
  350. package/dist/workspace/promote-waves.js +290 -0
  351. package/dist/workspace/promote.d.ts +84 -2
  352. package/dist/workspace/promote.d.ts.map +1 -1
  353. package/dist/workspace/promote.js +347 -61
  354. package/dist/workspace/reason-codes.d.ts +12 -0
  355. package/dist/workspace/reason-codes.d.ts.map +1 -1
  356. package/dist/workspace/reason-codes.js +14 -0
  357. package/dist/workspace/search-contract.d.ts +3 -3
  358. package/dist/workspace/search-contract.d.ts.map +1 -1
  359. package/dist/workspace/search-contract.js +2 -0
  360. package/dist/workspace/search.schema.json +103 -5
  361. package/package.json +14 -1
  362. package/src/__fixtures__/drift-record/README.md +58 -0
  363. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  364. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  365. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  366. package/src/__fixtures__/drift-record/component-status.json +91 -0
  367. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  368. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  369. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  370. package/src/audit/discover.e2e.test.ts +14 -0
  371. package/src/audit/discover.ts +3 -1
  372. package/src/behaviour.ts +1 -1
  373. package/src/capabilities/templates.json +145 -0
  374. package/src/capabilities/templates.test.ts +212 -0
  375. package/src/capabilities/templates.ts +204 -0
  376. package/src/change-set-bundle.test.ts +83 -0
  377. package/src/change-set-graph.ts +80 -9
  378. package/src/change-set.test.ts +24 -0
  379. package/src/change-set.ts +77 -14
  380. package/src/ci-identity-targets.test.ts +23 -0
  381. package/src/ci-identity.ts +10 -4
  382. package/src/cli/commands/import-index.test.ts +117 -0
  383. package/src/cli/commands/import-index.ts +265 -0
  384. package/src/cli/commands/lint.ts +4 -2
  385. package/src/cli/handlers/components.test.ts +9 -0
  386. package/src/cli/handlers/components.ts +12 -2
  387. package/src/cli/handlers/fan-out.ts +17 -3
  388. package/src/cli/handlers/graph.test.ts +42 -0
  389. package/src/cli/handlers/graph.ts +26 -4
  390. package/src/cli/handlers/index-cloud.ts +32 -0
  391. package/src/cli/handlers/index-diff.test.ts +68 -0
  392. package/src/cli/handlers/index-diff.ts +146 -0
  393. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  394. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  395. package/src/cli/handlers/lifecycle.test.ts +33 -11
  396. package/src/cli/handlers/lifecycle.ts +205 -30
  397. package/src/cli/handlers/misc.ts +73 -0
  398. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  399. package/src/cli/handlers/pr.ts +14 -2
  400. package/src/cli/handlers/promote.test.ts +64 -0
  401. package/src/cli/handlers/promote.ts +81 -6
  402. package/src/cli/handlers/query.ts +27 -4
  403. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  404. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  405. package/src/cli/handlers/scenario.test.ts +13 -0
  406. package/src/cli/handlers/scenario.ts +8 -7
  407. package/src/cli/handlers/search-index.test.ts +255 -0
  408. package/src/cli/handlers/search-json.test.ts +1 -1
  409. package/src/cli/handlers/search-ownership.test.ts +194 -0
  410. package/src/cli/handlers/search.ts +271 -38
  411. package/src/cli/main.test.ts +7 -0
  412. package/src/cli/main.ts +56 -1
  413. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  414. package/src/cli/mcp/resource-handlers.ts +4 -3
  415. package/src/cli/mcp/workspace-tools.ts +1 -1
  416. package/src/cli/parse-args.ts +40 -2
  417. package/src/cli/registry.ts +19 -1
  418. package/src/cloud-lister/aws.test.ts +202 -0
  419. package/src/cloud-lister/aws.ts +226 -0
  420. package/src/cloud-lister/azure.test.ts +132 -0
  421. package/src/cloud-lister/azure.ts +142 -0
  422. package/src/cloud-lister/common.ts +115 -0
  423. package/src/cloud-lister/credentials.ts +110 -0
  424. package/src/cloud-lister/fakes.ts +66 -0
  425. package/src/cloud-lister/gcp.test.ts +121 -0
  426. package/src/cloud-lister/gcp.ts +159 -0
  427. package/src/cloud-lister/guard.test.ts +198 -0
  428. package/src/cloud-lister/guard.ts +111 -0
  429. package/src/cloud-lister/ids.ts +54 -0
  430. package/src/cloud-lister/index.ts +134 -0
  431. package/src/cloud-lister/kubernetes.test.ts +160 -0
  432. package/src/cloud-lister/kubernetes.ts +284 -0
  433. package/src/cloud-lister/markers.ts +74 -0
  434. package/src/cloud-lister/mask.ts +77 -0
  435. package/src/cloud-lister/shape.test.ts +81 -0
  436. package/src/cloud-lister/sigv4.ts +70 -0
  437. package/src/cloud-lister/transport.ts +118 -0
  438. package/src/cloud-lister/types.ts +135 -0
  439. package/src/components/capability-plugin-loader.ts +4 -0
  440. package/src/components/capability-plugin.ts +10 -0
  441. package/src/components/capability.ts +229 -0
  442. package/src/components/cli-support.ts +70 -14
  443. package/src/components/declared-reads.test.ts +103 -0
  444. package/src/components/declared-reads.ts +134 -0
  445. package/src/components/deploy-units.ts +4 -0
  446. package/src/components/driver.ts +89 -4
  447. package/src/components/fan-out-run.ts +95 -15
  448. package/src/components/fan-out-support.ts +10 -1
  449. package/src/components/fan-out.ts +30 -0
  450. package/src/components/groups.test.ts +320 -0
  451. package/src/components/groups.ts +880 -0
  452. package/src/components/index.ts +42 -0
  453. package/src/components/plan-shape-conformance.ts +130 -0
  454. package/src/components/pr-env-run-bundle.test.ts +2 -0
  455. package/src/components/pr-run.test.ts +516 -3
  456. package/src/components/pr-run.ts +231 -41
  457. package/src/components/promote-rounds.ts +25 -2
  458. package/src/components/promote.ts +55 -5
  459. package/src/components/registry.test.ts +1 -1
  460. package/src/components/release-gate.ts +115 -30
  461. package/src/components/release-groups.test.ts +362 -0
  462. package/src/components/run-groups.ts +113 -0
  463. package/src/components/starter-plugin.ts +6 -0
  464. package/src/components/verbs/index.ts +1 -0
  465. package/src/components/verbs/member-outputs.test.ts +205 -0
  466. package/src/components/verbs/member-outputs.ts +308 -0
  467. package/src/config.ts +44 -0
  468. package/src/deep-observation.ts +5 -23
  469. package/src/drift-record.test.ts +596 -0
  470. package/src/drift-record.ts +1206 -0
  471. package/src/engine-facts.guard.test.ts +111 -0
  472. package/src/engine-facts.test.ts +178 -0
  473. package/src/engine-facts.ts +154 -0
  474. package/src/gated-waves-bundle.test.ts +2 -0
  475. package/src/graph-ir-live.test.ts +92 -0
  476. package/src/graph-ir.ts +317 -7
  477. package/src/graph-refs.ts +416 -0
  478. package/src/graph-schema/fixtures/adopt.json +15 -0
  479. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  480. package/src/graph-schema/fixtures/index.json +68 -0
  481. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  482. package/src/graph-schema/fixtures.ts +267 -0
  483. package/src/graph-schema/generate.ts +263 -0
  484. package/src/graph-schema/graph-schema.test.ts +153 -0
  485. package/src/graph-schema/graph.schema.json +1387 -0
  486. package/src/health.schema.json +2 -1
  487. package/src/identity.ts +2 -0
  488. package/src/index.ts +2 -0
  489. package/src/instance-nodes.ts +288 -0
  490. package/src/lexicon.ts +76 -0
  491. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  492. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  493. package/src/lifecycle/adoption-file.test.ts +100 -0
  494. package/src/lifecycle/adoption-file.ts +154 -0
  495. package/src/lifecycle/cloud-index.test.ts +517 -0
  496. package/src/lifecycle/cloud-index.ts +736 -0
  497. package/src/lifecycle/derive-edges.test.ts +193 -0
  498. package/src/lifecycle/derive-edges.ts +182 -0
  499. package/src/lifecycle/git.ts +10 -4
  500. package/src/lifecycle/identity-match.test.ts +308 -0
  501. package/src/lifecycle/identity-match.ts +257 -0
  502. package/src/lifecycle/index-diff.test.ts +198 -0
  503. package/src/lifecycle/index-diff.ts +476 -0
  504. package/src/lifecycle/index-document.test.ts +64 -0
  505. package/src/lifecycle/index-document.ts +299 -0
  506. package/src/lifecycle/index-drift.test.ts +191 -0
  507. package/src/lifecycle/index-drift.ts +304 -0
  508. package/src/lifecycle/index-import.test.ts +255 -0
  509. package/src/lifecycle/index-import.ts +634 -0
  510. package/src/lifecycle/index-join.test.ts +250 -0
  511. package/src/lifecycle/index-join.ts +304 -0
  512. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  513. package/src/lifecycle/index.ts +4 -0
  514. package/src/lifecycle/member-ledger.test.ts +25 -7
  515. package/src/lifecycle/observation-baseline.test.ts +72 -1
  516. package/src/lifecycle/observation-baseline.ts +190 -27
  517. package/src/lifecycle/observe.ts +6 -1
  518. package/src/lifecycle/query.test.ts +69 -1
  519. package/src/lifecycle/query.ts +103 -10
  520. package/src/lifecycle/replay.test.ts +9 -5
  521. package/src/lifecycle/replay.ts +9 -5
  522. package/src/lifecycle/scenario.ts +4 -3
  523. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  524. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  525. package/src/lifecycle/snapshot-store.test.ts +385 -0
  526. package/src/lifecycle/snapshot-store.ts +981 -0
  527. package/src/lifecycle/snapshot.test.ts +13 -0
  528. package/src/lifecycle/snapshot.ts +23 -6
  529. package/src/lifecycle/type-listing.test.ts +166 -0
  530. package/src/lifecycle/type-listing.ts +309 -0
  531. package/src/lint/component-checks.ts +17 -1
  532. package/src/lint/config.test.ts +3 -0
  533. package/src/lint/presets/relaxed.json +2 -1
  534. package/src/lint/presets/strict.json +2 -1
  535. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  536. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  537. package/src/lint/rules/comp/comp.test.ts +2 -1
  538. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  539. package/src/lint/rules/comp/comp008.test.ts +77 -0
  540. package/src/lint/rules/comp/index.ts +4 -1
  541. package/src/observation-edges.test.ts +33 -0
  542. package/src/observation.ts +36 -1
  543. package/src/op/activities/activity-contracts.ts +1 -1
  544. package/src/op/activities/change-set.ts +8 -5
  545. package/src/op/activity-failure.ts +36 -0
  546. package/src/op/index.ts +2 -1
  547. package/src/op/local-executor.test.ts +80 -0
  548. package/src/op/local-executor.ts +27 -9
  549. package/src/pr-loop-bundle.test.ts +5 -2
  550. package/src/pr-loop.test.ts +43 -0
  551. package/src/pr-loop.ts +111 -2
  552. package/src/pulumi-urn.ts +151 -0
  553. package/src/release-floors.ts +22 -0
  554. package/src/sensitive-key.ts +34 -0
  555. package/src/workspace/adoption-report.test.ts +61 -0
  556. package/src/workspace/adoption-report.ts +103 -0
  557. package/src/workspace/capabilities-contract.test.ts +4 -0
  558. package/src/workspace/capabilities-contract.ts +9 -1
  559. package/src/workspace/capabilities.schema.json +98 -0
  560. package/src/workspace/change-set.schema.json +42 -3
  561. package/src/workspace/ci-cloud-identity.ts +31 -1
  562. package/src/workspace/compose-graph.test.ts +12 -0
  563. package/src/workspace/compose-graph.ts +12 -1
  564. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  565. package/src/workspace/conformance/index.ts +6 -1
  566. package/src/workspace/drift-cli.test.ts +65 -0
  567. package/src/workspace/drift-cli.ts +365 -0
  568. package/src/workspace/drift.schema.json +672 -0
  569. package/src/workspace/graph-cli.ts +5 -0
  570. package/src/workspace/graph.schema.json +72 -0
  571. package/src/workspace/init.test.ts +38 -1
  572. package/src/workspace/init.ts +50 -8
  573. package/src/workspace/intent-gaps.test.ts +18 -2
  574. package/src/workspace/ledger.schema.json +1 -1
  575. package/src/workspace/nested-graph.ts +1 -0
  576. package/src/workspace/pr-report.schema.json +51 -2
  577. package/src/workspace/promote-waves.test.ts +296 -0
  578. package/src/workspace/promote-waves.ts +340 -0
  579. package/src/workspace/promote.test.ts +158 -4
  580. package/src/workspace/promote.ts +398 -57
  581. package/src/workspace/reason-codes.test.ts +4 -1
  582. package/src/workspace/reason-codes.ts +14 -0
  583. package/src/workspace/search-contract.ts +2 -0
  584. package/src/workspace/search.schema.json +103 -5
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Drift without a deploy (#4526, tier 2 of INTENTIUS/behold#1025): the deep
3
+ * diff of declared source against an index version, for source chant never
4
+ * applied. `chant lifecycle diff <env> --index <ref>` is the command.
5
+ *
6
+ * Three steps, all pure:
7
+ *
8
+ * 1. Every declared entity is joined to the index through its lexicon's
9
+ * index match, with {@link joinDeclaredToIndex} (./index-join.ts, #4525).
10
+ * `chant search --index` joins the same way, so a row that binds there
11
+ * binds here.
12
+ * 2. A bound row's declared property tree is compared with its indexed
13
+ * node's `attrs` by the live deep diff ({@link diffDeepObservation},
14
+ * ./deep-observe.ts): both sides normalized with the lexicon's own hooks,
15
+ * and the observation baseline (`.chant/observation-baseline/<env>.json`,
16
+ * #4519) subtracted, value-bound. An accepted deviation stays quiet until
17
+ * the indexed value moves again.
18
+ * 3. Every row becomes one drift record (`drift: "v1"`, ../drift-record.ts).
19
+ *
20
+ * A row that is not bound is reported with its join verdict and never
21
+ * diffed. The verdict rides in `evidence.engineCode`:
22
+ *
23
+ * - `absent`: the index's coverage listed the type and location and holds
24
+ * no resource with the identity the declaration spells. The record is
25
+ * `pending` (declared, confirmed absent). The rule is
26
+ * {@link indexAbsence}, the one `chant index diff` reads "removed" by
27
+ * (./index-diff.ts, #4533);
28
+ * - `not-observed`: the index did not look there (filtered, not reached, a
29
+ * read failure), or the lexicon has no index match. An uncovered entity is
30
+ * not observed, never drifted and never in sync;
31
+ * - `no-binding`: the declaration spells no identity to join on;
32
+ * - `ambiguous`: two or more indexed resources carry the identity, and chant
33
+ * will not pick one.
34
+ *
35
+ * The last three are status `not-observed` with a reason from the
36
+ * observation contract's closed list. `not-observed` is also the index
37
+ * diff's word ({@link IndexChangeKind}), so the two comparisons against an
38
+ * index use one vocabulary.
39
+ *
40
+ * Every record's read is `live-read`, observed at the index's
41
+ * `stamp.observedAt`: an index version is a read of the provider, taken
42
+ * then. `evidence.command` names the index version that answered.
43
+ */
44
+ import type { ChangeSetGraphNodes } from "../change-set-graph";
45
+ import type { DeepNormalizationHooks, DeepResourceObservation } from "../deep-observation";
46
+ import {
47
+ deepDiffDriftRecords,
48
+ driftDocument,
49
+ driftRecord,
50
+ placeDriftRecord,
51
+ type DriftDocument,
52
+ type DriftRecord,
53
+ type DriftStatus,
54
+ type DriftUnobserved,
55
+ } from "../drift-record";
56
+ import type { GraphIR, IRNode } from "../graph-ir";
57
+ import { isUnobservedReason } from "../observation";
58
+ import { countPropertyDrift, type DeepDiffResult } from "./deep-diff";
59
+ import { diffDeepObservation, type DeclaredEntities } from "./deep-observe";
60
+ import { indexAbsence, type IndexChangeKind } from "./index-diff";
61
+ import type { IndexDocument } from "./index-document";
62
+ import { formatIndexRef, joinDeclaredToIndex, type IndexJoin, type JoinLexicon } from "./index-join";
63
+ import { baselineForLexicon, type ObservationBaseline } from "./observation-baseline";
64
+
65
+ /** The status an unbound row takes when the index could not say: the index diff's word and the drift record's. */
66
+ export const INDEX_NOT_OBSERVED: Extract<IndexChangeKind, DriftStatus> = "not-observed";
67
+
68
+ /** What the drift read needs of a lexicon: its index match and its deep hooks. A `LexiconPlugin` is one. */
69
+ export interface IndexDriftLexicon extends JoinLexicon {
70
+ deepNormalizationHooks?: DeepNormalizationHooks;
71
+ }
72
+
73
+ export interface IndexDriftInput {
74
+ /** The index version to compare against, as {@link readIndexRef} returned it. */
75
+ doc: IndexDocument;
76
+ /** Its version name, with or without `.json`. */
77
+ version: string;
78
+ /** Declared entities per lexicon, as the live deep diff takes them. */
79
+ entities: ReadonlyMap<string, DeclaredEntities>;
80
+ lexicons: readonly IndexDriftLexicon[];
81
+ /** The environment's accepted deviations, or null when none are recorded. */
82
+ baseline?: ObservationBaseline | null;
83
+ /** The drift record's `member`. */
84
+ member: string;
85
+ /** A stack of a multi-stack project: record addresses are `<stack>::<name>`, as search ids are. */
86
+ stack?: string;
87
+ /** The region the entities live in, from the stack (or the single stack) they belong to. */
88
+ region?: string;
89
+ /** The chant version, for `evidence.version` and the document's `chant`. */
90
+ chant?: string;
91
+ /** The member's graph, to place each record. Omit to leave `node` null. */
92
+ graph?: ChangeSetGraphNodes | null;
93
+ }
94
+
95
+ /** One lexicon's half of the read. */
96
+ export interface IndexDriftLexiconResult {
97
+ /** Every declared entity's join verdict, by entity name. */
98
+ joins: Record<string, IndexJoin>;
99
+ /** The deep diff over the bound rows only. Unbound rows are never in it. */
100
+ deep: DeepDiffResult;
101
+ }
102
+
103
+ export interface IndexDriftCounts {
104
+ /** Declared entities compared. */
105
+ declared: number;
106
+ bound: number;
107
+ /** Reported property differences across every bound row. */
108
+ properties: number;
109
+ /** Bound rows with at least one reported difference. */
110
+ drifted: number;
111
+ /** Differences the baseline suppressed. */
112
+ accepted: number;
113
+ /** Rows the index listed the place of and does not hold. */
114
+ pending: number;
115
+ /** Rows the index could not say anything about. */
116
+ notObserved: number;
117
+ }
118
+
119
+ export interface IndexDriftResult {
120
+ /** `<scope key>@<version>`. */
121
+ ref: string;
122
+ observedAt: string;
123
+ byLexicon: Record<string, IndexDriftLexiconResult>;
124
+ /** One record per declared entity, sorted by address. */
125
+ records: DriftRecord[];
126
+ counts: IndexDriftCounts;
127
+ }
128
+
129
+ /** The command an index read's records name. */
130
+ export function indexDriftCommand(ref: string): string {
131
+ return `chant lifecycle diff --index ${ref}`;
132
+ }
133
+
134
+ function declaredGraph(lexicon: string, entities: DeclaredEntities): GraphIR {
135
+ const nodes: IRNode[] = [...entities.entries()]
136
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
137
+ .map(([name, e]) => ({ id: name, kind: e.entityType, lexicon, attrs: e.props ?? {} }));
138
+ return { nodes, edges: [], groups: { byLexicon: { [lexicon]: nodes.map((n) => n.id) } } };
139
+ }
140
+
141
+ function queriedOf(j: IndexJoin): string | undefined {
142
+ if (!j.listedType) return undefined;
143
+ return `${j.listedType}${j.identifier !== undefined ? ` ${j.identifier}` : ""}${j.location ? ` @ ${j.location}` : ""}`;
144
+ }
145
+
146
+ /** An unbound row's status and why, from its verdict. Never diffed. */
147
+ function unboundStatus(doc: IndexDocument, j: Exclude<IndexJoin, { verdict: "bound" }>): { status: DriftStatus; unobserved?: DriftUnobserved } {
148
+ const queried = queriedOf(j);
149
+ const q = queried ? { queried } : {};
150
+ switch (j.verdict) {
151
+ case "absent": {
152
+ // The join said absent from the index's coverage. Read it again with the
153
+ // index diff's rule, so "absent" means one thing in both comparisons.
154
+ const a = j.listedType && j.location ? indexAbsence(doc.coverage, j.listedType, j.location) : ({ kind: "removed" } as const);
155
+ if (a.kind === "removed") return { status: "pending" };
156
+ return {
157
+ status: INDEX_NOT_OBSERVED,
158
+ unobserved: {
159
+ reason: isUnobservedReason(a.verdict) ? a.verdict : "not-reached",
160
+ detail: a.detail ?? `the index's coverage says ${a.verdict} for ${j.listedType} at ${j.location}`,
161
+ ...q,
162
+ },
163
+ };
164
+ }
165
+ case "not-observed":
166
+ return { status: INDEX_NOT_OBSERVED, unobserved: { reason: j.reason, detail: j.detail, ...q } };
167
+ case "no-binding":
168
+ return { status: INDEX_NOT_OBSERVED, unobserved: { reason: "no-binding", detail: j.detail, ...q } };
169
+ case "ambiguous":
170
+ return {
171
+ status: INDEX_NOT_OBSERVED,
172
+ unobserved: { reason: "no-binding", detail: `${j.detail} (candidates: ${j.candidates.join(", ")})`, ...q },
173
+ };
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Compare every declared entity with the index version, lexicon by lexicon.
179
+ * Deterministic: the same index, declaration and baseline give the same
180
+ * records.
181
+ */
182
+ export function diffDeclaredAgainstIndex(input: IndexDriftInput): IndexDriftResult {
183
+ const { doc } = input;
184
+ const ref = formatIndexRef(doc.stamp.scopeKey, input.version.endsWith(".json") ? input.version : `${input.version}.json`);
185
+ const observedAt = doc.stamp.observedAt;
186
+ const command = indexDriftCommand(ref);
187
+ const address = (name: string) => (input.stack ? `${input.stack}::${name}` : name);
188
+ const indexed = new Map(doc.graph.nodes.map((n) => [n.id, n]));
189
+ const byLexicon: Record<string, IndexDriftLexiconResult> = {};
190
+ const records: DriftRecord[] = [];
191
+ const counts: IndexDriftCounts = { declared: 0, bound: 0, properties: 0, drifted: 0, accepted: 0, pending: 0, notObserved: 0 };
192
+ const adapter = {
193
+ member: input.member,
194
+ observedAt,
195
+ ...(input.graph !== undefined ? { graph: input.graph } : {}),
196
+ ...(input.chant ? { version: input.chant } : {}),
197
+ };
198
+ const evidence = (engineCode?: string) => ({ tool: "chant", ...(input.chant ? { version: input.chant } : {}), command, ...(engineCode ? { engineCode } : {}) });
199
+
200
+ for (const lexicon of [...input.entities.keys()].sort()) {
201
+ const entities = input.entities.get(lexicon)!;
202
+ if (entities.size === 0) continue;
203
+ const plugin = input.lexicons.find((l) => l.name === lexicon) ?? { name: lexicon };
204
+ const { joins } = joinDeclaredToIndex(declaredGraph(lexicon, entities), doc, [plugin], {
205
+ regionOf: () => input.region,
206
+ });
207
+
208
+ // The bound rows are the observed side, each its indexed node's tree.
209
+ const bound: DeclaredEntities = new Map();
210
+ const resources: Record<string, DeepResourceObservation> = {};
211
+ for (const [name, entity] of entities) {
212
+ const j = joins[name];
213
+ if (j?.verdict !== "bound") continue;
214
+ const hit = indexed.get(j.node);
215
+ if (!hit) continue;
216
+ bound.set(name, entity);
217
+ resources[name] = { type: entity.entityType, physicalId: hit.physicalId ?? j.nativeId, properties: (hit.attrs ?? {}) as Record<string, unknown> };
218
+ }
219
+ const deep = diffDeepObservation(bound, { resources, unobserved: {} }, plugin.deepNormalizationHooks, baselineForLexicon(input.baseline, lexicon));
220
+ byLexicon[lexicon] = { joins, deep };
221
+
222
+ // Bound rows: the deep diff's records, and in-sync for a row whose every
223
+ // difference is accepted (the deep diff lists it under neither).
224
+ const deepRecords = new Map(deepDiffDriftRecords({ ...adapter, diff: deep, command }).map((r) => [r.address, r]));
225
+ for (const name of [...entities.keys()].sort()) {
226
+ const entity = entities.get(name)!;
227
+ const j = joins[name];
228
+ counts.declared++;
229
+ const at = { ...placeDriftRecord(adapter, "chant", address(name)) };
230
+ if (j?.verdict === "bound" && bound.has(name)) {
231
+ counts.bound++;
232
+ const r = deepRecords.get(name);
233
+ records.push(
234
+ driftRecord({
235
+ ...(r ?? {
236
+ member: input.member,
237
+ engine: "chant" as const,
238
+ address: name,
239
+ observedAt,
240
+ read: "live-read" as const,
241
+ status: "in-sync" as const,
242
+ evidence: evidence(),
243
+ }),
244
+ address: address(name),
245
+ ...at,
246
+ resourceType: entity.entityType,
247
+ }),
248
+ );
249
+ continue;
250
+ }
251
+ // Unbound: the verdict, never a diff.
252
+ const verdict = j ?? { verdict: "not-observed" as const, reason: "not-reached" as const, detail: "the join gave this row no verdict" };
253
+ if (verdict.verdict === "bound") {
254
+ // Bound to a node the index does not hold: the document disagrees with itself.
255
+ records.push(
256
+ driftRecord({
257
+ member: input.member,
258
+ engine: "chant",
259
+ address: address(name),
260
+ ...at,
261
+ resourceType: entity.entityType,
262
+ observedAt,
263
+ read: "live-read",
264
+ status: INDEX_NOT_OBSERVED,
265
+ unobserved: { reason: "read-failed", detail: `the index binds ${name} to ${verdict.node}, which its graph does not hold` },
266
+ evidence: evidence("bound"),
267
+ }),
268
+ );
269
+ counts.notObserved++;
270
+ continue;
271
+ }
272
+ const s = unboundStatus(doc, verdict);
273
+ if (s.status === "pending") counts.pending++;
274
+ else counts.notObserved++;
275
+ records.push(
276
+ driftRecord({
277
+ member: input.member,
278
+ engine: "chant",
279
+ address: address(name),
280
+ ...at,
281
+ resourceType: entity.entityType,
282
+ observedAt,
283
+ read: "live-read",
284
+ status: s.status,
285
+ ...(s.unobserved ? { unobserved: s.unobserved } : {}),
286
+ evidence: evidence(verdict.verdict),
287
+ }),
288
+ );
289
+ }
290
+ counts.properties += countPropertyDrift(deep);
291
+ counts.drifted += deep.drifted.length;
292
+ counts.accepted += deep.accepted.reduce((n, e) => n + e.changes.length, 0);
293
+ }
294
+
295
+ records.sort((a, b) => (a.address < b.address ? -1 : a.address > b.address ? 1 : 0));
296
+ return { ref, observedAt, byLexicon, records, counts };
297
+ }
298
+
299
+ /** The records as one drift document. */
300
+ export function indexDriftDocument(results: Iterable<IndexDriftResult>, chant?: string): DriftDocument {
301
+ const records: DriftRecord[] = [];
302
+ for (const r of results) records.push(...r.records);
303
+ return driftDocument(records, chant ? { chant } : {});
304
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * #4524: the pure part of `chant import --index` over a hand-made index,
3
+ * with a fake lexicon seam, so every rule is checked without a generated
4
+ * lexicon: selection, references from derived edges, secrets as parameters,
5
+ * declared nodes left out, unsupported nodes named, groups and determinism.
6
+ * The aws seam and its round trip through the generator and a build are in
7
+ * lexicons/aws/src/index-import.test.ts.
8
+ */
9
+ import { describe, expect, test } from "vitest";
10
+ import { MASKED } from "../deep-observation";
11
+ import type { IREdge, IRNode } from "../graph-ir";
12
+ import type { IndexIdentity } from "./identity-match";
13
+ import { indexDocumentDigest, indexScopeKey, type CloudIndexScope, type IndexDocument } from "./index-document";
14
+ import {
15
+ declaredNodes,
16
+ edgeRulePath,
17
+ indexImportHeader,
18
+ nodeSelected,
19
+ planIndexImport,
20
+ selectionIsEmpty,
21
+ type IndexImportCodegen,
22
+ type IndexImportLexicon,
23
+ } from "./index-import";
24
+
25
+ const SCOPE: CloudIndexScope = { kind: "cloud", substrate: "aws", scopeId: "111", locations: ["r1", "r2"], types: "*" };
26
+
27
+ function node(kind: string, location: string, nativeId: string, attrs: Record<string, unknown>, extra: Partial<IRNode> = {}): IRNode {
28
+ return {
29
+ id: `aws/111/${location}/${kind}/${nativeId}`,
30
+ kind,
31
+ lexicon: "fake",
32
+ attrs,
33
+ listedType: kind,
34
+ location,
35
+ nativeId,
36
+ physicalId: nativeId,
37
+ tags: {},
38
+ facts: { cloudIds: [{ kind: "arn", value: `arn:${kind}:${nativeId}` }], observedAt: "2026-10-26T00:00:00.000Z" },
39
+ ...extra,
40
+ };
41
+ }
42
+
43
+ function edge(from: IRNode, to: IRNode, path: string, extra: Partial<IREdge> = {}): IREdge {
44
+ return { from: from.id, to: to.id, kind: "ref", origin: "derived", grain: "property", rule: `fake:${from.kind}:${path}`, viaAttr: path, ...extra };
45
+ }
46
+
47
+ function docOf(nodes: IRNode[], edges: IREdge[]): IndexDocument {
48
+ const graph = { version: 1 as const, nodes, edges, groups: {} };
49
+ return {
50
+ index: "v1",
51
+ stamp: {
52
+ source: { kind: "cloud", name: "behold-lister" },
53
+ scope: SCOPE,
54
+ scopeKey: indexScopeKey(SCOPE),
55
+ observedAt: "2026-10-26T00:00:00.000Z",
56
+ digest: indexDocumentDigest({ graph, coverage: [] }),
57
+ },
58
+ coverage: [{ listedType: "*", location: "*", verdict: "listed" }],
59
+ graph,
60
+ };
61
+ }
62
+
63
+ const fake: IndexImportCodegen = {
64
+ resource: (n) => (n.kind === "Fake::Refused" ? { unsupported: "refused by the fake lexicon" } : { type: n.kind, properties: n.attrs }),
65
+ reference: (value, target) =>
66
+ value === target.node.nativeId ? { ref: target.logicalId } : value === `arn:${target.node.kind}:${target.node.nativeId}` ? { arn: target.logicalId } : undefined,
67
+ secretParameter: (name, ctx) => ({ name, type: "String", description: `${ctx.path} of ${ctx.node.id}` }),
68
+ parameterReference: (name) => ({ param: name }),
69
+ };
70
+ const LEX: IndexImportLexicon[] = [{ name: "fake", indexImport: fake }];
71
+
72
+ const net = node("Fake::Net", "r1", "net-1", { Cidr: "10.0.0.0/16" });
73
+ const sub = node("Fake::Sub", "r1", "sub-1", { NetId: "net-1", Cidr: "10.0.1.0/24" }, { tags: { team: "a" } });
74
+ const vm = node("Fake::Vm", "r1", "vm-1", {
75
+ SubIds: ["sub-1", "sub-elsewhere"],
76
+ Role: "arn:Fake::Role:role-1",
77
+ Password: MASKED,
78
+ Disks: [{ Key: MASKED }],
79
+ }, { tags: { team: "a" } });
80
+ const role = node("Fake::Role", "global", "role-1", { Name: "role-1" });
81
+ const far = node("Fake::Net", "r2", "net-2", { Cidr: "10.1.0.0/16" });
82
+ const odd = node("Unmapped::Thing", "r1", "t-1", {}, { unmappedKind: true, lexicon: "aws" });
83
+ const refused = node("Fake::Refused", "r1", "x-1", {});
84
+ const other = { ...node("Other::Kind", "r1", "o-1", {}), lexicon: "other" };
85
+
86
+ const EDGES = [
87
+ edge(sub, net, "NetId", { relation: "containment" }),
88
+ edge(vm, sub, "SubIds[]"),
89
+ { ...edge(vm, role, "Role"), rule: "identity-match:fake:Fake::Vm:arn" },
90
+ edge(far, net, "Peer"),
91
+ ];
92
+ const DOC = docOf([net, sub, vm, role, far, odd, refused, other], EDGES);
93
+
94
+ describe("selection", () => {
95
+ test("an empty selection selects nothing", () => {
96
+ expect(selectionIsEmpty({})).toBe(true);
97
+ expect(nodeSelected(net, {})).toBe(false);
98
+ expect(planIndexImport([DOC], {}, LEX).report.resources).toEqual([]);
99
+ });
100
+
101
+ test("types, names, nodes and tags narrow; within one field any value matches", () => {
102
+ expect(nodeSelected(net, { types: ["Fake::Net", "Fake::Sub"] })).toBe(true);
103
+ expect(nodeSelected(vm, { types: ["Fake::Net"] })).toBe(false);
104
+ expect(nodeSelected(sub, { names: ["sub-1"] })).toBe(true);
105
+ expect(nodeSelected(sub, { nodes: [sub.id] })).toBe(true);
106
+ expect(nodeSelected(sub, { tags: { team: "a" } })).toBe(true);
107
+ expect(nodeSelected(net, { tags: { team: "a" } })).toBe(false);
108
+ expect(nodeSelected(vm, { types: ["Fake::Vm"], tags: { team: "b" } })).toBe(false);
109
+ });
110
+ });
111
+
112
+ describe("planIndexImport", () => {
113
+ const plan = planIndexImport([DOC], { all: true }, LEX);
114
+ const report = (n: IRNode) => plan.report.resources.find((r) => r.node === n.id)!;
115
+
116
+ test("a derived edge to an imported node in the same group becomes a reference", () => {
117
+ const g = plan.groups.find((x) => x.location === "r1")!;
118
+ const subIr = g.ir.resources.find((r) => r.type === "Fake::Sub")!;
119
+ expect(subIr.properties.NetId).toEqual({ ref: report(net).logicalId });
120
+ expect(report(sub).references).toEqual([{ path: "NetId", target: net.id, rule: "fake:Fake::Sub:NetId", written: "reference" }]);
121
+ });
122
+
123
+ test("an array path rewrites only the values that name the target", () => {
124
+ const g = plan.groups.find((x) => x.location === "r1")!;
125
+ const vmIr = g.ir.resources.find((r) => r.type === "Fake::Vm")!;
126
+ expect(vmIr.properties.SubIds).toEqual([{ ref: report(sub).logicalId }, "sub-elsewhere"]);
127
+ });
128
+
129
+ test("an identity-match edge, which names no path, is found by value", () => {
130
+ const one = planIndexImport([DOC], { types: ["Fake::Vm", "Fake::Role", "Fake::Sub"] }, LEX);
131
+ const vmIr = one.groups[0].ir.resources.find((r) => r.type === "Fake::Vm")!;
132
+ expect(vmIr.properties.Role).toEqual({ arn: one.report.resources.find((r) => r.node === role.id)!.logicalId });
133
+ expect(one.report.resources.find((r) => r.node === vm.id)!.references).toContainEqual({
134
+ path: "Role",
135
+ target: role.id,
136
+ rule: "identity-match:fake:Fake::Vm:arn",
137
+ written: "reference",
138
+ });
139
+ });
140
+
141
+ test("a masked value becomes a parameter and is never written", () => {
142
+ const vmIr = plan.groups.flatMap((g) => g.ir.resources).find((r) => r.type === "Fake::Vm")!;
143
+ expect(JSON.stringify(plan.groups.map((g) => g.ir))).not.toContain(MASKED);
144
+ const lid = report(vm).logicalId!;
145
+ expect(vmIr.properties.Password).toEqual({ param: `${lid}Password` });
146
+ expect((vmIr.properties.Disks as Array<Record<string, unknown>>)[0].Key).toEqual({ param: `${lid}Disks0Key` });
147
+ expect(plan.report.parameters.map((p) => p.path)).toEqual(["Disks[0].Key", "Password"]);
148
+ const params = plan.groups.flatMap((g) => g.ir.parameters).map((p) => p.name);
149
+ expect(params).toEqual([`${lid}Disks0Key`, `${lid}Password`]);
150
+ const g = plan.groups.find((x) => x.ir.resources.some((r) => r.type === "Fake::Vm"))!;
151
+ expect(indexImportHeader(plan, g)).toContain(`fill in the parameter ${lid}Password`);
152
+ });
153
+
154
+ test("an edge into another group stays a literal, and the header names the edge", () => {
155
+ const g2 = plan.groups.find((x) => x.location === "r2")!;
156
+ const farIr = g2.ir.resources[0];
157
+ expect(farIr.properties).toEqual({ Cidr: "10.1.0.0/16" });
158
+ expect(report(far).references?.[0]).toMatchObject({ path: "Peer", written: "literal" });
159
+ expect(report(far).references?.[0].reason).toMatch(/another group/);
160
+ expect(g2.header.join("\n")).toContain(`derived edge fake:Fake::Net:Peer to ${net.id}`);
161
+ });
162
+
163
+ test("two regions give two groups, and a global node with two regions to pick from gets its own", () => {
164
+ expect(plan.groups.map((g) => g.dir)).toEqual(["fake/global", "fake/r1", "fake/r2"]);
165
+ });
166
+
167
+ test("a global node joins the only region", () => {
168
+ const one = planIndexImport([DOC], { types: ["Fake::Vm", "Fake::Role"] }, LEX);
169
+ expect(one.groups.map((g) => g.dir)).toEqual([""]);
170
+ const vmIr = one.groups[0].ir.resources.find((r) => r.type === "Fake::Vm")!;
171
+ expect(vmIr.properties.Role).toEqual({ arn: one.report.resources.find((r) => r.node === role.id)!.logicalId });
172
+ });
173
+
174
+ test("unmapped kinds, a lexicon with no seam and a refused node are named as unsupported", () => {
175
+ expect(report(odd)).toMatchObject({ status: "unsupported" });
176
+ expect(report(odd).reason).toMatch(/no lexicon kind maps/);
177
+ expect(report(other).reason).toBe("the other lexicon is not loaded");
178
+ const withOther = planIndexImport([DOC], { all: true }, [...LEX, { name: "other" }]);
179
+ expect(withOther.report.resources.find((r) => r.node === other.id)!.reason).toMatch(/no index import codegen/);
180
+ expect(report(refused).reason).toBe("refused by the fake lexicon");
181
+ });
182
+
183
+ test("the report names each resource's identity and cloud ids", () => {
184
+ expect(report(vm).identity).toEqual({ substrate: "aws", scopeId: "111", listedType: "Fake::Vm", location: "r1", nativeId: "vm-1" });
185
+ expect(report(vm).cloudIds).toEqual([{ kind: "arn", value: "arn:Fake::Vm:vm-1" }]);
186
+ expect(plan.report.index).toEqual([{ scopeKey: DOC.stamp.scopeKey, observedAt: DOC.stamp.observedAt, digest: DOC.stamp.digest, source: "behold-lister" }]);
187
+ });
188
+
189
+ test("a declared node is reported with its entity and not imported; an edge to it stays a literal", () => {
190
+ const declared = new Map([[net.id, "network"]]);
191
+ const p = planIndexImport([DOC], { types: ["Fake::Net", "Fake::Sub"] }, LEX, { declared });
192
+ const r = p.report.resources.find((x) => x.node === net.id)!;
193
+ expect(r).toMatchObject({ status: "declared", entity: "network" });
194
+ // The other Fake::Net (net-2, in r2) is selected by type and not declared, so it is imported.
195
+ const imported = p.report.resources.filter((x) => x.status === "imported").map((x) => x.node);
196
+ expect(imported).not.toContain(net.id);
197
+ expect(imported).toEqual([far.id, sub.id].sort());
198
+ expect(p.groups.flatMap((g) => g.ir.resources).filter((x) => x.type === "Fake::Net")).toHaveLength(1);
199
+ const s = p.report.resources.find((x) => x.node === sub.id)!;
200
+ expect(s.references?.[0]).toMatchObject({ written: "literal", reason: "the target is already declared as network" });
201
+ });
202
+
203
+ test("the same index and selection give the same plan, whatever the node and edge order", () => {
204
+ // The same index version: same stamp, its nodes and edges listed in another order.
205
+ const shuffled = { ...docOf([other, refused, far, role, odd, vm, net, sub], [...EDGES].reverse()), stamp: DOC.stamp };
206
+ expect(JSON.stringify(planIndexImport([shuffled], { all: true }, LEX))).toBe(JSON.stringify(plan));
207
+ expect(JSON.stringify(planIndexImport([DOC], { all: true }, LEX))).toBe(JSON.stringify(plan));
208
+ });
209
+
210
+ test("two reads are refused", () => {
211
+ const later = { ...DOC, stamp: { ...DOC.stamp, observedAt: "2026-10-27T00:00:00.000Z" } };
212
+ expect(() => planIndexImport([DOC, later], { all: true }, LEX)).toThrow(/one read of one account/);
213
+ });
214
+
215
+ test("logical ids are letters and digits, stable, and drop a repeated type word", () => {
216
+ const ids = plan.report.resources.filter((r) => r.logicalId).map((r) => r.logicalId!);
217
+ for (const id of ids) expect(id).toMatch(/^[A-Za-z][A-Za-z0-9]*$/);
218
+ expect(new Set(ids).size).toBe(ids.length);
219
+ expect(report(net).logicalId).toBe("Net1");
220
+ expect(report(sub).logicalId).toBe("Sub1");
221
+ });
222
+ });
223
+
224
+ describe("edgeRulePath", () => {
225
+ test("a catalog rule names its path; tag and identity-match rules name none", () => {
226
+ expect(edgeRulePath(EDGES[0], sub)).toBe("NetId");
227
+ expect(edgeRulePath({ ...EDGES[0], rule: "fake:Fake::Sub:tag:owner" }, sub)).toBeUndefined();
228
+ expect(edgeRulePath(EDGES[2], vm)).toBeUndefined();
229
+ });
230
+ });
231
+
232
+ describe("declaredNodes", () => {
233
+ const identity: IndexIdentity = {
234
+ substrate: "aws",
235
+ declaredIdentity: (type, props) =>
236
+ typeof props.Name === "string"
237
+ ? { kind: "identifier", listedType: type, identifier: props.Name, global: type === "Fake::Role" }
238
+ : { kind: "unmatchable", reason: "no-binding", detail: "no name" },
239
+ };
240
+
241
+ test("an entity the matcher binds marks its node as declared", () => {
242
+ const entities = new Map([["fake", [
243
+ { name: "network", entityType: "Fake::Net", props: { Name: "net-1" } },
244
+ { name: "role", entityType: "Fake::Role", props: { Name: "role-1" } },
245
+ { name: "nameless", entityType: "Fake::Sub", props: {} },
246
+ ]]]);
247
+ const declared = declaredNodes([DOC], entities, [{ name: "fake", indexIdentity: identity }]);
248
+ expect([...declared.entries()].sort()).toEqual([[net.id, "network"], [role.id, "role"]].sort());
249
+ });
250
+
251
+ test("a lexicon with no identity rule declares nothing", () => {
252
+ const entities = new Map([["fake", [{ name: "network", entityType: "Fake::Net", props: { Name: "net-1" } }]]]);
253
+ expect(declaredNodes([DOC], entities, [{ name: "fake" }]).size).toBe(0);
254
+ });
255
+ });