@datasynx/agentic-ai-cartography 2.9.0 → 2.10.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.
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  runDrift,
12
12
  runLocalDiscovery,
13
13
  startMcp
14
- } from "./chunk-LRUWWHMQ.js";
14
+ } from "./chunk-ASCA3UFM.js";
15
15
  import {
16
16
  entitiesToYaml,
17
17
  startApi,
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,
@@ -6134,9 +6137,146 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
6134
6137
  return executeNlQuery(db, sessionId, search, parseNlQuery(raw), opts);
6135
6138
  }
6136
6139
 
6140
+ // src/correlation/signals.ts
6141
+ 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;
6142
+ var DNS = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.){1,8}[a-z]{2,24}\b/gi;
6143
+ var ENDPOINT = /\b((?:[a-z0-9][a-z0-9.-]{0,253})):(\d{1,5})\b/gi;
6144
+ function isPrivateIp(ip) {
6145
+ return /^(?:10\.|127\.|169\.254\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(ip);
6146
+ }
6147
+ function sources(node) {
6148
+ const out = [node.id, node.name];
6149
+ const meta = node.metadata;
6150
+ if (meta) {
6151
+ for (const k of ["host", "hostname", "ip", "address", "dns", "dnsName", "endpoint", "fqdn", "publicIp", "privateIp", "url"]) {
6152
+ const v = meta[k];
6153
+ if (typeof v === "string") out.push(v);
6154
+ }
6155
+ }
6156
+ return out;
6157
+ }
6158
+ var KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["aws", "gcp", "azure", "k8s", "kubernetes", "localhost", "docker"]);
6159
+ function parseProvider(id) {
6160
+ const seg = id.split(":");
6161
+ if (seg.length >= 3 && KNOWN_PROVIDERS.has(seg[1])) return seg[1];
6162
+ return void 0;
6163
+ }
6164
+ function extractSignals(node) {
6165
+ const text = sources(node).join(" \n ");
6166
+ const publicIps = /* @__PURE__ */ new Set();
6167
+ const privateIps = /* @__PURE__ */ new Set();
6168
+ const dnsNames = /* @__PURE__ */ new Set();
6169
+ const endpoints = /* @__PURE__ */ new Set();
6170
+ for (const m of text.matchAll(IPV4)) (isPrivateIp(m[0]) ? privateIps : publicIps).add(m[0]);
6171
+ for (const m of text.matchAll(DNS)) dnsNames.add(m[0].toLowerCase());
6172
+ for (const m of text.matchAll(ENDPOINT)) endpoints.add(`${m[1].toLowerCase()}:${m[2]}`);
6173
+ const provider = parseProvider(node.id);
6174
+ const sortUniq = (s) => [...s].sort();
6175
+ return {
6176
+ ...provider ? { provider } : {},
6177
+ endpoints: sortUniq(endpoints),
6178
+ publicIps: sortUniq(publicIps),
6179
+ privateIps: sortUniq(privateIps),
6180
+ // The DNS regex requires an alphabetic TLD, so pure IPv4s never land here.
6181
+ dnsNames: sortUniq(dnsNames)
6182
+ };
6183
+ }
6184
+
6185
+ // src/correlation/correlate.ts
6186
+ var CORRELATION_CONFIDENCE = {
6187
+ "global-identity": 1,
6188
+ "dns-name": 0.95,
6189
+ "public-ip": 0.9,
6190
+ "endpoint": 0.85,
6191
+ "private-ip": 0.5
6192
+ };
6193
+ var MERGE_THRESHOLD = 0.85;
6194
+ var DSU = class {
6195
+ parent;
6196
+ constructor(n) {
6197
+ this.parent = Array.from({ length: n }, (_, i) => i);
6198
+ }
6199
+ find(x) {
6200
+ while (this.parent[x] !== x) {
6201
+ this.parent[x] = this.parent[this.parent[x]];
6202
+ x = this.parent[x];
6203
+ }
6204
+ return x;
6205
+ }
6206
+ union(a, b) {
6207
+ const ra = this.find(a), rb = this.find(b);
6208
+ if (ra !== rb) this.parent[Math.max(ra, rb)] = Math.min(ra, rb);
6209
+ }
6210
+ };
6211
+ function correlateTopology(nodes, _edges = []) {
6212
+ const n = nodes.length;
6213
+ const signals = nodes.map(extractSignals);
6214
+ const dsu = new DSU(n);
6215
+ const correlations = [];
6216
+ const merged = /* @__PURE__ */ new Set();
6217
+ const buckets = /* @__PURE__ */ new Map();
6218
+ const add = (sig, val, i) => {
6219
+ const k = `${sig}|${val}`;
6220
+ const arr = buckets.get(k);
6221
+ if (arr) arr.push(i);
6222
+ else buckets.set(k, [i]);
6223
+ };
6224
+ nodes.forEach((node, i) => {
6225
+ if (node.globalId) add("global-identity", node.globalId, i);
6226
+ for (const d of signals[i].dnsNames) add("dns-name", d, i);
6227
+ for (const ip of signals[i].publicIps) add("public-ip", ip, i);
6228
+ for (const e of signals[i].endpoints) add("endpoint", e, i);
6229
+ });
6230
+ const order = ["global-identity", "dns-name", "public-ip", "endpoint"];
6231
+ for (const sig of order) {
6232
+ const conf = CORRELATION_CONFIDENCE[sig];
6233
+ const keys = [...buckets.keys()].filter((k) => k.startsWith(`${sig}|`)).sort();
6234
+ for (const key of keys) {
6235
+ const members = buckets.get(key).slice().sort((x, y) => nodes[x].id.localeCompare(nodes[y].id));
6236
+ const value = key.slice(sig.length + 1);
6237
+ for (let j = 1; j < members.length; j++) {
6238
+ const a = members[0], b = members[j];
6239
+ if (conf < MERGE_THRESHOLD) continue;
6240
+ dsu.union(a, b);
6241
+ merged.add(a);
6242
+ merged.add(b);
6243
+ const [s, t] = [nodes[a].id, nodes[b].id].sort();
6244
+ correlations.push({ sourceId: s, targetId: t, relationship: "same_as", signal: sig, confidence: conf, evidence: `[${sig}] shared ${value}` });
6245
+ }
6246
+ }
6247
+ }
6248
+ const clusters = /* @__PURE__ */ new Map();
6249
+ for (let i = 0; i < n; i++) {
6250
+ const r = dsu.find(i);
6251
+ const arr = clusters.get(r);
6252
+ if (arr) arr.push(i);
6253
+ else clusters.set(r, [i]);
6254
+ }
6255
+ const canonical = [];
6256
+ let crossCloud = 0;
6257
+ for (const idxs of clusters.values()) {
6258
+ const memberNodes = idxs.map((i) => nodes[i]);
6259
+ const members = memberNodes.map((m) => m.id).sort();
6260
+ const providers = [...new Set(idxs.map((i) => signals[i].provider).filter((p) => !!p))].sort();
6261
+ const rep = memberNodes.reduce((a, b) => a.id < b.id ? a : b);
6262
+ const memberIds = new Set(members);
6263
+ const internal = correlations.filter((c) => memberIds.has(c.sourceId) && memberIds.has(c.targetId));
6264
+ const confidence = internal.length ? Math.min(...internal.map((c) => c.confidence)) : 1;
6265
+ if (providers.length > 1) crossCloud += 1;
6266
+ canonical.push({ id: rep.id, type: rep.type, name: rep.name, members, providers, confidence });
6267
+ }
6268
+ canonical.sort((a, b) => a.id.localeCompare(b.id));
6269
+ correlations.sort((a, b) => b.confidence - a.confidence || a.sourceId.localeCompare(b.sourceId) || a.targetId.localeCompare(b.targetId));
6270
+ return {
6271
+ canonical,
6272
+ correlations,
6273
+ summary: { rawNodes: n, canonicalNodes: canonical.length, collapsed: n - canonical.length, crossCloud }
6274
+ };
6275
+ }
6276
+
6137
6277
  // src/mcp/server.ts
6138
6278
  var SERVER_NAME = "cartography";
6139
- var SERVER_VERSION = "2.9.0";
6279
+ var SERVER_VERSION = "2.10.0";
6140
6280
  var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
6141
6281
  var DATA_TYPES = NODE_TYPE_GROUPS.data;
6142
6282
  var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
@@ -6296,6 +6436,15 @@ function createMcpServer(opts = {}) {
6296
6436
  return json(db.getGraphSummary(sid));
6297
6437
  }
6298
6438
  );
6439
+ server.registerTool(
6440
+ "correlate_topology",
6441
+ { 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 },
6442
+ () => {
6443
+ const sid = resolveSession();
6444
+ if (!sid) return json({ error: "No discovery session found." });
6445
+ return json(correlateTopology(db.getNodes(sid), db.getEdges(sid)));
6446
+ }
6447
+ );
6299
6448
  server.registerTool(
6300
6449
  "get_cost_summary",
6301
6450
  { title: "Get cost summary", description: "FinOps rollup: cost by domain and owner, currency/period-bucketed (3.3).", inputSchema: {}, annotations: readOnly },
@@ -12220,6 +12369,7 @@ function checkClaudePrerequisites() {
12220
12369
  AuthorizationError,
12221
12370
  CLIENTS,
12222
12371
  CONFIDENCE,
12372
+ CORRELATION_CONFIDENCE,
12223
12373
  CartographyDB,
12224
12374
  ComplianceReportSchema,
12225
12375
  ComplianceRuleSchema,
@@ -12306,6 +12456,7 @@ function checkClaudePrerequisites() {
12306
12456
  computeIdentity,
12307
12457
  connectionsScanner,
12308
12458
  contentHash,
12459
+ correlateTopology,
12309
12460
  createBashTool,
12310
12461
  createCartographyTools,
12311
12462
  createClaudeProvider,
@@ -12353,6 +12504,7 @@ function checkClaudePrerequisites() {
12353
12504
  exportJGF,
12354
12505
  exportJSON,
12355
12506
  extractListeningPorts,
12507
+ extractSignals,
12356
12508
  filterBySeverity,
12357
12509
  findAnonViolations,
12358
12510
  formatComplianceText,