@datasynx/agentic-ai-cartography 2.11.0 → 2.12.1

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
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ScannerRegistry,
3
4
  getRuleset,
4
5
  isPersonalHost,
5
6
  listRulesets,
6
7
  loadOrgKey,
7
8
  pseudonymize,
9
+ pseudonymizeId,
8
10
  pseudonymizeString,
9
11
  reversePseudonym,
10
12
  rotateOrgKey,
11
13
  runDrift,
12
14
  runLocalDiscovery,
13
15
  startMcp
14
- } from "./chunk-FFUUNSWP.js";
16
+ } from "./chunk-OIDAXUW5.js";
15
17
  import {
16
18
  entitiesToYaml,
17
19
  startApi,
@@ -24,6 +26,7 @@ import {
24
26
  deriveSessionName,
25
27
  diffTopology,
26
28
  hashToken,
29
+ k8sScanner,
27
30
  normalizeTenant,
28
31
  redactValue,
29
32
  stableStringify,
@@ -952,6 +955,63 @@ async function runOnce(cfg, db) {
952
955
  }
953
956
  }
954
957
 
958
+ // src/k8s/operator.ts
959
+ function k8sRegistry() {
960
+ return new ScannerRegistry().register(k8sScanner);
961
+ }
962
+ function isInCluster(env = process.env) {
963
+ return typeof env["KUBERNETES_SERVICE_HOST"] === "string" && env["KUBERNETES_SERVICE_HOST"].length > 0;
964
+ }
965
+ function pruneToRetention(db, keep, tenant) {
966
+ const stale = db.getSessions(tenant).slice(Math.max(1, keep));
967
+ for (const s of stale) db.deleteSession(s.id);
968
+ return stale.length;
969
+ }
970
+ async function runOperatorCycle(db, config, opts = {}) {
971
+ const sessionId = db.createSession("discover", config);
972
+ const discover = opts.discover ?? ((d, s) => runLocalDiscovery(d, s, { registry: k8sRegistry() }));
973
+ const res = await discover(db, sessionId);
974
+ const sess = db.getSession(sessionId);
975
+ if (sess && !sess.name) db.setSessionName(sessionId, deriveSessionName(db.getGraphSummary(sessionId), sess.startedAt));
976
+ const driftFn = opts.drift ?? ((d, c) => runDrift(d, c));
977
+ const drift = await driftFn(db, config);
978
+ pruneToRetention(db, opts.retain ?? 10, normalizeTenant(config.organization));
979
+ return { sessionId, nodes: res.nodes, edges: res.edges, drift };
980
+ }
981
+ async function runOperator(db, config, opts = {}) {
982
+ const log = opts.log ?? ((m) => process.stderr.write(m + "\n"));
983
+ const intervalMs = opts.intervalMs ?? 5 * 6e4;
984
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve3) => {
985
+ if (opts.signal?.aborted) {
986
+ resolve3();
987
+ return;
988
+ }
989
+ const t = setTimeout(() => {
990
+ opts.signal?.removeEventListener?.("abort", onAbort);
991
+ resolve3();
992
+ }, ms);
993
+ const onAbort = () => {
994
+ clearTimeout(t);
995
+ resolve3();
996
+ };
997
+ opts.signal?.addEventListener?.("abort", onAbort, { once: true });
998
+ }));
999
+ log(`Cartograph Kubernetes operator (in-cluster: ${isInCluster()}, interval: ${Math.round(intervalMs / 1e3)}s${opts.once ? ", single pass" : ""})`);
1000
+ for (; ; ) {
1001
+ try {
1002
+ const c = await runOperatorCycle(db, config, opts);
1003
+ log(
1004
+ `reconcile: session ${c.sessionId} \u2014 ${c.nodes} nodes, ${c.edges} edges` + (c.drift ? `, drift ${c.drift.severity} (${c.drift.items.length} change${c.drift.items.length === 1 ? "" : "s"})` : ", no drift")
1005
+ );
1006
+ } catch (err) {
1007
+ log(`reconcile failed: ${err instanceof Error ? err.message : String(err)}`);
1008
+ }
1009
+ if (opts.once || opts.signal?.aborted) return;
1010
+ await sleep(intervalMs);
1011
+ if (opts.signal?.aborted) return;
1012
+ }
1013
+ }
1014
+
955
1015
  // src/exporter.ts
956
1016
  import { mkdirSync, writeFileSync } from "fs";
957
1017
  import { join as join2 } from "path";
@@ -2792,9 +2852,10 @@ function resolveEffectiveLevel(node, policy) {
2792
2852
  function applySharingLevel(node, level, orgKey, db) {
2793
2853
  if (level === "none") return null;
2794
2854
  if (level === "full") return { ...node, metadata: { ...node.metadata ?? {} }, tags: [...node.tags ?? []] };
2855
+ const { globalId: _g, contentHash: _h, ...rest } = node;
2795
2856
  return {
2796
- ...node,
2797
- id: pseudonymizeString(node.id, orgKey, db),
2857
+ ...rest,
2858
+ id: pseudonymizeId(node.id, orgKey, db),
2798
2859
  name: pseudonymizeString(node.name, orgKey, db),
2799
2860
  metadata: pseudonymize(node.metadata ?? {}, orgKey, db),
2800
2861
  tags: (node.tags ?? []).map((t) => pseudonymizeString(t, orgKey, db))
@@ -5052,6 +5113,37 @@ error: ${err instanceof Error ? err.message : String(err)}
5052
5113
  process.exitCode = 1;
5053
5114
  }
5054
5115
  });
5116
+ program.command("operator").description("Run the Kubernetes operator: continuous in-cluster discovery + drift reporting (5.2)").option("--config <file>", "JSON config file (drift sinks, db path)").option("--interval <sec>", "Reconcile interval in seconds", "300").option("--once", "Run a single reconcile and exit (CronJob-driver friendly)", false).option("--db <path>", "DB path (overrides config)").action(async (opts) => {
5117
+ let db;
5118
+ try {
5119
+ const config = opts.config ? loadConfig(opts.config) : defaultConfig(opts.db ? { dbPath: opts.db } : {});
5120
+ const interval = parseInt(opts.interval, 10);
5121
+ if (Number.isNaN(interval) || interval < 1) {
5122
+ process.stderr.write(`
5123
+ error: --interval must be a positive number of seconds (got '${opts.interval}')
5124
+ `);
5125
+ process.exitCode = 1;
5126
+ return;
5127
+ }
5128
+ db = new CartographyDB(opts.db ?? config.dbPath);
5129
+ const controller = new AbortController();
5130
+ process.once("SIGINT", () => controller.abort());
5131
+ process.once("SIGTERM", () => controller.abort());
5132
+ process.stderr.write(`Cartograph operator: ${isInCluster() ? "in-cluster" : "out-of-cluster (dev)"}
5133
+ `);
5134
+ await runOperator(db, config, { intervalMs: interval * 1e3, once: opts.once === true, signal: controller.signal });
5135
+ } catch (err) {
5136
+ if (err instanceof ConfigError) process.stderr.write(`
5137
+ config error: ${err.message}
5138
+ `);
5139
+ else process.stderr.write(`
5140
+ error: ${err instanceof Error ? err.message : String(err)}
5141
+ `);
5142
+ process.exitCode = 1;
5143
+ } finally {
5144
+ db?.close();
5145
+ }
5146
+ });
5055
5147
  const authCmd = program.command("auth").description("Manage RBAC credentials for the HTTP surfaces (MCP transport + API server, 4.5)");
5056
5148
  authCmd.command("add <subject>").description("Create a credential and print its bearer token ONCE (only the hash is stored)").option("--role <role>", "viewer | operator | admin", "viewer").option("--tenant <id>", "Tenant the credential is scoped to (default: local)").option("--token <secret>", "Use this token instead of generating one").option("--db <path>", "DB path").action((subject, opts) => {
5057
5149
  const role = RoleSchema.safeParse(opts.role);