@datasynx/agentic-ai-cartography 2.9.0 → 2.11.0

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.
@@ -3391,6 +3391,7 @@ export {
3391
3391
  k8sScanner,
3392
3392
  databasesScanner,
3393
3393
  stripSensitive,
3394
+ redactSecrets,
3394
3395
  redactValue,
3395
3396
  buildCartographyToolHandlers,
3396
3397
  createCartographyTools,
@@ -3414,4 +3415,4 @@ export {
3414
3415
  AuthorizationError,
3415
3416
  authorize
3416
3417
  };
3417
- //# sourceMappingURL=chunk-YVV6NIT2.js.map
3418
+ //# sourceMappingURL=chunk-LO6YFS6H.js.map
@@ -10,7 +10,7 @@ import {
10
10
  defaultAllowedHosts,
11
11
  normalizeTenant,
12
12
  resolvePrincipal
13
- } from "./chunk-YVV6NIT2.js";
13
+ } from "./chunk-LO6YFS6H.js";
14
14
  import {
15
15
  ANOMALY_KINDS,
16
16
  ANOMALY_SEVERITIES,
@@ -1409,4 +1409,4 @@ export {
1409
1409
  parseApiArgs,
1410
1410
  startApi
1411
1411
  };
1412
- //# sourceMappingURL=chunk-W4Q3TXHR.js.map
1412
+ //# sourceMappingURL=chunk-PD67MOKR.js.map
package/dist/cli.js CHANGED
@@ -11,12 +11,12 @@ import {
11
11
  runDrift,
12
12
  runLocalDiscovery,
13
13
  startMcp
14
- } from "./chunk-LRUWWHMQ.js";
14
+ } from "./chunk-FFUUNSWP.js";
15
15
  import {
16
16
  entitiesToYaml,
17
17
  startApi,
18
18
  toBackstageEntities
19
- } from "./chunk-W4Q3TXHR.js";
19
+ } from "./chunk-PD67MOKR.js";
20
20
  import {
21
21
  CartographyDB,
22
22
  buildCartographyToolHandlers,
@@ -28,7 +28,7 @@ import {
28
28
  redactValue,
29
29
  stableStringify,
30
30
  stripSensitive
31
- } from "./chunk-YVV6NIT2.js";
31
+ } from "./chunk-LO6YFS6H.js";
32
32
  import {
33
33
  ConfigFileSchema,
34
34
  CostEntrySchema,
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(src_exports, {
36
36
  AuthorizationError: () => AuthorizationError,
37
37
  CLIENTS: () => CLIENTS,
38
38
  CONFIDENCE: () => CONFIDENCE,
39
+ CORRELATION_CONFIDENCE: () => CORRELATION_CONFIDENCE,
39
40
  CartographyDB: () => CartographyDB,
40
41
  ComplianceReportSchema: () => ComplianceReportSchema,
41
42
  ComplianceRuleSchema: () => ComplianceRuleSchema,
@@ -122,6 +123,7 @@ __export(src_exports, {
122
123
  computeIdentity: () => computeIdentity,
123
124
  connectionsScanner: () => connectionsScanner,
124
125
  contentHash: () => contentHash,
126
+ correlateTopology: () => correlateTopology,
125
127
  createBashTool: () => createBashTool,
126
128
  createCartographyTools: () => createCartographyTools,
127
129
  createClaudeProvider: () => createClaudeProvider,
@@ -169,6 +171,7 @@ __export(src_exports, {
169
171
  exportJGF: () => exportJGF,
170
172
  exportJSON: () => exportJSON,
171
173
  extractListeningPorts: () => extractListeningPorts,
174
+ extractSignals: () => extractSignals,
172
175
  filterBySeverity: () => filterBySeverity,
173
176
  findAnonViolations: () => findAnonViolations,
174
177
  formatComplianceText: () => formatComplianceText,
@@ -235,6 +238,7 @@ __export(src_exports, {
235
238
  parseNginxUpstreams: () => parseNginxUpstreams,
236
239
  parseNlQuery: () => parseNlQuery,
237
240
  parseScanHint: () => parseScanHint,
241
+ parseTerraformState: () => parseTerraformState,
238
242
  pixelToHex: () => pixelToHex,
239
243
  planInstall: () => planInstall,
240
244
  portsScanner: () => portsScanner,
@@ -284,6 +288,8 @@ __export(src_exports, {
284
288
  stableStringify: () => stableStringify,
285
289
  startApi: () => startApi,
286
290
  stripSensitive: () => stripSensitive,
291
+ terraformScanner: () => terraformScanner,
292
+ terraformTypeToNode: () => terraformTypeToNode,
287
293
  timingSafeEqual: () => timingSafeEqual,
288
294
  toBackstageEntities: () => toBackstageEntities,
289
295
  validateScanner: () => validateScanner,
@@ -6134,9 +6140,146 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
6134
6140
  return executeNlQuery(db, sessionId, search, parseNlQuery(raw), opts);
6135
6141
  }
6136
6142
 
6143
+ // src/correlation/signals.ts
6144
+ var IPV4 = /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g;
6145
+ var DNS = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.){1,8}[a-z]{2,24}\b/gi;
6146
+ var ENDPOINT = /\b((?:[a-z0-9][a-z0-9.-]{0,253})):(\d{1,5})\b/gi;
6147
+ function isPrivateIp(ip) {
6148
+ return /^(?:10\.|127\.|169\.254\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(ip);
6149
+ }
6150
+ function sources(node) {
6151
+ const out = [node.id, node.name];
6152
+ const meta = node.metadata;
6153
+ if (meta) {
6154
+ for (const k of ["host", "hostname", "ip", "address", "dns", "dnsName", "endpoint", "fqdn", "publicIp", "privateIp", "url"]) {
6155
+ const v = meta[k];
6156
+ if (typeof v === "string") out.push(v);
6157
+ }
6158
+ }
6159
+ return out;
6160
+ }
6161
+ var KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["aws", "gcp", "azure", "k8s", "kubernetes", "localhost", "docker"]);
6162
+ function parseProvider(id) {
6163
+ const seg = id.split(":");
6164
+ if (seg.length >= 3 && KNOWN_PROVIDERS.has(seg[1])) return seg[1];
6165
+ return void 0;
6166
+ }
6167
+ function extractSignals(node) {
6168
+ const text = sources(node).join(" \n ");
6169
+ const publicIps = /* @__PURE__ */ new Set();
6170
+ const privateIps = /* @__PURE__ */ new Set();
6171
+ const dnsNames = /* @__PURE__ */ new Set();
6172
+ const endpoints = /* @__PURE__ */ new Set();
6173
+ for (const m of text.matchAll(IPV4)) (isPrivateIp(m[0]) ? privateIps : publicIps).add(m[0]);
6174
+ for (const m of text.matchAll(DNS)) dnsNames.add(m[0].toLowerCase());
6175
+ for (const m of text.matchAll(ENDPOINT)) endpoints.add(`${m[1].toLowerCase()}:${m[2]}`);
6176
+ const provider = parseProvider(node.id);
6177
+ const sortUniq = (s) => [...s].sort();
6178
+ return {
6179
+ ...provider ? { provider } : {},
6180
+ endpoints: sortUniq(endpoints),
6181
+ publicIps: sortUniq(publicIps),
6182
+ privateIps: sortUniq(privateIps),
6183
+ // The DNS regex requires an alphabetic TLD, so pure IPv4s never land here.
6184
+ dnsNames: sortUniq(dnsNames)
6185
+ };
6186
+ }
6187
+
6188
+ // src/correlation/correlate.ts
6189
+ var CORRELATION_CONFIDENCE = {
6190
+ "global-identity": 1,
6191
+ "dns-name": 0.95,
6192
+ "public-ip": 0.9,
6193
+ "endpoint": 0.85,
6194
+ "private-ip": 0.5
6195
+ };
6196
+ var MERGE_THRESHOLD = 0.85;
6197
+ var DSU = class {
6198
+ parent;
6199
+ constructor(n) {
6200
+ this.parent = Array.from({ length: n }, (_, i) => i);
6201
+ }
6202
+ find(x) {
6203
+ while (this.parent[x] !== x) {
6204
+ this.parent[x] = this.parent[this.parent[x]];
6205
+ x = this.parent[x];
6206
+ }
6207
+ return x;
6208
+ }
6209
+ union(a, b) {
6210
+ const ra = this.find(a), rb = this.find(b);
6211
+ if (ra !== rb) this.parent[Math.max(ra, rb)] = Math.min(ra, rb);
6212
+ }
6213
+ };
6214
+ function correlateTopology(nodes, _edges = []) {
6215
+ const n = nodes.length;
6216
+ const signals = nodes.map(extractSignals);
6217
+ const dsu = new DSU(n);
6218
+ const correlations = [];
6219
+ const merged = /* @__PURE__ */ new Set();
6220
+ const buckets = /* @__PURE__ */ new Map();
6221
+ const add = (sig, val, i) => {
6222
+ const k = `${sig}|${val}`;
6223
+ const arr = buckets.get(k);
6224
+ if (arr) arr.push(i);
6225
+ else buckets.set(k, [i]);
6226
+ };
6227
+ nodes.forEach((node, i) => {
6228
+ if (node.globalId) add("global-identity", node.globalId, i);
6229
+ for (const d of signals[i].dnsNames) add("dns-name", d, i);
6230
+ for (const ip of signals[i].publicIps) add("public-ip", ip, i);
6231
+ for (const e of signals[i].endpoints) add("endpoint", e, i);
6232
+ });
6233
+ const order = ["global-identity", "dns-name", "public-ip", "endpoint"];
6234
+ for (const sig of order) {
6235
+ const conf = CORRELATION_CONFIDENCE[sig];
6236
+ const keys = [...buckets.keys()].filter((k) => k.startsWith(`${sig}|`)).sort();
6237
+ for (const key of keys) {
6238
+ const members = buckets.get(key).slice().sort((x, y) => nodes[x].id.localeCompare(nodes[y].id));
6239
+ const value = key.slice(sig.length + 1);
6240
+ for (let j = 1; j < members.length; j++) {
6241
+ const a = members[0], b = members[j];
6242
+ if (conf < MERGE_THRESHOLD) continue;
6243
+ dsu.union(a, b);
6244
+ merged.add(a);
6245
+ merged.add(b);
6246
+ const [s, t] = [nodes[a].id, nodes[b].id].sort();
6247
+ correlations.push({ sourceId: s, targetId: t, relationship: "same_as", signal: sig, confidence: conf, evidence: `[${sig}] shared ${value}` });
6248
+ }
6249
+ }
6250
+ }
6251
+ const clusters = /* @__PURE__ */ new Map();
6252
+ for (let i = 0; i < n; i++) {
6253
+ const r = dsu.find(i);
6254
+ const arr = clusters.get(r);
6255
+ if (arr) arr.push(i);
6256
+ else clusters.set(r, [i]);
6257
+ }
6258
+ const canonical = [];
6259
+ let crossCloud = 0;
6260
+ for (const idxs of clusters.values()) {
6261
+ const memberNodes = idxs.map((i) => nodes[i]);
6262
+ const members = memberNodes.map((m) => m.id).sort();
6263
+ const providers = [...new Set(idxs.map((i) => signals[i].provider).filter((p) => !!p))].sort();
6264
+ const rep = memberNodes.reduce((a, b) => a.id < b.id ? a : b);
6265
+ const memberIds = new Set(members);
6266
+ const internal = correlations.filter((c) => memberIds.has(c.sourceId) && memberIds.has(c.targetId));
6267
+ const confidence = internal.length ? Math.min(...internal.map((c) => c.confidence)) : 1;
6268
+ if (providers.length > 1) crossCloud += 1;
6269
+ canonical.push({ id: rep.id, type: rep.type, name: rep.name, members, providers, confidence });
6270
+ }
6271
+ canonical.sort((a, b) => a.id.localeCompare(b.id));
6272
+ correlations.sort((a, b) => b.confidence - a.confidence || a.sourceId.localeCompare(b.sourceId) || a.targetId.localeCompare(b.targetId));
6273
+ return {
6274
+ canonical,
6275
+ correlations,
6276
+ summary: { rawNodes: n, canonicalNodes: canonical.length, collapsed: n - canonical.length, crossCloud }
6277
+ };
6278
+ }
6279
+
6137
6280
  // src/mcp/server.ts
6138
6281
  var SERVER_NAME = "cartography";
6139
- var SERVER_VERSION = "2.9.0";
6282
+ var SERVER_VERSION = "2.11.0";
6140
6283
  var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
6141
6284
  var DATA_TYPES = NODE_TYPE_GROUPS.data;
6142
6285
  var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
@@ -6296,6 +6439,15 @@ function createMcpServer(opts = {}) {
6296
6439
  return json(db.getGraphSummary(sid));
6297
6440
  }
6298
6441
  );
6442
+ server.registerTool(
6443
+ "correlate_topology",
6444
+ { title: "Correlate multi-cloud topology", description: "Collapse the same logical resource discovered across clouds/on-prem (by global identity or a shared DNS name / public IP / endpoint) into canonical entities with confidence-scored same_as links. Read-only, non-destructive (5.1).", inputSchema: {}, annotations: readOnly },
6445
+ () => {
6446
+ const sid = resolveSession();
6447
+ if (!sid) return json({ error: "No discovery session found." });
6448
+ return json(correlateTopology(db.getNodes(sid), db.getEdges(sid)));
6449
+ }
6450
+ );
6299
6451
  server.registerTool(
6300
6452
  "get_cost_summary",
6301
6453
  { title: "Get cost summary", description: "FinOps rollup: cost by domain and owner, currency/period-bucketed (3.3).", inputSchema: {}, annotations: readOnly },
@@ -7537,9 +7689,132 @@ var serviceConfigScanner = {
7537
7689
  }
7538
7690
  };
7539
7691
 
7692
+ // src/scanners/terraform.ts
7693
+ var import_node_fs5 = require("fs");
7694
+ var TYPE_RULES = [
7695
+ [/(db_instance|_rds|sql_database|sql_instance|database_instance|cosmosdb|dynamodb|spanner|bigtable|documentdb|redshift)/, "database_server"],
7696
+ [/(elasticache|_redis|memcached|memorystore)/, "cache_server"],
7697
+ [/(s3_bucket|storage_bucket|gcs_bucket|storage_account|_blob)/, "database"],
7698
+ [/(_sqs|_queue|servicebus_queue)/, "queue"],
7699
+ [/(_sns|_topic|pubsub_topic|servicebus_topic)/, "topic"],
7700
+ [/(kafka|_msk|event_hub|kinesis)/, "message_broker"],
7701
+ [/(_eks|_gke|_aks|kubernetes_cluster|container_cluster)/, "k8s_cluster"],
7702
+ [/(ecs_|_container|fargate)/, "container"],
7703
+ [/(lambda|cloud_function|cloudfunctions|function_app|cloud_run)/, "web_service"],
7704
+ [/(_lb$|load_balancer|_alb|_elb|application_gateway)/, "web_service"],
7705
+ [/(api_gateway|apigateway)/, "api_endpoint"],
7706
+ [/(_instance|virtual_machine|_vm$|compute_instance)/, "host"]
7707
+ ];
7708
+ function terraformTypeToNode(tfType) {
7709
+ const t = tfType.toLowerCase();
7710
+ for (const [re, nt] of TYPE_RULES) if (re.test(t)) return nt;
7711
+ return "unknown";
7712
+ }
7713
+ var IDENTITY_ATTRS = ["id", "arn", "region", "location", "instance_type", "engine", "machine_type"];
7714
+ var OWNER_TAGS = ["Owner", "owner", "Team", "team"];
7715
+ var SAFE_TAG_KEYS = /* @__PURE__ */ new Set(["Name", "name", "Owner", "owner", "Team", "team", "Env", "env", "Environment", "environment", "Service", "service", "Component", "component", "App", "app", "Project", "project", "Tier", "tier", "Role", "role"]);
7716
+ var SECRET_KEY = /pass|secret|token|key|pwd|cred|private/i;
7717
+ function attrTags(tags) {
7718
+ if (!tags || typeof tags !== "object") return [];
7719
+ return Object.entries(tags).filter(([k]) => SAFE_TAG_KEYS.has(k) && !SECRET_KEY.test(k)).map(([k, v]) => `${k}:${redactSecrets(String(v))}`);
7720
+ }
7721
+ function parseTerraformState(json2) {
7722
+ let state;
7723
+ try {
7724
+ state = JSON.parse(json2);
7725
+ } catch {
7726
+ return { nodes: [], edges: [] };
7727
+ }
7728
+ const resources = Array.isArray(state?.resources) ? state.resources : [];
7729
+ const nodes = [];
7730
+ const edges = [];
7731
+ const addrToId = /* @__PURE__ */ new Map();
7732
+ for (const raw of resources) {
7733
+ const r = raw;
7734
+ if (r.mode && r.mode !== "managed") continue;
7735
+ if (typeof r.type !== "string" || typeof r.name !== "string") continue;
7736
+ const address = `${r.type}.${r.name}`;
7737
+ const nt = terraformTypeToNode(r.type);
7738
+ const id = `${nt}:terraform:${address}`;
7739
+ if (addrToId.has(address)) continue;
7740
+ addrToId.set(address, id);
7741
+ const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
7742
+ const attrs = inst?.attributes ?? {};
7743
+ const identity = { source: "terraform", tfType: r.type };
7744
+ for (const k of IDENTITY_ATTRS) if (attrs[k] !== void 0) identity[k] = attrs[k];
7745
+ const owner = OWNER_TAGS.map((k) => attrs.tags?.[k]).find((v) => typeof v === "string");
7746
+ nodes.push({
7747
+ id,
7748
+ type: nt,
7749
+ name: address,
7750
+ discoveredVia: "terraform-state",
7751
+ confidence: 0.9,
7752
+ // IaC is authoritative declared intent.
7753
+ metadata: redactValue(identity),
7754
+ tags: attrTags(attrs.tags),
7755
+ ...owner ? { owner } : {}
7756
+ });
7757
+ }
7758
+ for (const raw of resources) {
7759
+ const r = raw;
7760
+ if (r.mode && r.mode !== "managed") continue;
7761
+ if (typeof r.type !== "string" || typeof r.name !== "string") continue;
7762
+ const srcId = addrToId.get(`${r.type}.${r.name}`);
7763
+ if (!srcId) continue;
7764
+ const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
7765
+ const deps = Array.isArray(inst?.dependencies) ? inst.dependencies : [];
7766
+ for (const dep of deps) {
7767
+ if (typeof dep !== "string") continue;
7768
+ const tgtId = addrToId.get(dep) ?? addrToId.get(dep.split("[")[0]);
7769
+ if (!tgtId || tgtId === srcId) continue;
7770
+ edges.push({ sourceId: srcId, targetId: tgtId, relationship: "depends_on", evidence: evidenceLine("config-declared", `terraform depends_on ${dep}`), confidence: 0.85 });
7771
+ }
7772
+ }
7773
+ return { nodes, edges };
7774
+ }
7775
+ function stateDirs() {
7776
+ return [".", "./terraform", "./infra", "./infrastructure", "./deploy", "./terraform/environments"];
7777
+ }
7778
+ function hintPath(hint) {
7779
+ if (!hint) return void 0;
7780
+ const m = /(?:^|[\s,])tfstate=([^\s,]+)/.exec(hint);
7781
+ return m ? m[1] : void 0;
7782
+ }
7783
+ function resolveStatePath(ctx) {
7784
+ const explicit = hintPath(ctx.hint);
7785
+ if (explicit) return explicit;
7786
+ const found = (ctx.findFiles ?? findFiles)(stateDirs(), ["*.tfstate"], 4, 20).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
7787
+ return found[0];
7788
+ }
7789
+ function readStateFile(path) {
7790
+ try {
7791
+ return (0, import_node_fs5.readFileSync)(path, "utf8");
7792
+ } catch {
7793
+ return "";
7794
+ }
7795
+ }
7796
+ var terraformScanner = {
7797
+ id: "terraform-state",
7798
+ title: "Terraform state (IaC)",
7799
+ platforms: "all",
7800
+ // No shell commands — the state file is read directly via node:fs, so an
7801
+ // operator-supplied path can never inject a command (no `cat "${path}"` interpolation).
7802
+ detect(ctx) {
7803
+ return resolveStatePath(ctx) !== void 0;
7804
+ },
7805
+ async scan(ctx) {
7806
+ const path = resolveStatePath(ctx);
7807
+ if (!path) return { nodes: [], edges: [] };
7808
+ const json2 = (ctx.readFile ?? readStateFile)(path);
7809
+ if (!json2) return { nodes: [], edges: [] };
7810
+ const result = parseTerraformState(json2);
7811
+ return { ...result, report: `terraform-state: ${result.nodes.length} resources, ${result.edges.length} dependencies from ${path}` };
7812
+ }
7813
+ };
7814
+
7540
7815
  // src/scanners/registry.ts
7541
7816
  function defaultRegistry() {
7542
- return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner);
7817
+ return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner).register(terraformScanner);
7543
7818
  }
7544
7819
 
7545
7820
  // src/scanners/loader.ts
@@ -8907,14 +9182,14 @@ var AuthConfigSchema = import_zod9.z.object({
8907
9182
  });
8908
9183
 
8909
9184
  // src/api/start.ts
8910
- var import_node_fs5 = require("fs");
9185
+ var import_node_fs6 = require("fs");
8911
9186
  var import_node_path5 = require("path");
8912
9187
  var import_node_url = require("url");
8913
9188
  var import_meta = {};
8914
9189
  function readVersion() {
8915
9190
  try {
8916
9191
  const dir = import_meta.dirname ?? (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
8917
- return JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path5.resolve)(dir, "..", "package.json"), "utf-8")).version ?? "0.0.0";
9192
+ return JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.resolve)(dir, "..", "package.json"), "utf-8")).version ?? "0.0.0";
8918
9193
  } catch {
8919
9194
  return "0.0.0";
8920
9195
  }
@@ -9045,7 +9320,7 @@ function defaultServerEntry(opts = {}) {
9045
9320
  }
9046
9321
 
9047
9322
  // src/installer/install.ts
9048
- var import_node_fs6 = require("fs");
9323
+ var import_node_fs7 = require("fs");
9049
9324
  var import_node_path6 = require("path");
9050
9325
  var import_node_os4 = require("os");
9051
9326
  function currentOs() {
@@ -9061,8 +9336,8 @@ function planInstall(spec, ctx, opts) {
9061
9336
  if (!path) {
9062
9337
  throw new Error(`${spec.label} does not support the "${ctx.scope}" scope.`);
9063
9338
  }
9064
- const fileExists = (0, import_node_fs6.existsSync)(path);
9065
- const before = fileExists ? (0, import_node_fs6.readFileSync)(path, "utf8") : "";
9339
+ const fileExists = (0, import_node_fs7.existsSync)(path);
9340
+ const before = fileExists ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
9066
9341
  const existing = parseConfig(before, spec.format);
9067
9342
  const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);
9068
9343
  const after = serializeConfig(merged, spec.format);
@@ -9079,8 +9354,8 @@ function planInstall(spec, ctx, opts) {
9079
9354
  };
9080
9355
  }
9081
9356
  function applyInstall(plan) {
9082
- (0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(plan.path), { recursive: true });
9083
- (0, import_node_fs6.writeFileSync)(plan.path, plan.after, "utf8");
9357
+ (0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(plan.path), { recursive: true });
9358
+ (0, import_node_fs7.writeFileSync)(plan.path, plan.after, "utf8");
9084
9359
  }
9085
9360
  function renderDiff(before, after) {
9086
9361
  if (before === after) return " (no changes)";
@@ -9928,7 +10203,7 @@ Use ask_user when you need context from the user.`;
9928
10203
  }
9929
10204
 
9930
10205
  // src/cost.ts
9931
- var import_node_fs7 = require("fs");
10206
+ var import_node_fs8 = require("fs");
9932
10207
  var import_node_path8 = require("path");
9933
10208
  function splitCsvLine(line) {
9934
10209
  const out = [];
@@ -10007,7 +10282,7 @@ var CsvCostSource = class {
10007
10282
  }
10008
10283
  id;
10009
10284
  async fetch() {
10010
- const text = (0, import_node_fs7.readFileSync)((0, import_node_path8.resolve)(this.opts.filePath), "utf-8");
10285
+ const text = (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(this.opts.filePath), "utf-8");
10011
10286
  const records = parseCostCsv(text);
10012
10287
  const match = this.opts.match ?? "nodeId";
10013
10288
  const out = /* @__PURE__ */ new Map();
@@ -10049,7 +10324,7 @@ async function enrichCosts(db, sessionId, source) {
10049
10324
  }
10050
10325
 
10051
10326
  // src/exporter.ts
10052
- var import_node_fs8 = require("fs");
10327
+ var import_node_fs9 = require("fs");
10053
10328
  var import_node_path9 = require("path");
10054
10329
 
10055
10330
  // src/hex.ts
@@ -11719,28 +11994,28 @@ function exportComplianceReport(report, format) {
11719
11994
  return lines.join("\n");
11720
11995
  }
11721
11996
  function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "map", "discovery"]) {
11722
- (0, import_node_fs8.mkdirSync)(outputDir, { recursive: true });
11997
+ (0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
11723
11998
  const nodes = db.getNodes(sessionId);
11724
11999
  const edges = db.getEdges(sessionId);
11725
12000
  const jgfPath = (0, import_node_path9.join)(outputDir, "cartography-graph.jgf.json");
11726
- (0, import_node_fs8.writeFileSync)(jgfPath, exportJGF(nodes, edges));
12001
+ (0, import_node_fs9.writeFileSync)(jgfPath, exportJGF(nodes, edges));
11727
12002
  if (formats.includes("mermaid")) {
11728
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
11729
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
12003
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
12004
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
11730
12005
  }
11731
12006
  if (formats.includes("json")) {
11732
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog.json"), exportJSON(db, sessionId));
12007
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog.json"), exportJSON(db, sessionId));
11733
12008
  }
11734
12009
  if (formats.includes("yaml")) {
11735
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
12010
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
11736
12011
  }
11737
12012
  if (formats.includes("html") || formats.includes("map") || formats.includes("discovery")) {
11738
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
12013
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
11739
12014
  }
11740
12015
  if (formats.includes("cost")) {
11741
12016
  const summary = db.getGraphSummary(sessionId);
11742
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-by-domain.csv"), exportCostCSV(summary));
11743
- (0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-summary.json"), exportCostSummary(summary));
12017
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-by-domain.csv"), exportCostCSV(summary));
12018
+ (0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-summary.json"), exportCostSummary(summary));
11744
12019
  }
11745
12020
  }
11746
12021
 
@@ -11772,7 +12047,7 @@ function formatComplianceText(report) {
11772
12047
  }
11773
12048
 
11774
12049
  // src/config.ts
11775
- var import_node_fs9 = require("fs");
12050
+ var import_node_fs10 = require("fs");
11776
12051
  var ConfigError = class extends Error {
11777
12052
  constructor(message) {
11778
12053
  super(message);
@@ -11797,7 +12072,7 @@ function loadConfig(path) {
11797
12072
  function readConfigFile(path) {
11798
12073
  let raw;
11799
12074
  try {
11800
- raw = (0, import_node_fs9.readFileSync)(path, "utf-8");
12075
+ raw = (0, import_node_fs10.readFileSync)(path, "utf-8");
11801
12076
  } catch (err) {
11802
12077
  throw new ConfigError(
11803
12078
  `Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`
@@ -12155,14 +12430,14 @@ function runSyncClassify(db, sessionId, config, opts = {}) {
12155
12430
 
12156
12431
  // src/preflight.ts
12157
12432
  var import_node_child_process2 = require("child_process");
12158
- var import_node_fs10 = require("fs");
12433
+ var import_node_fs11 = require("fs");
12159
12434
  var import_node_path10 = require("path");
12160
12435
  function isOAuthLoggedIn() {
12161
12436
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
12162
12437
  const credFile = (0, import_node_path10.join)(home, ".claude", ".credentials.json");
12163
- if (!(0, import_node_fs10.existsSync)(credFile)) return false;
12438
+ if (!(0, import_node_fs11.existsSync)(credFile)) return false;
12164
12439
  try {
12165
- const creds = JSON.parse((0, import_node_fs10.readFileSync)(credFile, "utf8"));
12440
+ const creds = JSON.parse((0, import_node_fs11.readFileSync)(credFile, "utf8"));
12166
12441
  const oauth = creds["claudeAiOauth"];
12167
12442
  return typeof oauth?.["accessToken"] === "string" && oauth["accessToken"].length > 0;
12168
12443
  } catch {
@@ -12220,6 +12495,7 @@ function checkClaudePrerequisites() {
12220
12495
  AuthorizationError,
12221
12496
  CLIENTS,
12222
12497
  CONFIDENCE,
12498
+ CORRELATION_CONFIDENCE,
12223
12499
  CartographyDB,
12224
12500
  ComplianceReportSchema,
12225
12501
  ComplianceRuleSchema,
@@ -12306,6 +12582,7 @@ function checkClaudePrerequisites() {
12306
12582
  computeIdentity,
12307
12583
  connectionsScanner,
12308
12584
  contentHash,
12585
+ correlateTopology,
12309
12586
  createBashTool,
12310
12587
  createCartographyTools,
12311
12588
  createClaudeProvider,
@@ -12353,6 +12630,7 @@ function checkClaudePrerequisites() {
12353
12630
  exportJGF,
12354
12631
  exportJSON,
12355
12632
  extractListeningPorts,
12633
+ extractSignals,
12356
12634
  filterBySeverity,
12357
12635
  findAnonViolations,
12358
12636
  formatComplianceText,
@@ -12419,6 +12697,7 @@ function checkClaudePrerequisites() {
12419
12697
  parseNginxUpstreams,
12420
12698
  parseNlQuery,
12421
12699
  parseScanHint,
12700
+ parseTerraformState,
12422
12701
  pixelToHex,
12423
12702
  planInstall,
12424
12703
  portsScanner,
@@ -12468,6 +12747,8 @@ function checkClaudePrerequisites() {
12468
12747
  stableStringify,
12469
12748
  startApi,
12470
12749
  stripSensitive,
12750
+ terraformScanner,
12751
+ terraformTypeToNode,
12471
12752
  timingSafeEqual,
12472
12753
  toBackstageEntities,
12473
12754
  validateScanner,