@intentius/chant 0.124.44 → 0.124.46

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 (249) hide show
  1. package/dist/capabilities/aws.paths +148 -0
  2. package/dist/capabilities/base-mappings.json +122 -0
  3. package/dist/capabilities/catalog.d.ts +89 -0
  4. package/dist/capabilities/catalog.d.ts.map +1 -0
  5. package/dist/capabilities/catalog.js +204 -0
  6. package/dist/capabilities/language.d.ts +103 -0
  7. package/dist/capabilities/language.d.ts.map +1 -0
  8. package/dist/capabilities/language.js +335 -0
  9. package/dist/capabilities/matcher.d.ts +65 -0
  10. package/dist/capabilities/matcher.d.ts.map +1 -0
  11. package/dist/capabilities/matcher.js +299 -0
  12. package/dist/capabilities/view.d.ts +207 -0
  13. package/dist/capabilities/view.d.ts.map +1 -0
  14. package/dist/capabilities/view.js +484 -0
  15. package/dist/ci-identity.d.ts +183 -12
  16. package/dist/ci-identity.d.ts.map +1 -1
  17. package/dist/ci-identity.js +362 -53
  18. package/dist/cli/commands/lint.d.ts.map +1 -1
  19. package/dist/cli/commands/lint.js +1 -0
  20. package/dist/cli/handlers/build.d.ts.map +1 -1
  21. package/dist/cli/handlers/build.js +17 -1
  22. package/dist/cli/handlers/declared-graph.d.ts +44 -0
  23. package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
  24. package/dist/cli/handlers/declared-graph.js +87 -0
  25. package/dist/cli/handlers/graph.d.ts +25 -0
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/graph.js +93 -87
  28. package/dist/cli/handlers/pr-env.d.ts +8 -2
  29. package/dist/cli/handlers/pr-env.d.ts.map +1 -1
  30. package/dist/cli/handlers/pr-env.js +127 -225
  31. package/dist/cli/handlers/pr.d.ts +3 -0
  32. package/dist/cli/handlers/pr.d.ts.map +1 -1
  33. package/dist/cli/handlers/pr.js +1 -0
  34. package/dist/cli/handlers/search-count.d.ts +89 -0
  35. package/dist/cli/handlers/search-count.d.ts.map +1 -0
  36. package/dist/cli/handlers/search-count.js +132 -0
  37. package/dist/cli/handlers/search.d.ts +358 -6
  38. package/dist/cli/handlers/search.d.ts.map +1 -1
  39. package/dist/cli/handlers/search.js +1065 -155
  40. package/dist/cli/main.d.ts.map +1 -1
  41. package/dist/cli/main.js +23 -3
  42. package/dist/cli/mcp/server.d.ts.map +1 -1
  43. package/dist/cli/mcp/server.js +3 -0
  44. package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
  45. package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
  46. package/dist/cli/mcp/tools/capabilities.js +27 -0
  47. package/dist/cli/mcp/workspace-tools.d.ts +3 -1
  48. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  49. package/dist/cli/mcp/workspace-tools.js +47 -2
  50. package/dist/cli/parse-args.d.ts.map +1 -1
  51. package/dist/cli/parse-args.js +64 -17
  52. package/dist/cli/registry.d.ts +22 -0
  53. package/dist/cli/registry.d.ts.map +1 -1
  54. package/dist/components/cli-support.d.ts +2 -0
  55. package/dist/components/cli-support.d.ts.map +1 -1
  56. package/dist/components/cli-support.js +4 -2
  57. package/dist/components/fan-out-support.d.ts +3 -0
  58. package/dist/components/fan-out-support.d.ts.map +1 -1
  59. package/dist/components/fan-out-support.js +1 -1
  60. package/dist/components/pr-env-ports.d.ts +29 -0
  61. package/dist/components/pr-env-ports.d.ts.map +1 -0
  62. package/dist/components/pr-env-ports.js +77 -0
  63. package/dist/components/pr-env-run.d.ts +254 -0
  64. package/dist/components/pr-env-run.d.ts.map +1 -0
  65. package/dist/components/pr-env-run.js +376 -0
  66. package/dist/components/pr-environment.d.ts +91 -15
  67. package/dist/components/pr-environment.d.ts.map +1 -1
  68. package/dist/components/pr-environment.js +95 -9
  69. package/dist/components/pr-pipeline.d.ts +10 -1
  70. package/dist/components/pr-pipeline.d.ts.map +1 -1
  71. package/dist/config-environments.d.ts +92 -0
  72. package/dist/config-environments.d.ts.map +1 -0
  73. package/dist/config-environments.js +102 -0
  74. package/dist/config.d.ts +24 -84
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +9 -96
  77. package/dist/graph-detail.d.ts.map +1 -1
  78. package/dist/graph-detail.js +15 -3
  79. package/dist/graph-effective.d.ts +33 -2
  80. package/dist/graph-effective.d.ts.map +1 -1
  81. package/dist/graph-effective.js +215 -68
  82. package/dist/graph-ir.d.ts +23 -0
  83. package/dist/graph-ir.d.ts.map +1 -1
  84. package/dist/graph-ir.js +130 -0
  85. package/dist/graph-sub-template.d.ts +11 -0
  86. package/dist/graph-sub-template.d.ts.map +1 -0
  87. package/dist/graph-sub-template.js +19 -0
  88. package/dist/lexicon.d.ts +9 -0
  89. package/dist/lexicon.d.ts.map +1 -1
  90. package/dist/lifecycle/observe.d.ts +28 -0
  91. package/dist/lifecycle/observe.d.ts.map +1 -1
  92. package/dist/lifecycle/observe.js +23 -1
  93. package/dist/lint/rule.d.ts +6 -0
  94. package/dist/lint/rule.d.ts.map +1 -1
  95. package/dist/live-endpoint.d.ts +1 -1
  96. package/dist/live-endpoint.d.ts.map +1 -1
  97. package/dist/live-endpoint.js +1 -1
  98. package/dist/observation.d.ts +48 -1
  99. package/dist/observation.d.ts.map +1 -1
  100. package/dist/observation.js +21 -5
  101. package/dist/op/activities/activity-contracts.d.ts +4 -4
  102. package/dist/op/op-waves.d.ts +17 -0
  103. package/dist/op/op-waves.d.ts.map +1 -1
  104. package/dist/op/op-waves.js +23 -1
  105. package/dist/pr-forge.d.ts +15 -1
  106. package/dist/pr-forge.d.ts.map +1 -1
  107. package/dist/pr-forge.js +28 -16
  108. package/dist/pr-requires.d.ts +5 -1
  109. package/dist/pr-requires.d.ts.map +1 -1
  110. package/dist/wave-runtime.mjs +251 -250
  111. package/dist/workspace/capabilities-contract.d.ts +66 -0
  112. package/dist/workspace/capabilities-contract.d.ts.map +1 -0
  113. package/dist/workspace/capabilities-contract.js +107 -0
  114. package/dist/workspace/capabilities.schema.json +229 -0
  115. package/dist/workspace/ci-cloud-identity.d.ts +3 -1
  116. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  117. package/dist/workspace/ci-cloud-identity.js +16 -1
  118. package/dist/workspace/ci-run.d.ts +35 -8
  119. package/dist/workspace/ci-run.d.ts.map +1 -1
  120. package/dist/workspace/ci-run.js +95 -21
  121. package/dist/workspace/ci-secrets.d.ts.map +1 -1
  122. package/dist/workspace/ci-secrets.js +4 -3
  123. package/dist/workspace/compose-graph.d.ts +9 -0
  124. package/dist/workspace/compose-graph.d.ts.map +1 -1
  125. package/dist/workspace/compose-graph.js +3 -1
  126. package/dist/workspace/conformance/index.d.ts +1 -1
  127. package/dist/workspace/conformance/index.d.ts.map +1 -1
  128. package/dist/workspace/conformance/index.js +39 -2
  129. package/dist/workspace/declaration.d.ts +10 -0
  130. package/dist/workspace/declaration.d.ts.map +1 -1
  131. package/dist/workspace/declaration.js +17 -1
  132. package/dist/workspace/declaration.schema.json +36 -1
  133. package/dist/workspace/graph-cache.d.ts +8 -0
  134. package/dist/workspace/graph-cache.d.ts.map +1 -1
  135. package/dist/workspace/graph-cache.js +27 -3
  136. package/dist/workspace/graph-cli.d.ts +39 -2
  137. package/dist/workspace/graph-cli.d.ts.map +1 -1
  138. package/dist/workspace/graph-cli.js +66 -24
  139. package/dist/workspace/graph.schema.json +148 -0
  140. package/dist/workspace/kind-readers.d.ts +3 -0
  141. package/dist/workspace/kind-readers.d.ts.map +1 -1
  142. package/dist/workspace/kind-readers.js +7 -1
  143. package/dist/workspace/member-commands.d.ts +10 -1
  144. package/dist/workspace/member-commands.d.ts.map +1 -1
  145. package/dist/workspace/member-commands.js +17 -1
  146. package/dist/workspace/nested-graph.d.ts +2 -0
  147. package/dist/workspace/nested-graph.d.ts.map +1 -1
  148. package/dist/workspace/nested-graph.js +32 -1
  149. package/dist/workspace/reason-codes.d.ts +10 -0
  150. package/dist/workspace/reason-codes.d.ts.map +1 -1
  151. package/dist/workspace/reason-codes.js +12 -0
  152. package/dist/workspace/search-cli.d.ts +204 -0
  153. package/dist/workspace/search-cli.d.ts.map +1 -0
  154. package/dist/workspace/search-cli.js +466 -0
  155. package/dist/workspace/search-contract.d.ts +56 -0
  156. package/dist/workspace/search-contract.d.ts.map +1 -0
  157. package/dist/workspace/search-contract.js +70 -0
  158. package/dist/workspace/search.schema.json +822 -0
  159. package/package.json +1 -1
  160. package/src/__snapshots__/okf.test.ts.snap +10 -0
  161. package/src/capabilities/aws.paths +148 -0
  162. package/src/capabilities/base-mappings.json +122 -0
  163. package/src/capabilities/catalog.ts +230 -0
  164. package/src/capabilities/language.test.ts +191 -0
  165. package/src/capabilities/language.ts +373 -0
  166. package/src/capabilities/matcher.ts +315 -0
  167. package/src/capabilities/view-compose.test.ts +76 -0
  168. package/src/capabilities/view.ts +631 -0
  169. package/src/ci-identity-targets.test.ts +275 -0
  170. package/src/ci-identity.ts +394 -61
  171. package/src/cli/commands/lint.ts +1 -0
  172. package/src/cli/handlers/build.ts +15 -1
  173. package/src/cli/handlers/declared-graph.ts +99 -0
  174. package/src/cli/handlers/graph.test.ts +94 -3
  175. package/src/cli/handlers/graph.ts +123 -96
  176. package/src/cli/handlers/pr-env.ts +135 -227
  177. package/src/cli/handlers/pr.ts +4 -0
  178. package/src/cli/handlers/search-build-roots.test.ts +144 -0
  179. package/src/cli/handlers/search-count.test.ts +191 -0
  180. package/src/cli/handlers/search-count.ts +165 -0
  181. package/src/cli/handlers/search-json.test.ts +333 -0
  182. package/src/cli/handlers/search-live.test.ts +387 -0
  183. package/src/cli/handlers/search-paths.test.ts +235 -0
  184. package/src/cli/handlers/search.ts +1312 -166
  185. package/src/cli/main.test.ts +28 -3
  186. package/src/cli/main.ts +23 -3
  187. package/src/cli/mcp/server.test.ts +3 -3
  188. package/src/cli/mcp/server.ts +3 -0
  189. package/src/cli/mcp/tools/capabilities.ts +32 -0
  190. package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
  191. package/src/cli/mcp/workspace-tools.ts +47 -2
  192. package/src/cli/parse-args.ts +52 -13
  193. package/src/cli/registry.ts +22 -0
  194. package/src/components/cli-support.ts +6 -1
  195. package/src/components/fan-out-support.ts +4 -1
  196. package/src/components/pr-env-ports.ts +89 -0
  197. package/src/components/pr-env-run-bundle.test.ts +54 -0
  198. package/src/components/pr-env-run.test.ts +290 -0
  199. package/src/components/pr-env-run.ts +563 -0
  200. package/src/components/pr-environment.ts +161 -13
  201. package/src/components/pr-pipeline.ts +10 -1
  202. package/src/config-environments.ts +133 -0
  203. package/src/config.ts +37 -124
  204. package/src/graph-detail.ts +15 -3
  205. package/src/graph-effective.ts +226 -75
  206. package/src/graph-ir.test.ts +163 -0
  207. package/src/graph-ir.ts +147 -0
  208. package/src/graph-sub-template.ts +22 -0
  209. package/src/lexicon.ts +10 -0
  210. package/src/lifecycle/observe.test.ts +27 -1
  211. package/src/lifecycle/observe.ts +43 -1
  212. package/src/lint/rule.ts +6 -0
  213. package/src/live-endpoint.ts +1 -1
  214. package/src/observation.test.ts +25 -0
  215. package/src/observation.ts +69 -5
  216. package/src/op/op-waves.ts +38 -1
  217. package/src/pr-forge-bundle.test.ts +1 -1
  218. package/src/pr-forge.ts +44 -17
  219. package/src/pr-requires.test.ts +43 -6
  220. package/src/pr-requires.ts +5 -1
  221. package/src/workspace/capabilities-contract.test.ts +88 -0
  222. package/src/workspace/capabilities-contract.ts +132 -0
  223. package/src/workspace/capabilities.schema.json +229 -0
  224. package/src/workspace/ci-cloud-identity.test.ts +20 -0
  225. package/src/workspace/ci-cloud-identity.ts +14 -1
  226. package/src/workspace/ci-run.test.ts +74 -1
  227. package/src/workspace/ci-run.ts +122 -29
  228. package/src/workspace/ci-secrets.test.ts +1 -1
  229. package/src/workspace/ci-secrets.ts +4 -3
  230. package/src/workspace/compose-graph.ts +10 -1
  231. package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
  232. package/src/workspace/conformance/index.ts +31 -2
  233. package/src/workspace/declaration.schema.json +36 -1
  234. package/src/workspace/declaration.test.ts +7 -0
  235. package/src/workspace/declaration.ts +24 -2
  236. package/src/workspace/graph-cache.ts +27 -3
  237. package/src/workspace/graph-cli.ts +96 -24
  238. package/src/workspace/graph.schema.json +148 -0
  239. package/src/workspace/kind-readers.e2e.test.ts +28 -0
  240. package/src/workspace/kind-readers.test.ts +1 -1
  241. package/src/workspace/kind-readers.ts +7 -1
  242. package/src/workspace/member-commands.ts +22 -1
  243. package/src/workspace/nested-graph.ts +32 -1
  244. package/src/workspace/reason-codes.test.ts +7 -0
  245. package/src/workspace/reason-codes.ts +12 -0
  246. package/src/workspace/search-cli.e2e.test.ts +305 -0
  247. package/src/workspace/search-cli.ts +591 -0
  248. package/src/workspace/search-contract.ts +95 -0
  249. package/src/workspace/search.schema.json +822 -0
@@ -0,0 +1,484 @@
1
+ import { terraformType } from "../graph-effective.js";
2
+ /**
3
+ * Merge mappings for the same lexicon: objects merge key by key at every
4
+ * depth, lists concatenate, and anything else in a later mapping wins.
5
+ */
6
+ export function mergeMappings(...ms) {
7
+ const merge = (a, b) => {
8
+ if (b === undefined)
9
+ return a;
10
+ if (Array.isArray(a) && Array.isArray(b))
11
+ return [...a, ...b.filter((x) => typeof x === "object" || !a.includes(x))];
12
+ const isObj = (x) => !!x && typeof x === "object" && !Array.isArray(x);
13
+ if (isObj(a) && isObj(b)) {
14
+ const out = { ...a };
15
+ for (const [k, v] of Object.entries(b))
16
+ out[k] = merge(a[k], v);
17
+ return out;
18
+ }
19
+ return b;
20
+ };
21
+ return ms.reduce((acc, m) => merge(acc, m), { lexicon: ms[0]?.lexicon ?? "" });
22
+ }
23
+ const arr = (v) => (v == null ? [] : Array.isArray(v) ? v : [v]);
24
+ /** Every value at `path` (`A.B`, `A[].B`), flattened. */
25
+ function readPath(obj, path) {
26
+ let cur = [obj];
27
+ for (const seg of path.split(".")) {
28
+ const spread = seg.endsWith("[]");
29
+ const key = spread ? seg.slice(0, -2) : seg;
30
+ const next = [];
31
+ for (const c of cur) {
32
+ if (!c || typeof c !== "object")
33
+ continue;
34
+ const v = c[key];
35
+ if (v === undefined)
36
+ continue;
37
+ if (spread)
38
+ next.push(...arr(v));
39
+ else
40
+ next.push(v);
41
+ }
42
+ cur = next;
43
+ }
44
+ return cur;
45
+ }
46
+ function readSource(obj, src) {
47
+ if (typeof src === "string")
48
+ return readPath(obj, src)[0];
49
+ if (Array.isArray(src)) {
50
+ for (const p of src) {
51
+ const v = readPath(obj, p)[0];
52
+ if (v !== undefined && v !== null)
53
+ return v;
54
+ }
55
+ return undefined;
56
+ }
57
+ if ("const" in src)
58
+ return src.const;
59
+ return src.all.flatMap((p) => readPath(obj, p).flatMap(arr));
60
+ }
61
+ function matchesWhen(obj, when) {
62
+ if (!when)
63
+ return true;
64
+ return Object.entries(when).every(([k, v]) => readPath(obj, k)[0] === v);
65
+ }
66
+ /** The bare name a row prints: no stack qualification, no root directory. */
67
+ export function displayName(id) {
68
+ const s = id.includes("::") ? id.slice(id.lastIndexOf("::") + 2) : id;
69
+ return s.slice(s.lastIndexOf("/") + 1);
70
+ }
71
+ const ARN_NAMES = [
72
+ { label: "Role", re: /^arn:aws[\w-]*:iam::[^:]*:role\/(?:[^*]*\/)?([^/*]+)$/ },
73
+ { label: "Bucket", re: /^arn:aws[\w-]*:s3:::([^/*]+)(?:\/.*)?$/ },
74
+ { label: "Table", re: /^arn:aws[\w-]*:dynamodb:[^:]*:[^:]*:table\/([^/*]+)$/ },
75
+ ];
76
+ /**
77
+ * Normalize what each lexicon spells its own way, for labels whose meaning is
78
+ * core vocabulary: an ingress rule's protocol and port range.
79
+ */
80
+ function normalize(label, attrs) {
81
+ if (label === "IngressRule") {
82
+ const raw = attrs.proto == null ? "-1" : String(attrs.proto).toLowerCase();
83
+ const proto = raw === "all" || raw === "-1" ? "-1" : raw;
84
+ attrs.proto = proto;
85
+ if (proto === "-1" || attrs.from == null) {
86
+ attrs.from = 0;
87
+ attrs.to = 65535;
88
+ }
89
+ else {
90
+ attrs.from = Number(attrs.from);
91
+ attrs.to = attrs.to == null ? attrs.from : Number(attrs.to);
92
+ }
93
+ attrs.cidrs = arr(attrs.cidrs).map(String);
94
+ }
95
+ }
96
+ /**
97
+ * Build the view of an IR through the packs given. Nodes of a kind no
98
+ * mapping labels are left out, as are edges no mapping names.
99
+ */
100
+ export function buildView(ir, packs) {
101
+ const byLexicon = new Map();
102
+ for (const p of packs) {
103
+ const prev = byLexicon.get(p.mapping.lexicon);
104
+ byLexicon.set(p.mapping.lexicon, prev ? { mapping: mergeMappings(prev.mapping, p.mapping), adapter: { ...prev.adapter, ...p.adapter } } : p);
105
+ }
106
+ const terraformPack = [...byLexicon.values()].find((p) => p.mapping.types === "terraform");
107
+ const kindPacks = [...byLexicon.values()].filter((p) => p.mapping.types !== "terraform");
108
+ const nodes = new Map();
109
+ const edges = [];
110
+ const lexicons = new Set();
111
+ const irById = new Map(ir.nodes.map((n) => [n.id, n]));
112
+ const packOf = new Map();
113
+ const typeAndPack = (n) => {
114
+ const tf = terraformType(n);
115
+ if (tf !== undefined)
116
+ return terraformPack ? { type: tf, pack: terraformPack } : undefined;
117
+ const kind = n.kind ?? "";
118
+ const pack = kindPacks.find((p) => p.mapping.labels?.[kind] !== undefined);
119
+ return pack ? { type: kind, pack } : undefined;
120
+ };
121
+ const bodyOf = (n, pack) => {
122
+ const attrs = (n.attrs ?? {});
123
+ if (!pack.mapping.body)
124
+ return attrs;
125
+ const b = attrs[pack.mapping.body];
126
+ return b && typeof b === "object" && !Array.isArray(b) ? b : {};
127
+ };
128
+ // Terraform locals, per scope, so `tags = local.restricted` reads as a map.
129
+ const locals = new Map();
130
+ const scopeOf = (id) => id.slice(0, id.lastIndexOf("/") + 1);
131
+ for (const n of ir.nodes) {
132
+ if (n.kind !== "Terraform::Locals")
133
+ continue;
134
+ const body = (n.attrs?.body ?? {});
135
+ locals.set(scopeOf(n.id), { ...locals.get(scopeOf(n.id)), ...body });
136
+ }
137
+ const resolveLocal = (from, v) => {
138
+ const m = typeof v === "string" ? /^\$\{local\.([\w-]+)\}$/.exec(v) : null;
139
+ return m ? locals.get(scopeOf(from))?.[m[1]] : v;
140
+ };
141
+ // ---- nodes
142
+ for (const n of ir.nodes) {
143
+ const tp = typeAndPack(n);
144
+ if (!tp)
145
+ continue;
146
+ const { type, pack } = tp;
147
+ const m = pack.mapping;
148
+ const spec = m.labels?.[type];
149
+ if (spec === undefined)
150
+ continue;
151
+ const label = typeof spec === "string" ? spec : spec.label;
152
+ const body = bodyOf(n, pack);
153
+ if (typeof spec !== "string" && !matchesWhen(body, spec.when))
154
+ continue;
155
+ const attrs = { type };
156
+ if (typeof spec !== "string" && spec.style)
157
+ attrs.style = spec.style;
158
+ if (typeof spec !== "string" && spec.set)
159
+ Object.assign(attrs, spec.set);
160
+ for (const [neutral, src] of Object.entries(m.attrs?.[label] ?? {})) {
161
+ const v = readSource(body, src);
162
+ if (v !== undefined)
163
+ attrs[neutral] = resolveLocal(n.id, v);
164
+ }
165
+ // Carry the IR's own derived attributes (a live read's `internetFacing`).
166
+ for (const [k, v] of Object.entries((n.attrs ?? {}))) {
167
+ if (k === "internetFacing" && attrs[k] === undefined)
168
+ attrs[k] = v;
169
+ }
170
+ if (m.classificationTags?.length) {
171
+ for (const path of m.tags ?? []) {
172
+ const tags = resolveLocal(n.id, readPath(body, path)[0]);
173
+ const map = Array.isArray(tags)
174
+ ? Object.fromEntries(tags.map((t) => [t.Key, t.Value]))
175
+ : tags && typeof tags === "object"
176
+ ? tags
177
+ : {};
178
+ for (const key of m.classificationTags) {
179
+ if (map[key] !== undefined && attrs.classification === undefined)
180
+ attrs.classification = map[key];
181
+ }
182
+ }
183
+ }
184
+ normalize(label, attrs);
185
+ nodes.set(n.id, { id: n.id, label, irId: n.id, lexicon: m.lexicon, attrs, body });
186
+ packOf.set(n.id, pack);
187
+ lexicons.add(m.lexicon);
188
+ }
189
+ // ---- references inside attribute values
190
+ const byName = new Map();
191
+ for (const v of nodes.values()) {
192
+ const name = v.attrs.name;
193
+ if (typeof name !== "string" || !name)
194
+ continue;
195
+ if (!byName.has(v.label))
196
+ byName.set(v.label, new Map());
197
+ byName.get(v.label).set(name, v.id);
198
+ }
199
+ const refTarget = (from, value) => {
200
+ const pack = packOf.get(from);
201
+ if (value && typeof value === "object") {
202
+ const ref = value.$ref;
203
+ if (typeof ref === "string") {
204
+ if (nodes.has(ref))
205
+ return { id: ref };
206
+ const dot = ref.lastIndexOf(".");
207
+ if (dot > 0 && nodes.has(ref.slice(0, dot)))
208
+ return { id: ref.slice(0, dot), attr: ref.slice(dot + 1) };
209
+ return undefined;
210
+ }
211
+ const sub = value.$sub;
212
+ if (typeof sub === "string") {
213
+ const m = /\$\{([^.}]+)(?:\.([^}]+))?\}/.exec(sub);
214
+ if (m && nodes.has(m[1]))
215
+ return { id: m[1], attr: m[2] };
216
+ }
217
+ return undefined;
218
+ }
219
+ if (typeof value !== "string")
220
+ return undefined;
221
+ if (pack?.mapping.refs === "terraform") {
222
+ const m = /(?:^|[^\w.])((?:data\.)?[a-z][a-z0-9]*_[a-z0-9_]+\.[A-Za-z0-9_-]+)(?:\[[^\]]*\])?\.([A-Za-z_]+)/.exec(value);
223
+ if (m) {
224
+ const id = scopeOf(from) + m[1];
225
+ if (nodes.has(id))
226
+ return { id, attr: m[2] };
227
+ }
228
+ }
229
+ for (const { label, re } of ARN_NAMES) {
230
+ const m = re.exec(value);
231
+ const id = m ? byName.get(label)?.get(m[1]) : undefined;
232
+ if (id)
233
+ return { id };
234
+ }
235
+ return undefined;
236
+ };
237
+ const unresolved = [];
238
+ // An ARN that names nothing declared here is kept for a workspace's composer (#4378).
239
+ const keepArn = (from, fromAttr, edge, value) => {
240
+ if (typeof value === "string" && ARN_NAMES.some(({ re }) => re.test(value)))
241
+ unresolved.push({ from, fromAttr, edge, value });
242
+ };
243
+ const blockEdges = [];
244
+ // ---- inline blocks: ingress rules, inline routes
245
+ for (const v of [...nodes.values()]) {
246
+ const pack = packOf.get(v.id);
247
+ const specs = pack.mapping.blocks?.[v.label];
248
+ if (!specs)
249
+ continue;
250
+ const body = bodyOf(irById.get(v.id), pack);
251
+ for (const spec of specs) {
252
+ readPath(body, spec.attr)
253
+ .flatMap(arr)
254
+ .forEach((raw, i) => {
255
+ if (!raw || typeof raw !== "object" || !matchesWhen(raw, spec.when))
256
+ return;
257
+ const id = `${v.id}#${spec.attr.replace(/\[\]/g, "")}[${i}]`;
258
+ const attrs = { block: true };
259
+ if (spec.style)
260
+ attrs.style = spec.style;
261
+ for (const [neutral, src] of Object.entries(spec.attrs ?? {})) {
262
+ const val = readSource(raw, src);
263
+ if (val !== undefined)
264
+ attrs[neutral] = val;
265
+ }
266
+ normalize(spec.label, attrs);
267
+ nodes.set(id, { id, label: spec.label, irId: v.id, lexicon: v.lexicon, attrs, body: raw });
268
+ packOf.set(id, pack);
269
+ const back = spec.edge.startsWith("-");
270
+ const name = back ? spec.edge.slice(1) : spec.edge;
271
+ const hop = { from: v.id, fromAttr: spec.attr.replace(/\[\]/g, ""), to: id, edge: name };
272
+ blockEdges.push(back ? { from: id, to: v.id, name, hops: [hop] } : { from: v.id, to: id, name, hops: [hop] });
273
+ for (const [attr, edge] of Object.entries(spec.refs ?? {})) {
274
+ for (const val of arr(raw[attr])) {
275
+ const t = refTarget(v.id, val);
276
+ if (!t)
277
+ keepArn(id, attr, edge, val);
278
+ if (t)
279
+ blockEdges.push({ from: id, to: t.id, name: edge, hops: [{ from: id, fromAttr: attr, to: t.id, ...(t.attr ? { toAttr: t.attr } : {}), edge }] });
280
+ }
281
+ }
282
+ });
283
+ }
284
+ }
285
+ // Blocks first: an inline route is read before a separate route resource,
286
+ // the order the folds always used.
287
+ edges.push(...blockEdges);
288
+ // ---- IR edges, named per label
289
+ for (const e of ir.edges ?? []) {
290
+ const from = nodes.get(e.from);
291
+ const to = nodes.get(e.to);
292
+ if (!from || !to)
293
+ continue;
294
+ const pack = packOf.get(e.from);
295
+ const via = e.viaAttr ?? e.kind ?? "";
296
+ const spec = pack.mapping.edges?.[from.label]?.[via];
297
+ const name = typeof spec === "string" ? spec : spec?.[to.label];
298
+ if (!name)
299
+ continue;
300
+ const back = name.startsWith("-");
301
+ const n = back ? name.slice(1) : name;
302
+ const hop = irHop(e, n);
303
+ edges.push(back ? { from: e.to, to: e.from, name: n, hops: [hop] } : { from: e.from, to: e.to, name: n, hops: [hop] });
304
+ }
305
+ // ---- link resources -> one edge
306
+ for (const v of [...nodes.values()]) {
307
+ const link = packOf.get(v.id)?.mapping.links?.[v.label];
308
+ if (!link)
309
+ continue;
310
+ const a = edges.find((e) => e.from === v.id && e.name === link.from);
311
+ const b = edges.find((e) => e.from === v.id && e.name === link.to);
312
+ if (a && b)
313
+ edges.push({ from: a.to, to: b.to, name: link.edge, hops: [...a.hops, ...b.hops] });
314
+ }
315
+ // ---- policy documents -> statements
316
+ for (const holder of [...nodes.values()]) {
317
+ if (holder.attrs.block)
318
+ continue;
319
+ const pack = packOf.get(holder.id);
320
+ const docs = pack.mapping.documents?.[holder.label];
321
+ if (!docs)
322
+ continue;
323
+ const body = bodyOf(irById.get(holder.id), pack);
324
+ for (const [path, docKind] of Object.entries(docs)) {
325
+ readPath(body, path).forEach((raw, di) => {
326
+ const doc = readDocument(raw, pack);
327
+ if (!doc)
328
+ return;
329
+ const base = path.replace(/\[\]/g, "");
330
+ arr(doc.Statement).forEach((s0, si) => {
331
+ if (!s0 || typeof s0 !== "object")
332
+ return;
333
+ const s = s0;
334
+ const id = `${holder.id}#${base}[${di}].Statement[${si}]`;
335
+ const principal = s.Principal;
336
+ const principals = principal === "*"
337
+ ? ["*"]
338
+ : principal && typeof principal === "object"
339
+ ? Object.values(principal).flatMap(arr).map((p) => (typeof p === "string" ? p : "ref"))
340
+ : [];
341
+ const resources = arr(s.Resource);
342
+ const attrs = {
343
+ doc: docKind,
344
+ effect: s.Effect,
345
+ actions: arr(s.Action).map(String),
346
+ ...(s.NotAction !== undefined ? { notActions: arr(s.NotAction).map(String) } : {}),
347
+ ...(s.NotResource !== undefined ? { notResource: true } : {}),
348
+ ...(s.Condition !== undefined ? { condition: true } : {}),
349
+ ...(resources.some((r) => r === "*") ? { anyResource: true } : {}),
350
+ principals,
351
+ };
352
+ nodes.set(id, { id, label: "Statement", irId: holder.id, lexicon: holder.lexicon, attrs });
353
+ packOf.set(id, pack);
354
+ edges.push({ from: holder.id, to: id, name: "statement", hops: [{ from: holder.id, fromAttr: base, to: id, edge: "statement" }] });
355
+ let unresolved = 0;
356
+ resources.forEach((r, ri) => {
357
+ const t = refTarget(holder.id, r);
358
+ // A reference the graph holds but can't name (an intrinsic with
359
+ // no target, a Terraform expression to an undeclared resource) is
360
+ // counted, so an answer can say what it skipped. A `Sub` is read
361
+ // through its `$sub` template (chant#4373).
362
+ if (!t && ((r && typeof r === "object") || (typeof r === "string" && r.includes("${"))))
363
+ unresolved++;
364
+ if (!t)
365
+ keepArn(id, `Resource[${ri}]`, "resource", r);
366
+ if (t)
367
+ edges.push({ from: id, to: t.id, name: "resource", hops: [{ from: id, fromAttr: `Resource[${ri}]`, to: t.id, ...(t.attr ? { toAttr: t.attr } : {}), edge: "resource" }] });
368
+ });
369
+ if (unresolved > 0)
370
+ attrs.unresolved = unresolved;
371
+ if (principal && typeof principal === "object") {
372
+ for (const p of arr(principal.AWS)) {
373
+ const t = refTarget(holder.id, p);
374
+ if (!t)
375
+ keepArn(id, "Principal.AWS", "principal", p);
376
+ if (t)
377
+ edges.push({ from: id, to: t.id, name: "principal", hops: [{ from: id, fromAttr: "Principal.AWS", to: t.id, ...(t.attr ? { toAttr: t.attr } : {}), edge: "principal" }] });
378
+ }
379
+ }
380
+ });
381
+ });
382
+ }
383
+ }
384
+ return { nodes, edges, lexicons, unresolved };
385
+ }
386
+ /** The version of the serialized view a member's `chant graph --capability-view` prints (#4378). */
387
+ export const VIEW_DOCUMENT_VERSION = 1;
388
+ export function serializeView(view) {
389
+ return {
390
+ version: VIEW_DOCUMENT_VERSION,
391
+ lexicons: [...view.lexicons].sort(),
392
+ nodes: [...view.nodes.values()].map((n) => ({ id: n.id, label: n.label, irId: n.irId, lexicon: n.lexicon, attrs: n.attrs })),
393
+ edges: view.edges,
394
+ unresolved: view.unresolved ?? [],
395
+ };
396
+ }
397
+ /** Whether a member printed a view this chant reads; anything else is treated as no view. */
398
+ export function isViewDocument(v) {
399
+ if (!v || typeof v !== "object")
400
+ return false;
401
+ const d = v;
402
+ return d.version === VIEW_DOCUMENT_VERSION && Array.isArray(d.nodes) && Array.isArray(d.edges) && Array.isArray(d.lexicons);
403
+ }
404
+ /** A view document with every id it holds given `prefix` (`<member>/`), as `workspace graph` composes ids. */
405
+ export function prefixView(doc, prefix) {
406
+ const p = (id) => `${prefix}${id}`;
407
+ const hop = (h) => ({ ...h, from: p(h.from), to: p(h.to) });
408
+ return {
409
+ version: doc.version,
410
+ lexicons: doc.lexicons,
411
+ nodes: doc.nodes.map((n) => ({ ...n, id: p(n.id), irId: p(n.irId) })),
412
+ edges: doc.edges.map((e) => ({ ...e, from: p(e.from), to: p(e.to), hops: e.hops.map(hop) })),
413
+ unresolved: (doc.unresolved ?? []).map((u) => ({ ...u, from: p(u.from) })),
414
+ };
415
+ }
416
+ /**
417
+ * Compose member views into one (#4378), the matcher's input over a
418
+ * workspace. Each member's view was mapped by its own chant; the one thing
419
+ * left to do here is resolve the ARNs a member couldn't, by name over every
420
+ * member's nodes. Where several members declare the name, the last one in
421
+ * member order wins, as within one view.
422
+ */
423
+ export function composeViews(docs) {
424
+ const nodes = new Map();
425
+ const edges = [];
426
+ const lexicons = new Set();
427
+ for (const d of docs) {
428
+ for (const n of d.nodes)
429
+ nodes.set(n.id, { ...n });
430
+ edges.push(...d.edges);
431
+ for (const l of d.lexicons)
432
+ lexicons.add(l);
433
+ }
434
+ const byName = new Map();
435
+ for (const v of nodes.values()) {
436
+ const name = v.attrs.name;
437
+ if (typeof name !== "string" || !name)
438
+ continue;
439
+ if (!byName.has(v.label))
440
+ byName.set(v.label, new Map());
441
+ byName.get(v.label).set(name, v.id);
442
+ }
443
+ const unresolved = [];
444
+ for (const u of docs.flatMap((d) => d.unresolved ?? [])) {
445
+ let to;
446
+ for (const { label, re } of ARN_NAMES) {
447
+ const m = re.exec(u.value);
448
+ to = m ? byName.get(label)?.get(m[1]) : undefined;
449
+ if (to)
450
+ break;
451
+ }
452
+ if (to)
453
+ edges.push({ from: u.from, to, name: u.edge, hops: [{ from: u.from, fromAttr: u.fromAttr, to, edge: u.edge }] });
454
+ else
455
+ unresolved.push(u);
456
+ }
457
+ return { nodes, edges, lexicons, unresolved };
458
+ }
459
+ function irHop(e, name) {
460
+ return {
461
+ from: e.from,
462
+ ...(e.viaAttr ? { fromAttr: e.viaAttr } : {}),
463
+ to: e.to,
464
+ ...(e.toAttr ? { toAttr: e.toAttr } : {}),
465
+ edge: name,
466
+ };
467
+ }
468
+ function readDocument(raw, pack) {
469
+ if (pack.adapter?.readDocument)
470
+ return pack.adapter.readDocument(raw);
471
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
472
+ const inner = raw.PolicyDocument;
473
+ return inner && typeof inner === "object" ? inner : raw;
474
+ }
475
+ if (typeof raw === "string") {
476
+ try {
477
+ return JSON.parse(raw);
478
+ }
479
+ catch {
480
+ return undefined;
481
+ }
482
+ }
483
+ return undefined;
484
+ }