@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,193 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { ReferenceCatalog } from "../graph-refs";
3
+ import type { IndexDocument } from "./index-document";
4
+ import { validateIndexDocument } from "./index-document";
5
+ import { deriveEdges, deriveEdgesAcross, type EdgeLexicon } from "./derive-edges";
6
+ import { typeListing, type ListedResource, type ListingLexicon, type ListingResult } from "./type-listing";
7
+
8
+ // #4522, to the rules ruled on INTENTIUS/behold#1023 (2026-10-26).
9
+ const catalog: ReferenceCatalog = {
10
+ identities: [
11
+ { kind: "Db::SubnetGroup", ids: ["Name"] },
12
+ { kind: "Iam::Role", ids: ["RoleName"] },
13
+ { kind: "Logs::Group", ids: ["GroupName"] },
14
+ { kind: "Svc::Cluster", ids: ["ClusterName"] },
15
+ { kind: "Eks::Cluster", ids: ["Name"] },
16
+ ],
17
+ refs: [
18
+ { from: "Db::Instance", path: "SubnetGroupName", targetKind: "Db::SubnetGroup", relation: "reference", label: "subnets" },
19
+ { from: "Task::Definition", path: "TaskRoleArn", targetKind: "Iam::Role", relation: "reference", label: "role" },
20
+ // Untargeted, the shape #1023 found false joins in: a name could be anything's.
21
+ { from: "Svc::Service", path: "Cluster", relation: "reference", label: "in cluster" },
22
+ { from: "Net::Subnet", path: "VpcId", targetKind: "Net::Vpc", relation: "containment", label: "in VPC" },
23
+ ],
24
+ tagRefs: [{ key: "kubernetes.io/cluster/", read: "key-suffix", targetKind: "Eks::Cluster" }],
25
+ };
26
+
27
+ const KINDS = ["Db::Instance", "Db::SubnetGroup", "Iam::Role", "Logs::Group", "Svc::Cluster", "Svc::Service", "Task::Definition", "Net::Subnet", "Net::Vpc", "Eks::Cluster"];
28
+ const typing: ListingLexicon[] = [{ name: "fake", listedTypes: { substrate: "aws", kindFor: (t) => (KINDS.includes(t) ? t : undefined) } }];
29
+ const lexicons: EdgeLexicon[] = [{ name: "fake", referenceCatalog: catalog }];
30
+
31
+ const r = (listedType: string, location: string, nativeId: string, properties: Record<string, unknown>, extra: Partial<ListedResource> = {}): ListedResource => ({
32
+ listedType,
33
+ location,
34
+ nativeId,
35
+ cloudIds: [{ kind: "import-id", value: nativeId }],
36
+ tags: {},
37
+ properties,
38
+ observedAt: "2026-10-26T09:00:01.000Z",
39
+ ...extra,
40
+ });
41
+
42
+ const RESOURCES: ListedResource[] = [
43
+ r("Db::Instance", "us-east-1", "orders", { SubnetGroupName: "default" }),
44
+ r("Db::SubnetGroup", "us-east-1", "default", { Name: "default" }),
45
+ r("Db::SubnetGroup", "us-west-2", "default", { Name: "default" }),
46
+ r("Iam::Role", "global", "app", { RoleName: "app" }, { cloudIds: [{ kind: "arn", value: "arn:aws:iam::000000000000:role/app" }, { kind: "import-id", value: "app" }] }),
47
+ r("Task::Definition", "us-east-1", "arn:aws:ecs:us-east-1:000000000000:task-definition/web:1", { TaskRoleArn: "arn:aws:iam::000000000000:role/app" }),
48
+ r("Svc::Cluster", "us-east-1", "default", { ClusterName: "default" }),
49
+ r("Logs::Group", "us-east-1", "default", { GroupName: "default" }),
50
+ r("Svc::Service", "us-east-1", "web", { Cluster: "default" }),
51
+ r("Net::Vpc", "us-east-1", "vpc-1", {}),
52
+ r("Net::Subnet", "us-east-1", "subnet-1", { VpcId: "vpc-1" }, { tags: { "kubernetes.io/cluster/prod": "shared", team: "prod" } }),
53
+ r("Net::Subnet", "us-east-1", "subnet-2", { VpcId: "vpc-gone" }),
54
+ r("Eks::Cluster", "us-east-1", "prod", { Name: "prod" }),
55
+ // A node whose value equals the cluster's name, only by its tag value: tags never join by value.
56
+ r("Logs::Group", "us-east-1", "team", { GroupName: "team" }),
57
+ ];
58
+
59
+ function listing(resources: ListedResource[], extra: Partial<ListingResult> = {}): ListingResult {
60
+ return {
61
+ listing: "v1",
62
+ source: { name: "behold-lister", version: "0.0.0-test" },
63
+ scope: { substrate: "aws", scopeId: "000000000000", locations: ["us-east-1", "us-west-2"], types: "*" },
64
+ startedAt: "2026-10-26T09:00:00.000Z",
65
+ completedAt: "2026-10-26T09:00:04.000Z",
66
+ resources,
67
+ coverage: [{ listedType: "*", location: "*", verdict: "listed" }],
68
+ notes: [],
69
+ ...extra,
70
+ };
71
+ }
72
+
73
+ const id = (location: string, type: string, nativeId: string) => `aws/000000000000/${location}/${type}/${nativeId}`;
74
+ const pairsOf = (doc: IndexDocument) => doc.graph.edges.map((e) => `${e.from} -> ${e.to} [${e.viaAttr}] ${e.relation} ${e.rule}`);
75
+
76
+ describe("deriveEdges", () => {
77
+ const doc = deriveEdges(typeListing(listing(RESOURCES), typing), lexicons);
78
+
79
+ test("every edge is derived, with its rule and property grain", () => {
80
+ expect(doc.graph.edges.length).toBeGreaterThan(0);
81
+ for (const e of doc.graph.edges) {
82
+ expect(e).toMatchObject({ kind: "ref", origin: "derived", grain: "property" });
83
+ expect(e.rule).toMatch(/^fake:/);
84
+ }
85
+ expect(() => validateIndexDocument(doc)).not.toThrow();
86
+ });
87
+
88
+ test("the join key is scoped: a name joins in its own region, and a global type by account", () => {
89
+ expect(pairsOf(doc)).toEqual(
90
+ expect.arrayContaining([
91
+ `${id("us-east-1", "Db::Instance", "orders")} -> ${id("us-east-1", "Db::SubnetGroup", "default")} [SubnetGroupName] reference fake:Db::Instance:SubnetGroupName`,
92
+ `${id("us-east-1", "Task::Definition", "arn:aws:ecs:us-east-1:000000000000:task-definition/web:1")} -> ${id("global", "Iam::Role", "app")} [TaskRoleArn] reference fake:Task::Definition:TaskRoleArn`,
93
+ ]),
94
+ );
95
+ expect(pairsOf(doc).some((p) => p.includes(id("us-west-2", "Db::SubnetGroup", "default")))).toBe(false);
96
+ });
97
+
98
+ test("a value matching several nodes draws no edge and is noted with every candidate", () => {
99
+ const service = id("us-east-1", "Svc::Service", "web");
100
+ expect(doc.graph.edges.some((e) => e.from === service)).toBe(false);
101
+ const note = doc.notes?.find((n) => n.kind === "ambiguous-reference");
102
+ expect(note?.message).toContain(service);
103
+ expect(note?.message).toContain(id("us-east-1", "Svc::Cluster", "default"));
104
+ expect(note?.message).toContain(id("us-east-1", "Logs::Group", "default"));
105
+ expect(note?.message).toContain("fake:Svc::Service:Cluster");
106
+ });
107
+
108
+ test("containment is an edge with its relation, and a box in groups.byContainer", () => {
109
+ const subnet = id("us-east-1", "Net::Subnet", "subnet-1");
110
+ const vpc = id("us-east-1", "Net::Vpc", "vpc-1");
111
+ expect(doc.graph.edges).toContainEqual(expect.objectContaining({ from: subnet, to: vpc, viaAttr: "VpcId", relation: "containment" }));
112
+ expect(doc.graph.groups.byContainer?.[vpc]).toEqual([subnet]);
113
+ });
114
+
115
+ test("a reference to nothing in the index is dangling, never an edge", () => {
116
+ expect(doc.graph.dangling).toContainEqual({ from: id("us-east-1", "Net::Subnet", "subnet-2"), kind: "ref", path: "VpcId", value: "vpc-gone" });
117
+ });
118
+
119
+ test("tags join only through a typed rule for a key that names a cloud id", () => {
120
+ const subnet = id("us-east-1", "Net::Subnet", "subnet-1");
121
+ expect(doc.graph.edges).toContainEqual(
122
+ expect.objectContaining({ from: subnet, to: id("us-east-1", "Eks::Cluster", "prod"), viaAttr: "Tags", rule: "fake:Net::Subnet:tag:kubernetes.io/cluster/" }),
123
+ );
124
+ // `team: prod` equals a cluster's name and a log group's name: no edge from a tag value.
125
+ expect(doc.graph.edges.some((e) => e.from === subnet && e.to === id("us-east-1", "Logs::Group", "team"))).toBe(false);
126
+ });
127
+
128
+ test("a lexicon with no catalog is named in the notes, and its nodes get no edges", () => {
129
+ const withAzure = typeListing(
130
+ listing([r("microsoft.storage/storageaccounts", "eastus", "/subscriptions/s/x", {})], {
131
+ scope: { substrate: "azure", scopeId: "s", locations: "*", types: "*" },
132
+ }),
133
+ [{ name: "azure", listedTypes: { substrate: "azure", kindFor: () => "Microsoft.Storage/storageAccounts" } }],
134
+ );
135
+ const out = deriveEdges(withAzure, [...lexicons, { name: "azure" }]);
136
+ expect(out.graph.edges).toEqual([]);
137
+ expect(out.notes?.find((n) => n.kind === "no-catalog")?.message).toContain("azure");
138
+ });
139
+
140
+ test("derivation is idempotent", () => {
141
+ expect(deriveEdges(doc, lexicons)).toEqual(doc);
142
+ });
143
+ });
144
+
145
+ describe("node order", () => {
146
+ // A small seeded shuffle, so a failure reproduces.
147
+ function shuffled<T>(xs: readonly T[], seed: number): T[] {
148
+ const out = [...xs];
149
+ let s = seed;
150
+ for (let i = out.length - 1; i > 0; i--) {
151
+ s = (s * 1103515245 + 12345) % 2147483648;
152
+ const j = s % (i + 1);
153
+ [out[i], out[j]] = [out[j], out[i]];
154
+ }
155
+ return out;
156
+ }
157
+
158
+ test("the edge set, dangling set and notes do not depend on the order nodes were listed in", () => {
159
+ const base = deriveEdges(typeListing(listing(RESOURCES), typing), lexicons);
160
+ for (const seed of [1, 2, 3, 7, 42, 1009]) {
161
+ const doc = typeListing(listing(RESOURCES), typing);
162
+ // Shuffle the typed nodes themselves, past typeListing's own sort.
163
+ const scrambled: IndexDocument = { ...doc, graph: { ...doc.graph, nodes: shuffled(doc.graph.nodes, seed) } };
164
+ const out = deriveEdges(scrambled, [...lexicons].reverse());
165
+ expect(out.graph.edges).toEqual(base.graph.edges);
166
+ expect(out.graph.dangling).toEqual(base.graph.dangling);
167
+ expect(out.graph.groups.byContainer).toEqual(base.graph.groups.byContainer);
168
+ expect(out.notes).toEqual(base.notes);
169
+ }
170
+ });
171
+ });
172
+
173
+ describe("deriveEdgesAcross", () => {
174
+ const regional = typeListing(listing(RESOURCES.filter((x) => x.location !== "global")), typing);
175
+ const accountLevel = typeListing(
176
+ listing(RESOURCES.filter((x) => x.location === "global"), { scope: { substrate: "aws", scopeId: "000000000000", locations: ["global"], types: "*" } }),
177
+ typing,
178
+ );
179
+
180
+ test("joins every document of one account from one read, and stores each edge with its holder", () => {
181
+ const [r1, g1] = deriveEdgesAcross([regional, accountLevel], lexicons);
182
+ const task = id("us-east-1", "Task::Definition", "arn:aws:ecs:us-east-1:000000000000:task-definition/web:1");
183
+ expect(r1.graph.edges).toContainEqual(expect.objectContaining({ from: task, to: id("global", "Iam::Role", "app"), rule: "fake:Task::Definition:TaskRoleArn" }));
184
+ expect(g1.graph.edges).toEqual([]);
185
+ });
186
+
187
+ test("refuses two accounts, or two reads", () => {
188
+ const other = typeListing(listing(RESOURCES, { scope: { substrate: "aws", scopeId: "111111111111", locations: ["us-east-1"], types: "*" } }), typing);
189
+ expect(() => deriveEdgesAcross([regional, other], lexicons)).toThrow(/one account/);
190
+ const later = typeListing(listing(RESOURCES, { startedAt: "2026-10-27T09:00:00.000Z" }), typing);
191
+ expect(() => deriveEdgesAcross([regional, later], lexicons)).toThrow(/one read/);
192
+ });
193
+ });
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Derived edges for indexed resources (#4522, tier 1 of INTENTIUS/behold#1025,
3
+ * rules ruled on INTENTIUS/behold#1023 2026-10-26).
4
+ *
5
+ * An index typed by ./type-listing.ts has nodes and no edges: with no project
6
+ * there are no declared references. {@link deriveEdges} runs each lexicon's
7
+ * `referenceCatalog` (the one `chant graph --live` uses, #778) over the
8
+ * indexed nodes through `reconstructIndexedEdges` (../graph-refs.ts), and
9
+ * writes what it finds into the document:
10
+ *
11
+ * - every edge is `origin: "derived"` with the `rule` that drew it and
12
+ * `grain: "property"`; containment edges say `relation: "containment"` and
13
+ * fill `groups.byContainer`;
14
+ * - the join key is the scoped value: substrate and account, subscription,
15
+ * project or cluster, plus region for a regional type, account only for a
16
+ * global one (Q2);
17
+ * - a value matching several nodes draws no edge; an `ambiguous-reference`
18
+ * note records it with every candidate (Q3);
19
+ * - a reference to nothing in the index goes in `graph.dangling`;
20
+ * - tags make edges only through a catalog's typed tag rules (Q5);
21
+ * - a lexicon with no catalog is named in a `no-catalog` note, and its nodes
22
+ * get no derived edges: nothing guesses;
23
+ * - derivation runs over every document of one account from the same read
24
+ * (Q7, {@link deriveEdgesAcross}), and each edge is stored in its holder's
25
+ * document. Edges are per read and never carried to another read.
26
+ *
27
+ * After the catalog pass, the identity-match pass (#4523, behold#1023 Q1)
28
+ * runs over the same nodes: an attribute value equal to the native id or ARN
29
+ * of a node whose lexicon declares an `indexIdentity` draws an edge whose
30
+ * `rule` starts `identity-match:`, under the same scoped join key, and a
31
+ * value naming several nodes draws none and is recorded in an
32
+ * `ambiguous-identity` note with every candidate. A pair the catalog joined
33
+ * gets no second edge. Pure.
34
+ */
35
+ import type { GraphIR, IRDanglingRef, IRNode } from "../graph-ir";
36
+ import {
37
+ containmentGroups,
38
+ reconstructIdentityEdges,
39
+ reconstructIndexedEdges,
40
+ type IdentityOwners,
41
+ type IndexedNodeScope,
42
+ type NamedCatalog,
43
+ type ReferenceCatalog,
44
+ } from "../graph-refs";
45
+ import type { IndexIdentity } from "./identity-match";
46
+ import { indexDocumentDigest, type IndexDocument, type IndexNote } from "./index-document";
47
+
48
+ /** What {@link deriveEdges} needs from a lexicon. A `LexiconPlugin` is one. */
49
+ export interface EdgeLexicon {
50
+ name: string;
51
+ referenceCatalog?: ReferenceCatalog;
52
+ /** Its identity rule (#4523): when present, the identity-match pass can name this lexicon's nodes. */
53
+ indexIdentity?: IndexIdentity;
54
+ }
55
+
56
+ /** Locations that hold global types: AWS's `global`, Kubernetes' cluster-scoped objects. */
57
+ const GLOBAL_LOCATIONS: ReadonlySet<string> = new Set(["global", "cluster", ""]);
58
+
59
+ /** The scope an indexed node's identifiers are unique in, from its document's stamp and its own location. */
60
+ export function indexedNodeScope(doc: IndexDocument, node: IRNode): IndexedNodeScope {
61
+ const scope = doc.stamp.scope;
62
+ const account = scope.kind === "cloud" ? `${scope.substrate}/${scope.scopeId}` : doc.stamp.scopeKey;
63
+ const location = node.location ?? "";
64
+ return GLOBAL_LOCATIONS.has(location) ? { account } : { account, region: location };
65
+ }
66
+
67
+ const byString = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
68
+
69
+ function noteKey(n: IndexNote): string {
70
+ return `${n.kind}\u0000${n.listedType ?? ""}\u0000${n.location ?? ""}\u0000${n.message}`;
71
+ }
72
+
73
+ /** Derived edges over one index document. */
74
+ export function deriveEdges(doc: IndexDocument, lexicons: readonly EdgeLexicon[]): IndexDocument {
75
+ return deriveEdgesAcross([doc], lexicons)[0];
76
+ }
77
+
78
+ /**
79
+ * Derived edges over every document of one account from one read (behold#1023
80
+ * Q7): regional documents and an account-level one for global types, or the
81
+ * single document a `behold index` run writes (behold#1025 Q6). The documents
82
+ * must be cloud reads of one substrate and scope id with one `observedAt`;
83
+ * anything else is refused, because joining two reads would carry a name from
84
+ * one read to an object of another.
85
+ */
86
+ export function deriveEdgesAcross(docs: readonly IndexDocument[], lexicons: readonly EdgeLexicon[]): IndexDocument[] {
87
+ if (docs.length === 0) return [];
88
+ const first = docs[0].stamp;
89
+ for (const d of docs) {
90
+ const s = d.stamp.scope;
91
+ if (s.kind !== "cloud" || first.scope.kind !== "cloud") {
92
+ throw new Error(`derived edges run over cloud index documents; ${d.stamp.scopeKey} is a ${s.kind} source`);
93
+ }
94
+ if (s.substrate !== first.scope.substrate || s.scopeId !== first.scope.scopeId) {
95
+ throw new Error(`derived edges run over one account: ${first.scope.substrate}/${first.scope.scopeId} and ${s.substrate}/${s.scopeId} are two`);
96
+ }
97
+ if (d.stamp.observedAt !== first.observedAt) {
98
+ throw new Error(`derived edges run over one read: ${first.observedAt} and ${d.stamp.observedAt} are two`);
99
+ }
100
+ }
101
+
102
+ const named: NamedCatalog[] = [...lexicons]
103
+ .filter((l): l is EdgeLexicon & { referenceCatalog: ReferenceCatalog } => l.referenceCatalog !== undefined)
104
+ .sort((a, b) => byString(a.name, b.name))
105
+ .map((l) => ({ lexicon: l.name, catalog: l.referenceCatalog }));
106
+ const withCatalog = new Set(named.map((c) => c.lexicon));
107
+
108
+ const holderDoc = new Map<string, number>();
109
+ const scopes = new Map<string, IndexedNodeScope>();
110
+ const all: IRNode[] = [];
111
+ docs.forEach((d, i) => {
112
+ for (const n of d.graph.nodes) {
113
+ holderDoc.set(n.id, i);
114
+ scopes.set(n.id, indexedNodeScope(d, n));
115
+ all.push(n);
116
+ }
117
+ });
118
+
119
+ const derived = reconstructIndexedEdges(all, named, (n) => scopes.get(n.id)!);
120
+ const owners: IdentityOwners[] = [...lexicons]
121
+ .filter((l) => l.indexIdentity !== undefined)
122
+ .sort((a, b) => byString(a.name, b.name))
123
+ .map((l) => ({ lexicon: l.name, ...(l.indexIdentity!.nodeIdentities ? { identities: l.indexIdentity!.nodeIdentities } : {}) }));
124
+ const identity = owners.length > 0
125
+ ? reconstructIdentityEdges(all, owners, (n) => scopes.get(n.id)!, derived.edges)
126
+ : { edges: [], ambiguous: [] };
127
+
128
+ return docs.map((d, i) => {
129
+ const mine = (from: string) => holderDoc.get(from) === i;
130
+ const edges = [...d.graph.edges.filter((e) => e.origin !== "derived"), ...derived.edges.filter((e) => mine(e.from)), ...identity.edges.filter((e) => mine(e.from))];
131
+ const pairs = derived.containment.filter((p) => mine(p.child));
132
+ const dangling = new Map<string, IRDanglingRef>();
133
+ for (const r of [
134
+ ...(d.graph.dangling ?? []),
135
+ ...derived.dangling.filter((x) => mine(x.from)).map((x) => ({ from: x.from, kind: "ref" as const, path: x.path, value: x.value })),
136
+ ]) {
137
+ dangling.set(`${r.from}\u0000${r.kind}\u0000${r.path}\u0000${r.value}`, r);
138
+ }
139
+
140
+ const notes: IndexNote[] = (d.notes ?? []).filter((n) => n.kind !== "no-catalog" && n.kind !== "ambiguous-reference" && n.kind !== "ambiguous-identity");
141
+ for (const a of derived.ambiguous.filter((x) => mine(x.from))) {
142
+ notes.push({
143
+ kind: "ambiguous-reference",
144
+ message: `${a.from} ${a.path} = ${JSON.stringify(a.value)} matches ${a.candidates.length} nodes (${a.candidates.join(", ")}); rule ${a.rule} drew no edge`,
145
+ });
146
+ }
147
+ for (const a of identity.ambiguous.filter((x) => mine(x.from))) {
148
+ notes.push({
149
+ kind: "ambiguous-identity",
150
+ message: `${a.from} ${a.path} = ${JSON.stringify(a.value)} names ${a.candidates.length} nodes (${a.candidates.join(", ")}); the identity-match pass drew no edge`,
151
+ });
152
+ }
153
+ const counts = new Map<string, number>();
154
+ for (const n of d.graph.nodes) if (!n.unmappedKind) counts.set(n.lexicon, (counts.get(n.lexicon) ?? 0) + 1);
155
+ for (const lexicon of [...counts.keys()].sort()) {
156
+ if (withCatalog.has(lexicon)) continue;
157
+ notes.push({
158
+ kind: "no-catalog",
159
+ message: `the ${lexicon} lexicon has no referenceCatalog, so its ${counts.get(lexicon)} node(s) have no derived edges`,
160
+ });
161
+ }
162
+ notes.sort((a, b) => byString(noteKey(a), noteKey(b)));
163
+
164
+ const groups = { ...d.graph.groups };
165
+ const byContainer = containmentGroups(pairs);
166
+ if (Object.keys(byContainer).length > 0) groups.byContainer = byContainer;
167
+ else delete groups.byContainer;
168
+
169
+ const graph: GraphIR = { ...d.graph, edges, groups };
170
+ if (dangling.size > 0) graph.dangling = [...dangling.values()];
171
+ else delete graph.dangling;
172
+
173
+ const out: IndexDocument = {
174
+ ...d,
175
+ stamp: { ...d.stamp, digest: indexDocumentDigest({ graph, coverage: d.coverage }) },
176
+ graph,
177
+ };
178
+ if (notes.length > 0) out.notes = notes;
179
+ else delete out.notes;
180
+ return out;
181
+ });
182
+ }
@@ -475,6 +475,10 @@ export function snapshotStorageKey(lexicon: string, stack?: string): string {
475
475
  /**
476
476
  * Write a state snapshot JSON to the orphan branch.
477
477
  *
478
+ * Nothing in chant calls this since #4519: snapshots go through the index
479
+ * store (./snapshot-store.ts), whose default is `refs/chant/snapshots`. Kept
480
+ * for code outside chant that wrote snapshots here itself.
481
+ *
478
482
  * Pipeline: hash-object → mktree → commit-tree → update-ref
479
483
  */
480
484
  export async function writeSnapshot(
@@ -487,12 +491,14 @@ export async function writeSnapshot(
487
491
  }
488
492
 
489
493
  /**
490
- * Read a snapshot from the orphan branch.
494
+ * Read a snapshot from the orphan branch. Since #4519 this is the read side
495
+ * of the read-only orphan-branch store (./snapshot-store.ts); chant's own
496
+ * readers go through the store.
491
497
  */
492
498
  export async function readSnapshot(
493
499
  environment: string,
494
500
  lexicon: string,
495
- opts?: { cwd?: string },
501
+ opts?: LedgerLocation,
496
502
  ): Promise<string | null> {
497
503
  return readBlobFromPath(environment, `${lexicon}.json`, opts);
498
504
  }
@@ -507,7 +513,7 @@ export async function readSnapshotAt(
507
513
  environment: string,
508
514
  lexicon: string,
509
515
  ref: string,
510
- opts?: { cwd?: string },
516
+ opts?: LedgerLocation,
511
517
  ): Promise<string | null> {
512
518
  const rt = getRuntime();
513
519
  const dir = await ledgerDir(environment, opts);
@@ -704,7 +710,7 @@ export async function readEnvironmentSnapshots(
704
710
  * List snapshot history from the orphan branch.
705
711
  */
706
712
  export async function listSnapshots(
707
- opts?: { cwd?: string; environment?: string },
713
+ opts?: LedgerLocation & { environment?: string },
708
714
  ): Promise<Array<{ commit: string; date: string; message: string }>> {
709
715
  const rt = getRuntime();
710
716
  // A member sees the commits that touched its own ledger (#2538); the flat