@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,250 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { GraphIR, IRNode } from "../graph-ir";
3
+ import type { DeclaredIdentity, IndexIdentity } from "./identity-match";
4
+ import type { IndexDocument } from "./index-document";
5
+ import {
6
+ countJoins,
7
+ formatIndexRef,
8
+ indexOnlyGraph,
9
+ joinCell,
10
+ joinDeclaredToIndex,
11
+ parseIndexRef,
12
+ readIndexRef,
13
+ type JoinLexicon,
14
+ } from "./index-join";
15
+ import { fakeSnapshotStore } from "./snapshot-store-fake";
16
+ import { typeListing, type ListedResource, type ListingLexicon, type ListingResult } from "./type-listing";
17
+
18
+ // #4525: `chant search --index <ref>`, the ref and the join, over a fixture
19
+ // index typed the way behold#1026 writes one (#4521's typeListing).
20
+
21
+ const IDENTITY: Record<string, { parts?: string[]; serverAssigned?: boolean; global?: boolean }> = {
22
+ "Store::Bucket": { parts: ["BucketName"] },
23
+ "Iam::Role": { parts: ["RoleName"], global: true },
24
+ "Queue::Queue": { parts: ["QueueUrl"], serverAssigned: true },
25
+ "Kv::Table": { parts: ["TableName"] },
26
+ };
27
+
28
+ const identity: IndexIdentity = {
29
+ substrate: "aws",
30
+ declaredIdentity(entityType, props): DeclaredIdentity {
31
+ const row = IDENTITY[entityType];
32
+ if (!row?.parts) return { kind: "unmatchable", reason: "unsupported-kind", detail: `${entityType} has no primary identifier on record` };
33
+ if (row.serverAssigned) return { kind: "unmatchable", reason: "no-binding", detail: `${entityType} is assigned by the service` };
34
+ const values: string[] = [];
35
+ for (const p of row.parts) {
36
+ const v = props[p];
37
+ if (typeof v !== "string" || v === "") return { kind: "unmatchable", reason: "no-binding", detail: "identity is not in the declaration" };
38
+ values.push(v);
39
+ }
40
+ return { kind: "identifier", listedType: entityType, identifier: values.join("|"), global: row.global === true };
41
+ },
42
+ };
43
+
44
+ const typing: ListingLexicon[] = [{ name: "fake", listedTypes: { substrate: "aws", kindFor: (t) => (t in IDENTITY ? t : undefined) } }];
45
+ const lexicons: JoinLexicon[] = [{ name: "fake", indexIdentity: identity }, { name: "other" }];
46
+
47
+ const r = (listedType: string, location: string, nativeId: string, properties: Record<string, unknown> = {}, tags: Record<string, string> = {}): ListedResource => ({
48
+ listedType,
49
+ location,
50
+ nativeId,
51
+ cloudIds: [{ kind: "import-id", value: nativeId }],
52
+ tags,
53
+ properties,
54
+ observedAt: "2026-10-26T09:00:01.000Z",
55
+ });
56
+
57
+ function listing(resources: ListedResource[]): ListingResult {
58
+ return {
59
+ listing: "v1",
60
+ source: { name: "behold-lister", version: "0.0.0-test" },
61
+ scope: { substrate: "aws", scopeId: "000000000000", locations: ["us-east-1"], types: ["Store::Bucket", "Iam::Role", "Queue::Queue"] },
62
+ startedAt: "2026-10-26T09:00:00.000Z",
63
+ completedAt: "2026-10-26T09:00:04.000Z",
64
+ resources,
65
+ coverage: [
66
+ { listedType: "Store::Bucket", location: "us-east-1", verdict: "listed" },
67
+ { listedType: "Iam::Role", location: "global", verdict: "listed" },
68
+ { listedType: "Queue::Queue", location: "us-east-1", verdict: "listed" },
69
+ ],
70
+ notes: [],
71
+ };
72
+ }
73
+
74
+ const doc = typeListing(
75
+ listing([
76
+ r("Store::Bucket", "us-east-1", "shop-assets", { BucketName: "shop-assets", Arn: "arn:aws:s3:::shop-assets" }, { team: "web" }),
77
+ r("Store::Bucket", "us-east-1", "made-by-hand", { BucketName: "made-by-hand" }),
78
+ r("Iam::Role", "global", "app-runner", { RoleName: "app-runner" }),
79
+ r("Queue::Queue", "us-east-1", "https://sqs.us-east-1.amazonaws.com/000000000000/orders", { QueueName: "orders" }),
80
+ ]),
81
+ typing,
82
+ );
83
+ const nodeId = (location: string, type: string, nativeId: string) => `aws/000000000000/${location}/${type}/${nativeId}`;
84
+
85
+ const node = (id: string, kind: string, attrs: Record<string, unknown>, lexicon = "fake"): IRNode => ({ id, kind, lexicon, attrs });
86
+ function graph(nodes: IRNode[]): GraphIR {
87
+ return { version: 1, nodes, edges: [], groups: { byLexicon: {} } };
88
+ }
89
+
90
+ describe("parseIndexRef", () => {
91
+ const key = doc.stamp.scopeKey;
92
+
93
+ test("a scope key alone is its latest version", () => {
94
+ expect(parseIndexRef(key)).toEqual({ scopeKey: key, version: "latest" });
95
+ expect(parseIndexRef(`${key}@latest`)).toEqual({ scopeKey: key, version: "latest" });
96
+ });
97
+
98
+ test("a version is taken with or without .json, after @ or as the last path segment", () => {
99
+ expect(parseIndexRef(`${key}@20261026T090000Z-0123456789ab`)).toEqual({ scopeKey: key, version: "20261026T090000Z-0123456789ab.json" });
100
+ expect(parseIndexRef(`${key}@20261026T090000Z-0123456789ab.json`)).toEqual({ scopeKey: key, version: "20261026T090000Z-0123456789ab.json" });
101
+ expect(parseIndexRef(`${key}/20261026T090000Z-0123456789ab.json`)).toEqual({ scopeKey: key, version: "20261026T090000Z-0123456789ab.json" });
102
+ });
103
+
104
+ test("a ref that names no source, no version or a dotted path is refused with the shape", () => {
105
+ for (const bad of ["", "aws/000000000000", `${key}@yesterday`, "cloud/aws/../x", "cloud//aws"]) {
106
+ const parsed = parseIndexRef(bad);
107
+ expect(parsed).toMatchObject({ error: expect.any(String), hint: expect.stringContaining("scope key") });
108
+ }
109
+ });
110
+
111
+ test("formatIndexRef drops .json", () => {
112
+ expect(formatIndexRef("cloud/aws/a/b", "20261026T090000Z-0123456789ab.json")).toBe("cloud/aws/a/b@20261026T090000Z-0123456789ab");
113
+ });
114
+ });
115
+
116
+ describe("readIndexRef", () => {
117
+ const key = doc.stamp.scopeKey;
118
+ const store = fakeSnapshotStore({
119
+ readIndexLatest: async (k) => (k === key ? doc : null),
120
+ readIndexAt: async (k, v) => (k === key && v.startsWith("2026") ? doc : null),
121
+ });
122
+
123
+ test("latest reads the newest version and names it", async () => {
124
+ const read = await readIndexRef(store, { scopeKey: key, version: "latest" });
125
+ expect("doc" in read && read.version).toMatch(/^20261026T090000Z-[0-9a-f]{12}\.json$/);
126
+ });
127
+
128
+ test("a scope key with nothing under it is refused, naming the key and the store", async () => {
129
+ const read = await readIndexRef(store, { scopeKey: "cloud/aws/111111111111/0123456789ab", version: "latest" });
130
+ expect(read).toMatchObject({ error: expect.stringContaining("cloud/aws/111111111111/0123456789ab") });
131
+ expect("error" in read && read.error).toContain("the test store");
132
+ });
133
+
134
+ test("a version stamped for another version, or a document of another scope, is refused", async () => {
135
+ const wrongVersion = await readIndexRef(store, { scopeKey: key, version: "20261026T090000Z-ffffffffffff.json" });
136
+ expect(wrongVersion).toMatchObject({ error: expect.stringContaining("not the version asked for") });
137
+ const elsewhere = fakeSnapshotStore({ readIndexLatest: async () => doc });
138
+ const other = await readIndexRef(elsewhere, { scopeKey: "cloud/aws/111111111111/0123456789ab", version: "latest" });
139
+ expect(other).toMatchObject({ error: expect.stringContaining("not the scope asked for") });
140
+ });
141
+
142
+ test("a store that throws is a refusal, not a crash", async () => {
143
+ const broken = fakeSnapshotStore({ readIndexLatest: async () => { throw new Error("boom"); } });
144
+ expect(await readIndexRef(broken, { scopeKey: key, version: "latest" })).toMatchObject({ error: expect.stringContaining("boom") });
145
+ });
146
+ });
147
+
148
+ describe("joinDeclaredToIndex", () => {
149
+ const declared = graph([
150
+ node("east::assets", "Store::Bucket", { BucketName: "shop-assets" }),
151
+ node("east::runner", "Iam::Role", { RoleName: "app-runner" }),
152
+ node("east::archive", "Store::Bucket", { BucketName: "never-made" }),
153
+ node("east::orders", "Queue::Queue", { QueueName: "orders" }),
154
+ node("east::carts", "Kv::Table", { TableName: "carts" }),
155
+ node("east::byRef", "Store::Bucket", { BucketName: { $ref: "name" } }),
156
+ node("east::web", "Other::Thing", { Name: "web" }, "other"),
157
+ node("loose", "Store::Bucket", { BucketName: "never-made" }),
158
+ ]);
159
+ const regionOf = (id: string) => (id.startsWith("east::") ? "us-east-1" : undefined);
160
+ const { ir, joins } = joinDeclaredToIndex(declared, doc, lexicons, { regionOf });
161
+ const row = (id: string) => ir.nodes.find((n) => n.id === id)!;
162
+
163
+ test("every declared row has a verdict, and only declared rows are in the graph", () => {
164
+ expect(Object.keys(joins).sort()).toEqual(declared.nodes.map((n) => n.id).sort());
165
+ expect(ir.nodes.map((n) => n.id).sort()).toEqual(declared.nodes.map((n) => n.id).sort());
166
+ // made-by-hand is indexed, and no declaration binds it.
167
+ expect(ir.nodes.some((n) => n.id.includes("made-by-hand"))).toBe(false);
168
+ });
169
+
170
+ test("a bound row carries the indexed node's id, native id and observed attributes", () => {
171
+ expect(joins["east::assets"]).toMatchObject({ verdict: "bound", node: nodeId("us-east-1", "Store::Bucket", "shop-assets"), nativeId: "shop-assets" });
172
+ expect(row("east::assets").physicalId).toBe("shop-assets");
173
+ expect(row("east::assets").attrs).toMatchObject({ Arn: "arn:aws:s3:::shop-assets", _status: "good" });
174
+ expect(joins["east::runner"]).toMatchObject({ verdict: "bound", location: "global" });
175
+ });
176
+
177
+ test("absent only where the index listed the type and location", () => {
178
+ expect(joins["east::archive"]).toMatchObject({ verdict: "absent", listedType: "Store::Bucket", identifier: "never-made", location: "us-east-1" });
179
+ expect(row("east::archive").physicalId).toBeUndefined();
180
+ expect(row("east::archive").attrs?._status).toBe("accent");
181
+ });
182
+
183
+ test("a type outside the index's scope is not observed, with the coverage's reason", () => {
184
+ expect(joins["east::carts"]).toMatchObject({ verdict: "not-observed", reason: "filtered" });
185
+ expect(row("east::carts").attrs).toMatchObject({ _status: "neutral", _unobserved: "filtered" });
186
+ });
187
+
188
+ test("an identity the declaration does not spell is no-binding, never absent", () => {
189
+ expect(joins["east::orders"]).toMatchObject({ verdict: "no-binding", detail: expect.stringContaining("assigned by the service") });
190
+ expect(joins["east::byRef"]).toMatchObject({ verdict: "no-binding", detail: "identity is not in the declaration" });
191
+ expect(row("east::orders").attrs?._unobserved).toBe("no-binding");
192
+ });
193
+
194
+ test("an entity with no region that matches nothing is no-binding, not absent", () => {
195
+ expect(joins.loose).toMatchObject({ verdict: "no-binding", detail: expect.stringContaining("names no region") });
196
+ });
197
+
198
+ test("a lexicon with no index match is not observed, unsupported-kind", () => {
199
+ expect(joins["east::web"]).toMatchObject({ verdict: "not-observed", reason: "unsupported-kind", detail: expect.stringContaining("other lexicon has no index match") });
200
+ });
201
+
202
+ test("two indexed nodes with one identity are ambiguous, bind neither and are painted unknown", () => {
203
+ const twice = typeListing(
204
+ {
205
+ ...listing([r("Store::Bucket", "us-east-1", "shop-assets", { BucketName: "shop-assets" }), r("Store::Bucket", "us-west-2", "shop-assets", { BucketName: "shop-assets" })]),
206
+ scope: { substrate: "aws", scopeId: "000000000000", locations: ["us-east-1", "us-west-2"], types: ["Store::Bucket"] },
207
+ },
208
+ typing,
209
+ );
210
+ const { ir: g, joins: j } = joinDeclaredToIndex(graph([node("assets", "Store::Bucket", { BucketName: "shop-assets" })]), twice, lexicons);
211
+ expect(j.assets).toMatchObject({ verdict: "ambiguous", candidates: [nodeId("us-east-1", "Store::Bucket", "shop-assets"), nodeId("us-west-2", "Store::Bucket", "shop-assets")] });
212
+ const n = g.nodes.find((x) => x.id === "assets")!;
213
+ expect(n.physicalId).toBeUndefined();
214
+ expect(n.attrs?._status).toBe("neutral");
215
+ expect(n.attrs?._unobserved).toBeUndefined();
216
+ expect(joinCell(j.assets)).toBe("(ambiguous: 2 candidates)");
217
+ });
218
+
219
+ test("an index of another substrate, or not a cloud read, joins nothing", () => {
220
+ const azure: JoinLexicon[] = [{ name: "fake", indexIdentity: { ...identity, substrate: "azure" } }];
221
+ const { joins: j } = joinDeclaredToIndex(declared, doc, azure, { regionOf });
222
+ expect(j["east::assets"]).toMatchObject({ verdict: "not-observed", reason: "filtered", detail: expect.stringContaining("reads aws") });
223
+ const engine: IndexDocument = { ...doc, stamp: { ...doc.stamp, source: { kind: "engine", name: "plunge" }, scope: { kind: "engine", engine: "plunge", project: "p", stack: "s" } } };
224
+ const { joins: e } = joinDeclaredToIndex(declared, engine, lexicons, { regionOf });
225
+ expect(e["east::assets"]).toMatchObject({ verdict: "not-observed", reason: "filtered", detail: expect.stringContaining("engine source") });
226
+ });
227
+
228
+ test("the verdicts count per row, and each prints its own cell", () => {
229
+ const counts = countJoins(joins, Object.keys(joins));
230
+ expect(counts).toEqual({ bound: 2, absent: 1, "not-observed": 2, "no-binding": 3, ambiguous: 0 });
231
+ expect(joinCell(joins["east::assets"])).toBeUndefined();
232
+ expect(joinCell(joins["east::archive"])).toBe("(absent)");
233
+ expect(joinCell(joins["east::carts"])).toBe("(not observed: filtered)");
234
+ expect(joinCell(joins["east::orders"])).toBe("(no-binding)");
235
+ });
236
+
237
+ test("the join does not depend on node order", () => {
238
+ const reversed = joinDeclaredToIndex({ ...declared, nodes: [...declared.nodes].reverse() }, { ...doc, graph: { ...doc.graph, nodes: [...doc.graph.nodes].reverse() } }, lexicons, { regionOf });
239
+ expect(reversed.joins).toEqual(joins);
240
+ expect(reversed.ir.nodes).toEqual(ir.nodes);
241
+ });
242
+ });
243
+
244
+ describe("indexOnlyGraph", () => {
245
+ test("every indexed node is a row, with its native id as its physical id", () => {
246
+ const g = indexOnlyGraph(doc);
247
+ expect(g.nodes.map((n) => n.id)).toEqual(doc.graph.nodes.map((n) => n.id));
248
+ for (const n of g.nodes) expect(n.physicalId).toBe(n.nativeId);
249
+ });
250
+ });
@@ -0,0 +1,304 @@
1
+ /**
2
+ * `chant search --index <ref>` (#4525): an index version as the observed side
3
+ * of a search, joined to the declared rows.
4
+ *
5
+ * Two halves, both pure apart from {@link readIndexRef}'s store read:
6
+ *
7
+ * - a ref names one index version in the store (#4519, ./index-document.ts):
8
+ * `<scope key>`, `<scope key>@latest`, `<scope key>@<version>` or
9
+ * `<scope key>/<version>.json`, where the scope key is `stamp.scopeKey`
10
+ * (`cloud/aws/<account>/<scope digest>`) and a version is a name the store
11
+ * lists (`20261026T091403Z-<digest12>`, with or without `.json`);
12
+ * - {@link joinDeclaredToIndex} gives every declared node a join verdict
13
+ * and lays the bound indexed nodes over the declared canvas, the way
14
+ * `--live` lays a live read over it (`sourceOverlayGraphs`).
15
+ *
16
+ * The join goes through each lexicon's own index match, its plugin's
17
+ * `indexIdentity` (#4523, ./identity-match.ts). aws has one; a lexicon with
18
+ * none gets `not-observed`, reason `unsupported-kind`, on every row, never a
19
+ * guessed match. The verdicts:
20
+ *
21
+ * - `bound`: one indexed node carries the identity the declaration spells;
22
+ * - `absent`: none does, and the index's coverage says it listed that type
23
+ * and location;
24
+ * - `not-observed`: the index did not look (coverage `filtered`,
25
+ * `not-reached`, a read failure), the lexicon has no index match, or the
26
+ * index is of another substrate. Always with the reason and a detail;
27
+ * - `no-binding`: the declaration does not spell an identity (a
28
+ * server-assigned id, a Ref), or the entity names no region and nothing
29
+ * matched;
30
+ * - `ambiguous`: two or more indexed nodes carry the identity. None is bound.
31
+ *
32
+ * Indexed nodes no declared row binds stay out of a project's answer, as an
33
+ * undeclared resource stays out of a `--live` one; with no project the index
34
+ * is the whole graph ({@link indexOnlyGraph}).
35
+ */
36
+ import { sourceOverlayGraphs, type GraphIR, type IRNode } from "../graph-ir";
37
+ import type { UnobservedEntity, UnobservedReason } from "../observation";
38
+ import { matchDeclaredToIndex, type DeclaredEntity, type IdentityMatch, type IndexIdentity } from "./identity-match";
39
+ import { indexVersionName, isIndexVersionName, validateIndexDocument, type IndexDocument } from "./index-document";
40
+ import type { SnapshotStore } from "./snapshot-store";
41
+
42
+ /** One index version, as a ref names it. */
43
+ export interface IndexRef {
44
+ /** `stamp.scopeKey`: `<source path>/<scope path>`. */
45
+ scopeKey: string;
46
+ /** `latest`, or a version name with its `.json`. */
47
+ version: string;
48
+ }
49
+
50
+ const SOURCE_PREFIXES = ["cloud/", "engine/", "chant/"];
51
+ const VERSION_ID = /^\d{8}T\d{6}Z-[0-9a-f]{12}(\.json)?$/;
52
+
53
+ const REF_HINT =
54
+ "name an index version as <scope key>[@latest|@<version>], where the scope key is the index's stamp.scopeKey (cloud/aws/<account>/<scope digest>) and a version is a name the store lists, such as 20261026T091403Z-0123456789ab";
55
+
56
+ /** Parse `--index <ref>`, or say why it is not one. */
57
+ export function parseIndexRef(ref: string): IndexRef | { error: string; hint: string } {
58
+ const text = ref.trim();
59
+ if (text === "") return { error: "--index needs an index ref", hint: REF_HINT };
60
+ let scopeKey = text;
61
+ let version = "latest";
62
+ const at = text.lastIndexOf("@");
63
+ if (at >= 0) {
64
+ scopeKey = text.slice(0, at);
65
+ version = text.slice(at + 1);
66
+ if (version !== "latest" && !VERSION_ID.test(version)) {
67
+ return { error: `--index ${text}: "${version}" is not an index version`, hint: REF_HINT };
68
+ }
69
+ } else {
70
+ const slash = text.lastIndexOf("/");
71
+ const last = text.slice(slash + 1);
72
+ if (slash > 0 && last.endsWith(".json") && VERSION_ID.test(last)) {
73
+ scopeKey = text.slice(0, slash);
74
+ version = last;
75
+ }
76
+ }
77
+ if (version !== "latest" && !version.endsWith(".json")) version = `${version}.json`;
78
+ if (!SOURCE_PREFIXES.some((p) => scopeKey.startsWith(p)) || scopeKey.split("/").some((s) => s === "" || s === "." || s === "..")) {
79
+ return { error: `--index ${text}: "${scopeKey}" is not an index scope key`, hint: REF_HINT };
80
+ }
81
+ return { scopeKey, version };
82
+ }
83
+
84
+ /** The ref as one string, version resolved: `<scope key>@<version name without .json>`. */
85
+ export function formatIndexRef(scopeKey: string, versionName: string): string {
86
+ return `${scopeKey}@${versionName.replace(/\.json$/, "")}`;
87
+ }
88
+
89
+ export type IndexRead = { doc: IndexDocument; ref: IndexRef; version: string } | { error: string; hint?: string };
90
+
91
+ /**
92
+ * Read the version a ref names. A stamp that disagrees with itself or with
93
+ * the key it was read under is refused: the answer would be about another
94
+ * scope than the one asked for.
95
+ */
96
+ export async function readIndexRef(store: SnapshotStore, ref: IndexRef): Promise<IndexRead> {
97
+ let doc: IndexDocument | null;
98
+ try {
99
+ doc = ref.version === "latest" ? await store.readIndexLatest(ref.scopeKey) : await store.readIndexAt(ref.scopeKey, ref.version);
100
+ } catch (err) {
101
+ return { error: `could not read the index ${ref.scopeKey} from ${store.where}: ${err instanceof Error ? err.message : String(err)}` };
102
+ }
103
+ if (!doc) {
104
+ return {
105
+ error:
106
+ ref.version === "latest"
107
+ ? `no index version is recorded under ${ref.scopeKey} in ${store.where}`
108
+ : `no index version ${ref.version.replace(/\.json$/, "")} is recorded under ${ref.scopeKey} in ${store.where}`,
109
+ hint: "a scope key ends in its 12-hex scope digest, as the index's stamp.scopeKey spells it; the store is the project's `snapshots` store, or CHANT_SNAPSHOT_STORE and its companions",
110
+ };
111
+ }
112
+ try {
113
+ validateIndexDocument(doc);
114
+ } catch (err) {
115
+ return { error: `the index at ${ref.scopeKey} is not one chant can read: ${err instanceof Error ? err.message : String(err)}` };
116
+ }
117
+ if (doc.stamp.scopeKey !== ref.scopeKey) {
118
+ return { error: `the index read under ${ref.scopeKey} is stamped ${doc.stamp.scopeKey}, so it is not the scope asked for` };
119
+ }
120
+ const version = indexVersionName(doc.stamp.observedAt, doc.stamp.digest);
121
+ if (ref.version !== "latest" && isIndexVersionName(ref.version) && ref.version !== version) {
122
+ return { error: `the index version ${ref.version} under ${ref.scopeKey} is stamped as ${version}, so it is not the version asked for` };
123
+ }
124
+ return { doc, ref, version };
125
+ }
126
+
127
+ interface JoinBase {
128
+ /** The listed type and identifier the declaration spells, when it spells one. */
129
+ listedType?: string;
130
+ identifier?: string;
131
+ location?: string;
132
+ }
133
+
134
+ /** One declared row's join verdict. */
135
+ export type IndexJoin =
136
+ | (JoinBase & { verdict: "bound"; node: string; nativeId: string })
137
+ | (JoinBase & { verdict: "absent" })
138
+ | (JoinBase & { verdict: "not-observed"; reason: UnobservedReason; detail: string })
139
+ | (JoinBase & { verdict: "no-binding"; detail: string })
140
+ | (JoinBase & { verdict: "ambiguous"; candidates: string[]; detail: string });
141
+
142
+ export type IndexJoinVerdict = IndexJoin["verdict"];
143
+
144
+ /** Every verdict, in the order a summary lists them. */
145
+ export const INDEX_JOIN_VERDICTS: readonly IndexJoinVerdict[] = ["bound", "absent", "not-observed", "no-binding", "ambiguous"];
146
+
147
+ /** What the join needs of a lexicon: its name and its index match. */
148
+ export interface JoinLexicon {
149
+ name: string;
150
+ indexIdentity?: IndexIdentity;
151
+ }
152
+
153
+ export interface JoinOptions {
154
+ /** The region of a declared node, from the stack it belongs to. */
155
+ regionOf?: (id: string) => string | undefined;
156
+ }
157
+
158
+ export interface IndexJoinResult {
159
+ /** The declared canvas with the bound indexed nodes laid over it. */
160
+ ir: GraphIR;
161
+ /** Every declared node's verdict, by node id. */
162
+ joins: Record<string, IndexJoin>;
163
+ }
164
+
165
+ function fromMatch(m: IdentityMatch): IndexJoin {
166
+ const base: JoinBase = {
167
+ ...("listedType" in m && m.listedType !== undefined ? { listedType: m.listedType } : {}),
168
+ ...("identifier" in m && m.identifier !== undefined ? { identifier: m.identifier } : {}),
169
+ ...("location" in m && m.location !== undefined && m.location !== "" ? { location: m.location } : {}),
170
+ };
171
+ switch (m.verdict) {
172
+ case "bound":
173
+ return { ...base, verdict: "bound", node: m.node, nativeId: m.nativeId };
174
+ case "absent":
175
+ return { ...base, verdict: "absent" };
176
+ case "refused":
177
+ return { ...base, verdict: "ambiguous", candidates: m.candidates, detail: m.detail };
178
+ case "not-observed":
179
+ return m.reason === "no-binding"
180
+ ? { ...base, verdict: "no-binding", detail: m.detail }
181
+ : { ...base, verdict: "not-observed", reason: m.reason, detail: m.detail };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Join every declared node to an index version. Pure: the lexicons come in
187
+ * as data. The canvas keeps every declared node and edge; a bound node takes
188
+ * the indexed node's physical id, attributes and facts, as `--live` gives a
189
+ * node its read's.
190
+ */
191
+ export function joinDeclaredToIndex(
192
+ declared: GraphIR,
193
+ doc: IndexDocument,
194
+ lexicons: readonly JoinLexicon[],
195
+ options: JoinOptions = {},
196
+ ): IndexJoinResult {
197
+ const joins: Record<string, IndexJoin> = {};
198
+ const scope = doc.stamp.scope;
199
+ const byLexicon = new Map<string, IRNode[]>();
200
+ for (const n of declared.nodes) (byLexicon.get(n.lexicon) ?? byLexicon.set(n.lexicon, []).get(n.lexicon)!).push(n);
201
+
202
+ for (const [lexicon, nodes] of byLexicon) {
203
+ const identity = lexicons.find((l) => l.name === lexicon)?.indexIdentity;
204
+ if (!identity) {
205
+ for (const n of nodes) {
206
+ joins[n.id] = {
207
+ verdict: "not-observed",
208
+ reason: "unsupported-kind",
209
+ detail: `the ${lexicon} lexicon has no index match, so a declared ${n.kind} is not joined to an indexed resource`,
210
+ };
211
+ }
212
+ continue;
213
+ }
214
+ if (scope.kind !== "cloud") {
215
+ for (const n of nodes) {
216
+ joins[n.id] = {
217
+ verdict: "not-observed",
218
+ reason: "filtered",
219
+ detail: `the index ${doc.stamp.scopeKey} is a ${scope.kind} source (${doc.stamp.source.name}); declared rows join only to a cloud read`,
220
+ };
221
+ }
222
+ continue;
223
+ }
224
+ if (scope.substrate !== identity.substrate) {
225
+ for (const n of nodes) {
226
+ joins[n.id] = {
227
+ verdict: "not-observed",
228
+ reason: "filtered",
229
+ detail: `the index ${doc.stamp.scopeKey} reads ${scope.substrate}, and a ${lexicon} entity lives in ${identity.substrate}`,
230
+ };
231
+ }
232
+ continue;
233
+ }
234
+ const entities: DeclaredEntity[] = nodes.map((n) => {
235
+ const region = options.regionOf?.(n.id);
236
+ return { name: n.id, entityType: n.kind, props: (n.attrs ?? {}) as Record<string, unknown>, ...(region ? { region } : {}) };
237
+ });
238
+ const { matches } = matchDeclaredToIndex(entities, [doc], identity);
239
+ for (const [id, m] of Object.entries(matches)) joins[id] = fromMatch(m);
240
+ }
241
+
242
+ // The bound indexed nodes, renamed to the declared node they bind, are the
243
+ // observed side of the overlay. Nothing else of the index is.
244
+ const indexed = new Map(doc.graph.nodes.map((n) => [n.id, n]));
245
+ const live: IRNode[] = [];
246
+ const unobserved: Record<string, UnobservedEntity> = {};
247
+ for (const n of declared.nodes) {
248
+ const j = joins[n.id];
249
+ if (!j) continue;
250
+ if (j.verdict === "bound") {
251
+ const hit = indexed.get(j.node);
252
+ if (hit) live.push({ ...hit, id: n.id, physicalId: hit.physicalId ?? j.nativeId });
253
+ } else if (j.verdict === "not-observed" || j.verdict === "no-binding") {
254
+ unobserved[n.id] = { type: n.kind, reason: j.verdict === "no-binding" ? "no-binding" : j.reason, detail: j.detail };
255
+ }
256
+ }
257
+ live.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
258
+ let ir = sourceOverlayGraphs(declared, { ...declared, nodes: live, edges: [], exports: [] }, { unobserved });
259
+ // An ambiguous row is neither pending nor unread: the index holds candidates
260
+ // and chant will not pick one. Paint it as unknown, with no `_unobserved`.
261
+ ir = {
262
+ ...ir,
263
+ nodes: ir.nodes.map((n) => (joins[n.id]?.verdict === "ambiguous" ? { ...n, attrs: { ...n.attrs, _status: "neutral" } } : n)),
264
+ };
265
+ return { ir, joins };
266
+ }
267
+
268
+ /**
269
+ * The index alone, for a search with no project: every indexed node, each
270
+ * with its native id as its physical id.
271
+ */
272
+ export function indexOnlyGraph(doc: IndexDocument): GraphIR {
273
+ return {
274
+ ...doc.graph,
275
+ nodes: doc.graph.nodes.map((n) => (n.physicalId === undefined && n.nativeId !== undefined ? { ...n, physicalId: n.nativeId } : n)),
276
+ edges: doc.graph.edges ?? [],
277
+ };
278
+ }
279
+
280
+ /** How many rows carry each verdict, for the provenance line. */
281
+ export function countJoins(joins: Record<string, IndexJoin>, ids: readonly string[]): Record<IndexJoinVerdict, number> {
282
+ const out = Object.fromEntries(INDEX_JOIN_VERDICTS.map((v) => [v, 0])) as Record<IndexJoinVerdict, number>;
283
+ for (const id of ids) {
284
+ const j = joins[id];
285
+ if (j) out[j.verdict] += 1;
286
+ }
287
+ return out;
288
+ }
289
+
290
+ /** A row's verdict where a physical id would go: `(absent)`, `(not observed: filtered)`, `(ambiguous: 2 candidates)`. */
291
+ export function joinCell(j: IndexJoin): string | undefined {
292
+ switch (j.verdict) {
293
+ case "bound":
294
+ return undefined;
295
+ case "absent":
296
+ return "(absent)";
297
+ case "not-observed":
298
+ return `(not observed: ${j.reason})`;
299
+ case "no-binding":
300
+ return "(no-binding)";
301
+ case "ambiguous":
302
+ return `(ambiguous: ${j.candidates.length} candidates)`;
303
+ }
304
+ }