@datasynx/agentic-ai-cartography 2.3.0 → 2.5.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/index.cjs CHANGED
@@ -30,6 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ ACTIONS: () => ACTIONS,
34
+ ActionSchema: () => ActionSchema,
35
+ AuthConfigSchema: () => AuthConfigSchema,
36
+ AuthorizationError: () => AuthorizationError,
33
37
  CLIENTS: () => CLIENTS,
34
38
  CONFIDENCE: () => CONFIDENCE,
35
39
  CartographyDB: () => CartographyDB,
@@ -38,6 +42,7 @@ __export(src_exports, {
38
42
  ConditionSchema: () => ConditionSchema,
39
43
  ConfigError: () => ConfigError,
40
44
  ControlResultSchema: () => ControlResultSchema,
45
+ CredentialConfigSchema: () => CredentialConfigSchema,
41
46
  CsvCostSource: () => CsvCostSource,
42
47
  DEFAULT_ANOMALY_THRESHOLDS: () => DEFAULT_ANOMALY_THRESHOLDS,
43
48
  DEFAULT_SERVER_NAME: () => DEFAULT_SERVER_NAME,
@@ -46,16 +51,22 @@ __export(src_exports, {
46
51
  INGEST_SCHEMA_VERSION: () => INGEST_SCHEMA_VERSION,
47
52
  IngestEnvelopeSchema: () => IngestEnvelopeSchema,
48
53
  InvalidTenantError: () => InvalidTenantError,
54
+ JiraSink: () => JiraSink,
49
55
  LOOPBACK_HOSTS: () => LOOPBACK_HOSTS2,
50
56
  MCP_BIN: () => MCP_BIN,
51
57
  NotFoundError: () => NotFoundError,
52
58
  PACKAGE_NAME: () => PACKAGE_NAME,
59
+ PAGERDUTY_ENQUEUE_URL: () => PAGERDUTY_ENQUEUE_URL,
53
60
  PERSONAL: () => PERSONAL,
54
61
  PORT_MAP: () => PORT_MAP,
55
62
  PRIVATE_IP: () => PRIVATE_IP,
56
63
  PUSH_SCHEMA_VERSION: () => PUSH_SCHEMA_VERSION,
64
+ PagerDutySink: () => PagerDutySink,
65
+ PrincipalSchema: () => PrincipalSchema,
57
66
  ProviderRegistry: () => ProviderRegistry,
58
67
  RELATION_TO_DIRECTION: () => RELATION_TO_DIRECTION,
68
+ ROLES: () => ROLES,
69
+ RoleSchema: () => RoleSchema,
59
70
  RuleCheckSchema: () => RuleCheckSchema,
60
71
  RulesetSchema: () => RulesetSchema,
61
72
  SCAN_ARG_PATTERNS: () => SCAN_ARG_PATTERNS,
@@ -65,10 +76,13 @@ __export(src_exports, {
65
76
  ScannerRegistry: () => ScannerRegistry,
66
77
  ScannerShape: () => ScannerShape,
67
78
  SharingLevelSchema: () => SharingLevelSchema,
79
+ SlackSink: () => SlackSink,
80
+ SqliteCredentialStore: () => SqliteCredentialStore,
68
81
  SqliteQueryBackend: () => SqliteQueryBackend,
69
82
  SqliteStoreBackend: () => SqliteStoreBackend,
70
83
  StdoutSink: () => StdoutSink,
71
84
  TENANT_HEADER: () => TENANT_HEADER,
85
+ TenantMismatchError: () => TenantMismatchError,
72
86
  VectorStore: () => VectorStore,
73
87
  WebhookSink: () => WebhookSink,
74
88
  applyInstall: () => applyInstall,
@@ -76,7 +90,9 @@ __export(src_exports, {
76
90
  assertReadOnly: () => assertReadOnly,
77
91
  assertSafeBind: () => assertSafeBind,
78
92
  assertSafeScanArg: () => assertSafeScanArg,
93
+ assertSameTenant: () => assertSameTenant,
79
94
  assignColors: () => assignColors,
95
+ authorize: () => authorize,
80
96
  bearerToken: () => bearerToken,
81
97
  bookmarksScanner: () => bookmarksScanner,
82
98
  buildCartographyToolHandlers: () => buildCartographyToolHandlers,
@@ -84,6 +100,7 @@ __export(src_exports, {
84
100
  buildOpenApiDocument: () => buildOpenApiDocument,
85
101
  buildReport: () => buildReport,
86
102
  buildSinks: () => buildSinks,
103
+ can: () => can,
87
104
  centralDbFromEnv: () => centralDbFromEnv,
88
105
  checkBearer: () => checkBearer,
89
106
  checkPrerequisites: () => checkPrerequisites,
@@ -149,6 +166,9 @@ __export(src_exports, {
149
166
  filterBySeverity: () => filterBySeverity,
150
167
  findAnonViolations: () => findAnonViolations,
151
168
  formatComplianceText: () => formatComplianceText,
169
+ formatJira: () => formatJira,
170
+ formatPagerDuty: () => formatPagerDuty,
171
+ formatSlack: () => formatSlack,
152
172
  generateDependencyMermaid: () => generateDependencyMermaid,
153
173
  generateDiffMermaid: () => generateDiffMermaid,
154
174
  generateTopologyMermaid: () => generateTopologyMermaid,
@@ -157,6 +177,7 @@ __export(src_exports, {
157
177
  globalId: () => globalId,
158
178
  groupByDomain: () => groupByDomain,
159
179
  handleGraphqlGet: () => handleGraphqlGet,
180
+ hashToken: () => hashToken,
160
181
  hexCorners: () => hexCorners,
161
182
  hexDistance: () => hexDistance,
162
183
  hexNeighbors: () => hexNeighbors,
@@ -171,6 +192,7 @@ __export(src_exports, {
171
192
  isPersonalHost: () => isPersonalHost,
172
193
  isReadOnlyCommand: () => isReadOnlyCommand,
173
194
  isRemembered: () => isRemembered,
195
+ isSecureWebhookUrl: () => isSecureWebhookUrl,
174
196
  k8sScanner: () => k8sScanner,
175
197
  keyMetaOf: () => keyMetaOf,
176
198
  layoutClusters: () => layoutClusters,
@@ -209,6 +231,7 @@ __export(src_exports, {
209
231
  pixelToHex: () => pixelToHex,
210
232
  planInstall: () => planInstall,
211
233
  portsScanner: () => portsScanner,
234
+ postJson: () => postJson,
212
235
  previewShare: () => previewShare,
213
236
  pseudonymize: () => pseudonymize,
214
237
  pseudonymizeFragment: () => pseudonymizeFragment,
@@ -221,6 +244,7 @@ __export(src_exports, {
221
244
  renderDiff: () => renderDiff,
222
245
  resolveEffectiveLevel: () => resolveEffectiveLevel,
223
246
  resolveNlQuery: () => resolveNlQuery,
247
+ resolvePrincipal: () => resolvePrincipal,
224
248
  resolveSharingLevel: () => resolveSharingLevel,
225
249
  resolveTenant: () => resolveTenant,
226
250
  revalidateAnonymized: () => revalidateAnonymized,
@@ -240,6 +264,7 @@ __export(src_exports, {
240
264
  safetyHook: () => safetyHook,
241
265
  sanitizeUntrusted: () => sanitizeUntrusted,
242
266
  sanitizeValue: () => sanitizeValue,
267
+ scopeReads: () => scopeReads,
243
268
  scoreTopology: () => scoreTopology,
244
269
  securityRelevantChange: () => securityRelevantChange,
245
270
  serializeConfig: () => serializeConfig,
@@ -417,15 +442,26 @@ var SECURITY_METADATA_KEYS = [
417
442
  var DriftConfigSchema = import_zod.z.object({
418
443
  minSeverity: import_zod.z.enum(SEVERITIES).default("info"),
419
444
  sinks: import_zod.z.array(import_zod.z.object({
420
- type: import_zod.z.enum(["stdout", "webhook"]),
445
+ type: import_zod.z.enum(["stdout", "webhook", "slack", "pagerduty", "jira"]),
421
446
  url: import_zod.z.string().url().optional(),
422
447
  token: import_zod.z.string().optional(),
423
- timeoutMs: import_zod.z.number().int().positive().optional()
448
+ timeoutMs: import_zod.z.number().int().positive().optional(),
449
+ routingKey: import_zod.z.string().optional(),
450
+ email: import_zod.z.string().optional(),
451
+ project: import_zod.z.string().optional(),
452
+ issueType: import_zod.z.string().optional()
424
453
  })).default([{ type: "stdout" }])
425
454
  }).superRefine((cfg, ctx) => {
426
455
  for (const [i, s] of cfg.sinks.entries()) {
427
- if (s.type === "webhook" && !s.url) {
428
- ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message: "webhook sink requires a url" });
456
+ const requireUrl = (msg) => {
457
+ if (!s.url) ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message: msg });
458
+ };
459
+ if (s.type === "webhook") requireUrl("webhook sink requires a url");
460
+ if (s.type === "slack") requireUrl("slack sink requires a webhook url");
461
+ if (s.type === "jira") {
462
+ requireUrl("jira sink requires a base url");
463
+ if (!s.email) ctx.addIssue({ code: "custom", path: ["sinks", i, "email"], message: "jira sink requires an email" });
464
+ if (!s.project) ctx.addIssue({ code: "custom", path: ["sinks", i, "project"], message: "jira sink requires a project key" });
429
465
  }
430
466
  }
431
467
  });
@@ -2039,8 +2075,17 @@ function stripSensitive(target) {
2039
2075
  const stripped = `${url.hostname}${url.port ? ":" + url.port : ""}`;
2040
2076
  return stripped || raw;
2041
2077
  } catch {
2042
- const stripped = raw.replace(/\/.*$/, "").replace(/\?.*$/, "").replace(/@.*:/, ":");
2043
- return stripped || raw;
2078
+ let s = raw;
2079
+ const slash = s.indexOf("/");
2080
+ if (slash >= 0) s = s.slice(0, slash);
2081
+ const q = s.indexOf("?");
2082
+ if (q >= 0) s = s.slice(0, q);
2083
+ const at = s.indexOf("@");
2084
+ if (at >= 0) {
2085
+ const colon = s.lastIndexOf(":");
2086
+ if (colon > at) s = s.slice(0, at) + ":" + s.slice(colon + 1);
2087
+ }
2088
+ return s || raw;
2044
2089
  }
2045
2090
  }
2046
2091
  var SCAN_ARG_PATTERNS = {
@@ -2058,7 +2103,7 @@ function assertSafeScanArg(kind, value) {
2058
2103
  return value;
2059
2104
  }
2060
2105
  function redactSecrets(value) {
2061
- return value.replace(/([a-z][a-z0-9+.-]*:\/\/[^:@/\s]+):[^@/\s]+@/gi, "$1:***@");
2106
+ return value.replace(/([a-z][a-z0-9+.-]{0,63}:\/\/[^:@/\s]{1,256}):[^@/\s]{1,256}@/gi, "$1:***@");
2062
2107
  }
2063
2108
  function redactValue(value) {
2064
2109
  if (typeof value === "string") return redactSecrets(value);
@@ -3056,7 +3101,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
3056
3101
  duration_ms INTEGER,
3057
3102
  command TEXT,
3058
3103
  result_bytes INTEGER,
3059
- tenant TEXT NOT NULL DEFAULT 'local'
3104
+ tenant TEXT NOT NULL DEFAULT 'local',
3105
+ actor_subject TEXT,
3106
+ actor_role TEXT,
3107
+ actor_tenant TEXT
3060
3108
  );
3061
3109
 
3062
3110
  CREATE TABLE IF NOT EXISTS tasks (
@@ -3165,6 +3213,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
3165
3213
  CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
3166
3214
  CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
3167
3215
  `;
3216
+ var AUTH_SCHEMA = `
3217
+ CREATE TABLE IF NOT EXISTS auth_credentials (
3218
+ token_hash TEXT PRIMARY KEY,
3219
+ subject TEXT NOT NULL,
3220
+ tenant TEXT NOT NULL DEFAULT 'local',
3221
+ role TEXT NOT NULL,
3222
+ created_at TEXT NOT NULL
3223
+ );
3224
+ CREATE INDEX IF NOT EXISTS idx_auth_subject ON auth_credentials(subject);
3225
+ `;
3168
3226
  var CartographyDB = class {
3169
3227
  db;
3170
3228
  /** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
@@ -3184,7 +3242,8 @@ var CartographyDB = class {
3184
3242
  const version = this.db.pragma("user_version", { simple: true });
3185
3243
  if (version === 0) {
3186
3244
  this.db.exec(SCHEMA);
3187
- this.db.pragma("user_version = 14");
3245
+ this.db.exec(AUTH_SCHEMA);
3246
+ this.db.pragma("user_version = 15");
3188
3247
  return;
3189
3248
  } else if (version === 1) {
3190
3249
  const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
@@ -3370,6 +3429,18 @@ var CartographyDB = class {
3370
3429
  }
3371
3430
  this.db.pragma("user_version = 14");
3372
3431
  }
3432
+ const v14 = this.db.pragma("user_version", { simple: true });
3433
+ if (v14 < 15) {
3434
+ this.db.exec(AUTH_SCHEMA);
3435
+ const ev = this.db.prepare("PRAGMA table_info(activity_events)").all();
3436
+ if (ev.length > 0) {
3437
+ const cols = ev.map((c) => c.name);
3438
+ if (!cols.includes("actor_subject")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_subject TEXT");
3439
+ if (!cols.includes("actor_role")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_role TEXT");
3440
+ if (!cols.includes("actor_tenant")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_tenant TEXT");
3441
+ }
3442
+ this.db.pragma("user_version = 15");
3443
+ }
3373
3444
  }
3374
3445
  close() {
3375
3446
  this.db.pragma("optimize");
@@ -3800,13 +3871,13 @@ var CartographyDB = class {
3800
3871
  });
3801
3872
  }
3802
3873
  // ── Events ──────────────────────────────
3803
- insertEvent(sessionId, event, taskId) {
3874
+ insertEvent(sessionId, event, taskId, actor) {
3804
3875
  const id = crypto.randomUUID();
3805
3876
  const tenant = this.tenantOf(sessionId);
3806
3877
  this.db.prepare(`
3807
3878
  INSERT INTO activity_events
3808
- (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)
3809
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3879
+ (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant, actor_subject, actor_role, actor_tenant)
3880
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3810
3881
  `).run(
3811
3882
  id,
3812
3883
  sessionId,
@@ -3820,9 +3891,52 @@ var CartographyDB = class {
3820
3891
  event.port ?? null,
3821
3892
  event.command ?? null,
3822
3893
  event.resultBytes ?? null,
3823
- tenant
3894
+ tenant,
3895
+ actor?.subject ?? null,
3896
+ actor?.role ?? null,
3897
+ actor?.tenant ?? null
3824
3898
  );
3825
3899
  }
3900
+ // ── RBAC credential store (4.5) ─────────────
3901
+ /** Number of stored credentials. `0` ⇒ no RBAC configured (fall back to shared/loopback). */
3902
+ countCredentials() {
3903
+ return this.db.prepare("SELECT COUNT(*) AS n FROM auth_credentials").get().n;
3904
+ }
3905
+ /** Look up a credential by its sha256 token hash. */
3906
+ findCredentialByHash(tokenHash) {
3907
+ const r = this.db.prepare("SELECT * FROM auth_credentials WHERE token_hash = ?").get(tokenHash);
3908
+ if (!r) return void 0;
3909
+ return {
3910
+ tokenHash: r["token_hash"],
3911
+ subject: r["subject"],
3912
+ tenant: r["tenant"],
3913
+ role: r["role"],
3914
+ createdAt: r["created_at"]
3915
+ };
3916
+ }
3917
+ /** Upsert a credential (idempotent on the token hash). Stores only the hash, never the raw token. */
3918
+ addCredential(rec) {
3919
+ this.db.prepare(`
3920
+ INSERT INTO auth_credentials (token_hash, subject, tenant, role, created_at)
3921
+ VALUES (?, ?, ?, ?, ?)
3922
+ ON CONFLICT(token_hash) DO UPDATE SET subject = excluded.subject, tenant = excluded.tenant, role = excluded.role
3923
+ `).run(rec.tokenHash, rec.subject, rec.tenant, rec.role, (/* @__PURE__ */ new Date()).toISOString());
3924
+ }
3925
+ /** List all credentials (token hashes only — the raw token is unrecoverable). */
3926
+ listCredentials() {
3927
+ const rows = this.db.prepare("SELECT * FROM auth_credentials ORDER BY created_at").all();
3928
+ return rows.map((r) => ({
3929
+ tokenHash: r["token_hash"],
3930
+ subject: r["subject"],
3931
+ tenant: r["tenant"],
3932
+ role: r["role"],
3933
+ createdAt: r["created_at"]
3934
+ }));
3935
+ }
3936
+ /** Revoke every credential for a subject. Returns the number removed. */
3937
+ revokeCredentialsBySubject(subject) {
3938
+ return this.db.prepare("DELETE FROM auth_credentials WHERE subject = ?").run(subject).changes;
3939
+ }
3826
3940
  getEvents(sessionId, since) {
3827
3941
  const rows = since ? this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp").all(sessionId, since) : this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp").all(sessionId);
3828
3942
  return rows.map((r) => {
@@ -5156,6 +5270,41 @@ var StdoutSink = class {
5156
5270
 
5157
5271
  // src/sinks/webhook.ts
5158
5272
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
5273
+ async function postJson(opts) {
5274
+ const doFetch = opts.fetchImpl ?? (typeof fetch === "function" ? fetch : void 0);
5275
+ if (!doFetch) {
5276
+ logWarn("sink unavailable: global fetch missing", { sink: opts.sinkName });
5277
+ return;
5278
+ }
5279
+ if (!opts.url) {
5280
+ logWarn("sink unavailable: no url configured", { sink: opts.sinkName });
5281
+ return;
5282
+ }
5283
+ if (!isSecureWebhookUrl(opts.url)) {
5284
+ logWarn("sink refused: insecure scheme (use https:// or a loopback host)", {
5285
+ sink: opts.sinkName,
5286
+ host: stripSensitive(opts.url)
5287
+ });
5288
+ return;
5289
+ }
5290
+ try {
5291
+ const res = await doFetch(opts.url, {
5292
+ method: "POST",
5293
+ headers: { "content-type": "application/json", ...opts.headers ?? {} },
5294
+ body: JSON.stringify(opts.body),
5295
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
5296
+ });
5297
+ if (!res.ok) {
5298
+ logError("sink delivery failed", { sink: opts.sinkName, host: stripSensitive(opts.url), status: res.status });
5299
+ }
5300
+ } catch (err) {
5301
+ logError("sink delivery failed", {
5302
+ sink: opts.sinkName,
5303
+ host: stripSensitive(opts.url),
5304
+ reason: err instanceof Error ? err.message : String(err)
5305
+ });
5306
+ }
5307
+ }
5159
5308
  function isSecureWebhookUrl(url, env = process.env) {
5160
5309
  if (env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === "1") return true;
5161
5310
  let parsed;
@@ -5174,59 +5323,177 @@ var WebhookSink = class {
5174
5323
  }
5175
5324
  name = "webhook";
5176
5325
  async emit(alert) {
5177
- if (typeof fetch !== "function") {
5178
- logWarn("webhook sink unavailable: global fetch missing", { sink: this.name });
5179
- return;
5180
- }
5181
5326
  const { url, token, timeoutMs } = this.opts;
5182
- if (!url) {
5183
- logWarn("webhook sink unavailable: no url configured", { sink: this.name });
5184
- return;
5185
- }
5186
- if (!isSecureWebhookUrl(url)) {
5187
- logWarn("webhook sink refused: insecure scheme (use https:// or a loopback host)", {
5188
- sink: this.name,
5189
- host: stripSensitive(url)
5190
- });
5191
- return;
5192
- }
5193
- try {
5194
- const res = await fetch(url, {
5195
- method: "POST",
5196
- headers: {
5197
- "content-type": "application/json",
5198
- ...token ? { authorization: `Bearer ${token}` } : {}
5199
- },
5200
- body: JSON.stringify(redactValue(alert)),
5201
- signal: AbortSignal.timeout(timeoutMs ?? 1e4)
5202
- });
5203
- if (!res.ok) {
5204
- logError("webhook sink failed", { sink: this.name, host: stripSensitive(url), status: res.status });
5327
+ await postJson({
5328
+ url,
5329
+ body: redactValue(alert),
5330
+ ...token ? { headers: { authorization: `Bearer ${token}` } } : {},
5331
+ ...timeoutMs !== void 0 ? { timeoutMs } : {},
5332
+ sinkName: this.name
5333
+ });
5334
+ }
5335
+ };
5336
+
5337
+ // src/sinks/providers.ts
5338
+ var MAX_ITEMS2 = 20;
5339
+ var SEVERITY_EMOJI = { info: "\u{1F7E2}", warning: "\u{1F7E1}", critical: "\u{1F534}" };
5340
+ function headline(alert) {
5341
+ const s = alert.summary;
5342
+ return `${s.nodesAdded}+ / ${s.nodesRemoved}- / ${s.nodesChanged}~ nodes, ${s.edgesAdded}+ / ${s.edgesRemoved}- edges`;
5343
+ }
5344
+ function itemLine(it) {
5345
+ const sec = it.securityFields?.length ? ` [security: ${it.securityFields.join(", ")}]` : "";
5346
+ const fields = it.changedFields?.length ? ` (${it.changedFields.join(", ")})` : "";
5347
+ return `${it.severity.toUpperCase()} \xB7 ${it.kind} \xB7 ${it.label}${fields}${sec}`;
5348
+ }
5349
+ function bodyText(alert) {
5350
+ const lines = alert.items.slice(0, MAX_ITEMS2).map(itemLine);
5351
+ const more = alert.items.length > MAX_ITEMS2 ? [`\u2026and ${alert.items.length - MAX_ITEMS2} more`] : [];
5352
+ return [headline(alert), "", ...lines, ...more].join("\n");
5353
+ }
5354
+ function formatSlack(alert) {
5355
+ const title = `${SEVERITY_EMOJI[alert.severity]} Topology drift \u2014 ${alert.severity}`;
5356
+ return {
5357
+ text: `${title}: ${headline(alert)}`,
5358
+ blocks: [
5359
+ { type: "header", text: { type: "plain_text", text: title, emoji: true } },
5360
+ { type: "section", text: { type: "mrkdwn", text: "```" + bodyText(alert) + "```" } },
5361
+ { type: "context", elements: [{ type: "mrkdwn", text: `base ${alert.base.sessionId} \u2192 current ${alert.current.sessionId} \xB7 ${alert.generatedAt}` }] }
5362
+ ]
5363
+ };
5364
+ }
5365
+ var PD_SEVERITY = {
5366
+ info: "info",
5367
+ warning: "warning",
5368
+ critical: "critical"
5369
+ };
5370
+ function formatPagerDuty(alert, routingKey) {
5371
+ return {
5372
+ routing_key: routingKey,
5373
+ event_action: "trigger",
5374
+ // Stable per base→current pair so repeated alerts for the same delta de-duplicate.
5375
+ dedup_key: `cartograph-drift:${alert.base.sessionId}:${alert.current.sessionId}`,
5376
+ payload: {
5377
+ summary: `Cartograph topology drift (${alert.severity}): ${headline(alert)}`,
5378
+ source: "cartograph",
5379
+ severity: PD_SEVERITY[alert.severity],
5380
+ timestamp: alert.generatedAt,
5381
+ custom_details: {
5382
+ summary: alert.summary,
5383
+ items: alert.items.slice(0, MAX_ITEMS2).map((it) => ({
5384
+ kind: it.kind,
5385
+ ref: it.ref,
5386
+ severity: it.severity,
5387
+ ...it.changedFields ? { changedFields: it.changedFields } : {},
5388
+ ...it.securityFields ? { securityFields: it.securityFields } : {}
5389
+ }))
5205
5390
  }
5206
- } catch (err) {
5207
- logError("webhook sink failed", {
5208
- sink: this.name,
5209
- host: stripSensitive(url),
5210
- reason: err instanceof Error ? err.message : String(err)
5211
- });
5212
5391
  }
5392
+ };
5393
+ }
5394
+ function formatJira(alert, opts) {
5395
+ return {
5396
+ fields: {
5397
+ project: { key: opts.project },
5398
+ issuetype: { name: opts.issueType ?? "Task" },
5399
+ summary: `Cartograph topology drift (${alert.severity}): ${headline(alert)}`,
5400
+ description: bodyText(alert) + `
5401
+
5402
+ base ${alert.base.sessionId} \u2192 current ${alert.current.sessionId}
5403
+ generated ${alert.generatedAt}`
5404
+ }
5405
+ };
5406
+ }
5407
+
5408
+ // src/sinks/provider-sink.ts
5409
+ var PAGERDUTY_ENQUEUE_URL = "https://events.pagerduty.com/v2/enqueue";
5410
+ function deliver(name, url, body, opts, headers) {
5411
+ return postJson({
5412
+ url,
5413
+ body,
5414
+ sinkName: name,
5415
+ ...headers ? { headers } : {},
5416
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
5417
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
5418
+ });
5419
+ }
5420
+ var SlackSink = class {
5421
+ constructor(opts) {
5422
+ this.opts = opts;
5423
+ }
5424
+ name = "slack";
5425
+ async emit(alert) {
5426
+ await deliver(this.name, this.opts.url, formatSlack(redactValue(alert)), this.opts);
5427
+ }
5428
+ };
5429
+ var PagerDutySink = class {
5430
+ constructor(opts) {
5431
+ this.opts = opts;
5432
+ }
5433
+ name = "pagerduty";
5434
+ async emit(alert) {
5435
+ const body = formatPagerDuty(redactValue(alert), this.opts.routingKey);
5436
+ await deliver(this.name, this.opts.url || PAGERDUTY_ENQUEUE_URL, body, this.opts);
5437
+ }
5438
+ };
5439
+ var JiraSink = class {
5440
+ constructor(opts) {
5441
+ this.opts = opts;
5442
+ }
5443
+ name = "jira";
5444
+ async emit(alert) {
5445
+ const body = formatJira(redactValue(alert), {
5446
+ project: this.opts.project,
5447
+ ...this.opts.issueType ? { issueType: this.opts.issueType } : {}
5448
+ });
5449
+ const auth = Buffer.from(`${this.opts.email}:${this.opts.token}`).toString("base64");
5450
+ const base = this.opts.url.replace(/\/+$/, "");
5451
+ await deliver(this.name, `${base}/rest/api/2/issue`, body, this.opts, { authorization: `Basic ${auth}` });
5213
5452
  }
5214
5453
  };
5215
5454
 
5216
5455
  // src/sinks/index.ts
5217
5456
  function buildSinks(drift) {
5218
5457
  const configs = drift?.sinks && drift.sinks.length > 0 ? drift.sinks : [{ type: "stdout" }];
5458
+ const envSecret = process.env.CARTOGRAPHY_DRIFT_TOKEN;
5219
5459
  const sinks = [];
5220
5460
  for (const s of configs) {
5221
- if (s.type === "webhook") {
5222
- if (!s.url) continue;
5223
- sinks.push(new WebhookSink({
5224
- url: s.url,
5225
- token: s.token ?? process.env.CARTOGRAPHY_DRIFT_TOKEN,
5226
- timeoutMs: s.timeoutMs
5227
- }));
5228
- } else {
5229
- sinks.push(new StdoutSink());
5461
+ const timeoutMs = s.timeoutMs;
5462
+ switch (s.type) {
5463
+ case "webhook":
5464
+ if (!s.url) {
5465
+ logWarn("drift sink skipped: webhook requires a url", { sink: s.type });
5466
+ break;
5467
+ }
5468
+ sinks.push(new WebhookSink({ url: s.url, token: s.token ?? envSecret, timeoutMs }));
5469
+ break;
5470
+ case "slack":
5471
+ if (!s.url) {
5472
+ logWarn("drift sink skipped: slack requires a webhook url", { sink: s.type });
5473
+ break;
5474
+ }
5475
+ sinks.push(new SlackSink({ url: s.url, timeoutMs }));
5476
+ break;
5477
+ case "pagerduty": {
5478
+ const routingKey = s.routingKey ?? s.token ?? envSecret;
5479
+ if (!routingKey) {
5480
+ logWarn("drift sink skipped: pagerduty requires a routingKey (or CARTOGRAPHY_DRIFT_TOKEN)", { sink: s.type });
5481
+ break;
5482
+ }
5483
+ sinks.push(new PagerDutySink({ url: s.url ?? PAGERDUTY_ENQUEUE_URL, routingKey, timeoutMs }));
5484
+ break;
5485
+ }
5486
+ case "jira": {
5487
+ const token = s.token ?? envSecret;
5488
+ if (!s.url || !s.email || !s.project || !token) {
5489
+ logWarn("drift sink skipped: jira requires url, email, project and a token", { sink: s.type });
5490
+ break;
5491
+ }
5492
+ sinks.push(new JiraSink({ url: s.url, email: s.email, token, project: s.project, issueType: s.issueType, timeoutMs }));
5493
+ break;
5494
+ }
5495
+ default:
5496
+ sinks.push(new StdoutSink());
5230
5497
  }
5231
5498
  }
5232
5499
  return sinks.length > 0 ? sinks : [new StdoutSink()];
@@ -5348,6 +5615,36 @@ async function runDrift(db, config, opts = {}) {
5348
5615
  return alert;
5349
5616
  }
5350
5617
 
5618
+ // src/auth/rbac.ts
5619
+ var ROLE_RANK = { viewer: 1, operator: 2, admin: 3 };
5620
+ var ACTION_MIN_ROLE = { read: "viewer", discovery: "operator", admin: "admin" };
5621
+ function can(role, action) {
5622
+ return ROLE_RANK[role] >= ROLE_RANK[ACTION_MIN_ROLE[action]];
5623
+ }
5624
+ var AuthorizationError = class extends Error {
5625
+ constructor(action, role) {
5626
+ super(`forbidden: role '${role}' may not perform '${action}'`);
5627
+ this.action = action;
5628
+ this.role = role;
5629
+ this.name = "AuthorizationError";
5630
+ }
5631
+ };
5632
+ function authorize(principal, action) {
5633
+ if (!can(principal.role, action)) throw new AuthorizationError(action, principal.role);
5634
+ }
5635
+ var TenantMismatchError = class extends Error {
5636
+ constructor() {
5637
+ super("forbidden: principal is not scoped to the requested tenant");
5638
+ this.name = "TenantMismatchError";
5639
+ }
5640
+ };
5641
+ function scopeReads(principal) {
5642
+ return principal.tenant;
5643
+ }
5644
+ function assertSameTenant(principal, requestedTenant) {
5645
+ if (requestedTenant !== principal.tenant) throw new TenantMismatchError();
5646
+ }
5647
+
5351
5648
  // src/compliance/rulesets/baseline.ts
5352
5649
  var baseline = RulesetSchema.parse({
5353
5650
  name: "baseline",
@@ -5635,7 +5932,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
5635
5932
 
5636
5933
  // src/mcp/server.ts
5637
5934
  var SERVER_NAME = "cartography";
5638
- var SERVER_VERSION = "2.3.0";
5935
+ var SERVER_VERSION = "2.5.0";
5639
5936
  var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
5640
5937
  var DATA_TYPES = NODE_TYPE_GROUPS.data;
5641
5938
  var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
@@ -6037,6 +6334,14 @@ function createMcpServer(opts = {}) {
6037
6334
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
6038
6335
  },
6039
6336
  async (args) => {
6337
+ if (opts.principal) {
6338
+ try {
6339
+ authorize(opts.principal, "discovery");
6340
+ } catch (err) {
6341
+ if (err instanceof AuthorizationError) return json({ error: `forbidden: role '${opts.principal.role}' may not run discovery (operator required)` });
6342
+ throw err;
6343
+ }
6344
+ }
6040
6345
  let sid = resolveSession();
6041
6346
  if (args.update) {
6042
6347
  if (!sid) return json({ error: "No session to update; run discovery first." });
@@ -6132,7 +6437,7 @@ function createMcpServer(opts = {}) {
6132
6437
  }
6133
6438
 
6134
6439
  // src/mcp/transports.ts
6135
- var import_node_crypto5 = require("crypto");
6440
+ var import_node_crypto6 = require("crypto");
6136
6441
  var import_node_http = __toESM(require("http"), 1);
6137
6442
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
6138
6443
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
@@ -6179,7 +6484,41 @@ function defaultAllowedHosts(host2, port) {
6179
6484
  return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
6180
6485
  }
6181
6486
 
6487
+ // src/auth/identity.ts
6488
+ var import_node_crypto5 = require("crypto");
6489
+ function hashToken(token) {
6490
+ return (0, import_node_crypto5.createHash)("sha256").update(token, "utf8").digest("hex");
6491
+ }
6492
+ var SqliteCredentialStore = class {
6493
+ constructor(db) {
6494
+ this.db = db;
6495
+ }
6496
+ count() {
6497
+ return this.db.countCredentials();
6498
+ }
6499
+ findByHash(tokenHash) {
6500
+ return this.db.findCredentialByHash(tokenHash);
6501
+ }
6502
+ };
6503
+ function resolvePrincipal(presentedToken, opts) {
6504
+ const tenant = opts.defaultTenant ?? DEFAULT_TENANT;
6505
+ if (opts.store && opts.store.count() > 0) {
6506
+ if (!presentedToken) return void 0;
6507
+ const rec = opts.store.findByHash(hashToken(presentedToken));
6508
+ return rec ? { subject: rec.subject, tenant: rec.tenant, role: rec.role } : void 0;
6509
+ }
6510
+ if (opts.sharedToken) {
6511
+ if (!presentedToken || !timingSafeEqual(presentedToken, opts.sharedToken)) return void 0;
6512
+ return { subject: "shared-token", tenant, role: "admin" };
6513
+ }
6514
+ if (opts.required) return void 0;
6515
+ return { subject: "anonymous", tenant, role: "admin" };
6516
+ }
6517
+
6182
6518
  // src/mcp/transports.ts
6519
+ function samePrincipal(a, b) {
6520
+ return a.subject === b.subject && a.tenant === b.tenant && a.role === b.role;
6521
+ }
6183
6522
  async function runStdio(server) {
6184
6523
  const transport = new import_stdio.StdioServerTransport();
6185
6524
  await server.connect(transport);
@@ -6224,6 +6563,14 @@ async function runHttp(factory, opts = {}) {
6224
6563
  assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
6225
6564
  const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
6226
6565
  const token = opts.token;
6566
+ const authStore = opts.auth?.store;
6567
+ const defaultTenant = opts.defaultTenant;
6568
+ const resolveAuth = (header) => resolvePrincipal(bearerToken(header), {
6569
+ ...authStore ? { store: authStore } : {},
6570
+ ...token ? { sharedToken: token } : {},
6571
+ ...defaultTenant ? { defaultTenant } : {},
6572
+ ...opts.auth?.required ? { required: true } : {}
6573
+ });
6227
6574
  const transports = /* @__PURE__ */ new Map();
6228
6575
  const httpServer = import_node_http.default.createServer(async (req, res) => {
6229
6576
  try {
@@ -6233,7 +6580,8 @@ async function runHttp(factory, opts = {}) {
6233
6580
  res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
6234
6581
  return;
6235
6582
  }
6236
- if (!checkBearer(req.headers["authorization"], token)) {
6583
+ const principal = resolveAuth(req.headers["authorization"]);
6584
+ if (!principal) {
6237
6585
  res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
6238
6586
  return;
6239
6587
  }
@@ -6260,8 +6608,12 @@ async function runHttp(factory, opts = {}) {
6260
6608
  const sessionId = req.headers["mcp-session-id"];
6261
6609
  const existing = sessionId ? transports.get(sessionId) : void 0;
6262
6610
  if (existing) {
6611
+ if (!samePrincipal(existing.principal, principal)) {
6612
+ res.writeHead(403, { "content-type": "application/json" }).end('{"error":"session belongs to a different principal"}');
6613
+ return;
6614
+ }
6263
6615
  const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
6264
- await existing.handleRequest(req, res, body2);
6616
+ await existing.transport.handleRequest(req, res, body2);
6265
6617
  return;
6266
6618
  }
6267
6619
  if (req.method !== "POST") {
@@ -6270,18 +6622,18 @@ async function runHttp(factory, opts = {}) {
6270
6622
  }
6271
6623
  const body = await readJsonBody(req);
6272
6624
  const transport = new import_streamableHttp.StreamableHTTPServerTransport({
6273
- sessionIdGenerator: () => (0, import_node_crypto5.randomUUID)(),
6625
+ sessionIdGenerator: () => (0, import_node_crypto6.randomUUID)(),
6274
6626
  enableDnsRebindingProtection: true,
6275
6627
  allowedHosts,
6276
6628
  ...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
6277
6629
  onsessioninitialized: (id) => {
6278
- transports.set(id, transport);
6630
+ transports.set(id, { transport, principal });
6279
6631
  }
6280
6632
  });
6281
6633
  transport.onclose = () => {
6282
6634
  if (transport.sessionId) transports.delete(transport.sessionId);
6283
6635
  };
6284
- await factory().connect(transport);
6636
+ await factory(principal).connect(transport);
6285
6637
  await transport.handleRequest(req, res, body);
6286
6638
  } catch (err) {
6287
6639
  process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
@@ -7908,6 +8260,8 @@ async function runApi(opts) {
7908
8260
  const token = opts.token;
7909
8261
  const graphqlEnabled = opts.graphql !== false;
7910
8262
  const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
8263
+ const authStore = opts.auth?.store;
8264
+ const rbacMode = !!(authStore && authStore.count() > 0);
7911
8265
  const log2 = opts.log ?? (() => {
7912
8266
  });
7913
8267
  const restDeps = { backend: opts.backend, version: opts.version };
@@ -7966,23 +8320,44 @@ async function runApi(opts) {
7966
8320
  finish(r.status);
7967
8321
  return;
7968
8322
  }
7969
- if (!checkBearer(req.headers["authorization"], token)) {
8323
+ const principal = resolvePrincipal(bearerToken(req.headers["authorization"]), {
8324
+ ...authStore ? { store: authStore } : {},
8325
+ ...token ? { sharedToken: token } : {},
8326
+ defaultTenant,
8327
+ ...opts.auth?.required ? { required: true } : {}
8328
+ });
8329
+ if (!principal) {
7970
8330
  send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
7971
8331
  finish(401);
7972
8332
  return;
7973
8333
  }
7974
- let ctx;
7975
8334
  try {
7976
- ctx = resolveTenant(req, url, opts.tenant ?? {});
7977
- tenantLabel = ctx.tenant;
8335
+ authorize(principal, "read");
7978
8336
  } catch (err) {
7979
- if (err instanceof InvalidTenantError) {
7980
- send(res, 400, { error: "invalid tenant" }, cors);
7981
- finish(400);
8337
+ if (err instanceof AuthorizationError) {
8338
+ send(res, 403, { error: "forbidden" }, cors);
8339
+ finish(403);
7982
8340
  return;
7983
8341
  }
7984
8342
  throw err;
7985
8343
  }
8344
+ let ctx;
8345
+ if (rbacMode) {
8346
+ ctx = { tenant: principal.tenant };
8347
+ tenantLabel = principal.tenant;
8348
+ } else {
8349
+ try {
8350
+ ctx = resolveTenant(req, url, opts.tenant ?? {});
8351
+ tenantLabel = ctx.tenant;
8352
+ } catch (err) {
8353
+ if (err instanceof InvalidTenantError) {
8354
+ send(res, 400, { error: "invalid tenant" }, cors);
8355
+ finish(400);
8356
+ return;
8357
+ }
8358
+ throw err;
8359
+ }
8360
+ }
7986
8361
  if (graphqlEnabled && path === "/graphql") {
7987
8362
  if (req.method === "GET") {
7988
8363
  const g = handleGraphqlGet();
@@ -8052,6 +8427,30 @@ function dispatchRest(ctx, path, url, deps) {
8052
8427
  }
8053
8428
  }
8054
8429
 
8430
+ // src/auth/types.ts
8431
+ var import_zod9 = require("zod");
8432
+ var ROLES = ["viewer", "operator", "admin"];
8433
+ var RoleSchema = import_zod9.z.enum(ROLES);
8434
+ var ACTIONS = ["read", "discovery", "admin"];
8435
+ var ActionSchema = import_zod9.z.enum(ACTIONS);
8436
+ var PrincipalSchema = import_zod9.z.object({
8437
+ subject: import_zod9.z.string().min(1),
8438
+ tenant: import_zod9.z.string().min(1),
8439
+ role: RoleSchema
8440
+ });
8441
+ var CredentialConfigSchema = import_zod9.z.object({
8442
+ token: import_zod9.z.string().min(1),
8443
+ subject: import_zod9.z.string().min(1),
8444
+ tenant: import_zod9.z.string().optional(),
8445
+ role: RoleSchema.default("viewer")
8446
+ });
8447
+ var AuthConfigSchema = import_zod9.z.object({
8448
+ /** Seed credentials (merged into the SQLite store on startup). */
8449
+ credentials: import_zod9.z.array(CredentialConfigSchema).optional(),
8450
+ /** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */
8451
+ required: import_zod9.z.boolean().optional()
8452
+ });
8453
+
8055
8454
  // src/api/start.ts
8056
8455
  var import_node_fs5 = require("fs");
8057
8456
  var import_node_path5 = require("path");
@@ -8079,6 +8478,7 @@ function parseApiArgs(argv) {
8079
8478
  else if (a === "--db") opts.dbPath = argv[++i];
8080
8479
  else if (a === "--session") opts.session = argv[++i];
8081
8480
  else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
8481
+ else if (a === "--auth-required") opts.authRequired = true;
8082
8482
  else if (a === "--help" || a === "-h") opts.help = true;
8083
8483
  }
8084
8484
  return opts;
@@ -8094,11 +8494,13 @@ async function startApi(opts = {}) {
8094
8494
  const host2 = opts.host ?? "127.0.0.1";
8095
8495
  const port = opts.port ?? 3737;
8096
8496
  const version = readVersion();
8497
+ const authStore = new SqliteCredentialStore(db);
8097
8498
  const server = await runApi({
8098
8499
  host: host2,
8099
8500
  port,
8100
8501
  backend,
8101
8502
  version,
8503
+ auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
8102
8504
  ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
8103
8505
  ...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
8104
8506
  ...token ? { token } : {},
@@ -8593,13 +8995,13 @@ function createClaudeProvider() {
8593
8995
  }
8594
8996
 
8595
8997
  // src/providers/shell.ts
8596
- var import_zod9 = require("zod");
8998
+ var import_zod10 = require("zod");
8597
8999
  function createBashTool() {
8598
9000
  const shell = IS_WIN ? "powershell" : "posix";
8599
9001
  return {
8600
9002
  name: "Bash",
8601
9003
  description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
8602
- inputShape: { command: import_zod9.z.string().describe("The read-only shell command to run") },
9004
+ inputShape: { command: import_zod10.z.string().describe("The read-only shell command to run") },
8603
9005
  annotations: { readOnlyHint: true, openWorldHint: true },
8604
9006
  handler: async (args) => {
8605
9007
  const command = String(args["command"] ?? "").trim();
@@ -11111,9 +11513,9 @@ async function runOnce(cfg, db) {
11111
11513
  }
11112
11514
 
11113
11515
  // src/sync/hash.ts
11114
- var import_node_crypto6 = require("crypto");
11516
+ var import_node_crypto7 = require("crypto");
11115
11517
  function shareHash(kind, payload) {
11116
- return (0, import_node_crypto6.createHash)("sha256").update(stableStringify({ kind, payload })).digest("hex");
11518
+ return (0, import_node_crypto7.createHash)("sha256").update(stableStringify({ kind, payload })).digest("hex");
11117
11519
  }
11118
11520
 
11119
11521
  // src/sync/classify.ts
@@ -11157,7 +11559,7 @@ function classify2(input) {
11157
11559
  }
11158
11560
 
11159
11561
  // src/sync/push.ts
11160
- var import_node_crypto7 = require("crypto");
11562
+ var import_node_crypto8 = require("crypto");
11161
11563
  var PUSH_SCHEMA_VERSION = 1;
11162
11564
  var DEFAULT_BATCH = 100;
11163
11565
  var DEFAULT_RETRIES = 4;
@@ -11171,7 +11573,7 @@ function defaultSleep(ms) {
11171
11573
  }
11172
11574
  function batchKey(items) {
11173
11575
  const hashes = items.map((i) => i.contentHash).sort();
11174
- return (0, import_node_crypto7.createHash)("sha256").update(stableStringify(hashes)).digest("hex");
11576
+ return (0, import_node_crypto8.createHash)("sha256").update(stableStringify(hashes)).digest("hex");
11175
11577
  }
11176
11578
  async function pushDeltas(config, items, opts = {}) {
11177
11579
  const central = config.centralDb;
@@ -11377,6 +11779,10 @@ function checkClaudePrerequisites() {
11377
11779
  }
11378
11780
  // Annotate the CommonJS export names for ESM import in node:
11379
11781
  0 && (module.exports = {
11782
+ ACTIONS,
11783
+ ActionSchema,
11784
+ AuthConfigSchema,
11785
+ AuthorizationError,
11380
11786
  CLIENTS,
11381
11787
  CONFIDENCE,
11382
11788
  CartographyDB,
@@ -11385,6 +11791,7 @@ function checkClaudePrerequisites() {
11385
11791
  ConditionSchema,
11386
11792
  ConfigError,
11387
11793
  ControlResultSchema,
11794
+ CredentialConfigSchema,
11388
11795
  CsvCostSource,
11389
11796
  DEFAULT_ANOMALY_THRESHOLDS,
11390
11797
  DEFAULT_SERVER_NAME,
@@ -11393,16 +11800,22 @@ function checkClaudePrerequisites() {
11393
11800
  INGEST_SCHEMA_VERSION,
11394
11801
  IngestEnvelopeSchema,
11395
11802
  InvalidTenantError,
11803
+ JiraSink,
11396
11804
  LOOPBACK_HOSTS,
11397
11805
  MCP_BIN,
11398
11806
  NotFoundError,
11399
11807
  PACKAGE_NAME,
11808
+ PAGERDUTY_ENQUEUE_URL,
11400
11809
  PERSONAL,
11401
11810
  PORT_MAP,
11402
11811
  PRIVATE_IP,
11403
11812
  PUSH_SCHEMA_VERSION,
11813
+ PagerDutySink,
11814
+ PrincipalSchema,
11404
11815
  ProviderRegistry,
11405
11816
  RELATION_TO_DIRECTION,
11817
+ ROLES,
11818
+ RoleSchema,
11406
11819
  RuleCheckSchema,
11407
11820
  RulesetSchema,
11408
11821
  SCAN_ARG_PATTERNS,
@@ -11412,10 +11825,13 @@ function checkClaudePrerequisites() {
11412
11825
  ScannerRegistry,
11413
11826
  ScannerShape,
11414
11827
  SharingLevelSchema,
11828
+ SlackSink,
11829
+ SqliteCredentialStore,
11415
11830
  SqliteQueryBackend,
11416
11831
  SqliteStoreBackend,
11417
11832
  StdoutSink,
11418
11833
  TENANT_HEADER,
11834
+ TenantMismatchError,
11419
11835
  VectorStore,
11420
11836
  WebhookSink,
11421
11837
  applyInstall,
@@ -11423,7 +11839,9 @@ function checkClaudePrerequisites() {
11423
11839
  assertReadOnly,
11424
11840
  assertSafeBind,
11425
11841
  assertSafeScanArg,
11842
+ assertSameTenant,
11426
11843
  assignColors,
11844
+ authorize,
11427
11845
  bearerToken,
11428
11846
  bookmarksScanner,
11429
11847
  buildCartographyToolHandlers,
@@ -11431,6 +11849,7 @@ function checkClaudePrerequisites() {
11431
11849
  buildOpenApiDocument,
11432
11850
  buildReport,
11433
11851
  buildSinks,
11852
+ can,
11434
11853
  centralDbFromEnv,
11435
11854
  checkBearer,
11436
11855
  checkPrerequisites,
@@ -11496,6 +11915,9 @@ function checkClaudePrerequisites() {
11496
11915
  filterBySeverity,
11497
11916
  findAnonViolations,
11498
11917
  formatComplianceText,
11918
+ formatJira,
11919
+ formatPagerDuty,
11920
+ formatSlack,
11499
11921
  generateDependencyMermaid,
11500
11922
  generateDiffMermaid,
11501
11923
  generateTopologyMermaid,
@@ -11504,6 +11926,7 @@ function checkClaudePrerequisites() {
11504
11926
  globalId,
11505
11927
  groupByDomain,
11506
11928
  handleGraphqlGet,
11929
+ hashToken,
11507
11930
  hexCorners,
11508
11931
  hexDistance,
11509
11932
  hexNeighbors,
@@ -11518,6 +11941,7 @@ function checkClaudePrerequisites() {
11518
11941
  isPersonalHost,
11519
11942
  isReadOnlyCommand,
11520
11943
  isRemembered,
11944
+ isSecureWebhookUrl,
11521
11945
  k8sScanner,
11522
11946
  keyMetaOf,
11523
11947
  layoutClusters,
@@ -11556,6 +11980,7 @@ function checkClaudePrerequisites() {
11556
11980
  pixelToHex,
11557
11981
  planInstall,
11558
11982
  portsScanner,
11983
+ postJson,
11559
11984
  previewShare,
11560
11985
  pseudonymize,
11561
11986
  pseudonymizeFragment,
@@ -11568,6 +11993,7 @@ function checkClaudePrerequisites() {
11568
11993
  renderDiff,
11569
11994
  resolveEffectiveLevel,
11570
11995
  resolveNlQuery,
11996
+ resolvePrincipal,
11571
11997
  resolveSharingLevel,
11572
11998
  resolveTenant,
11573
11999
  revalidateAnonymized,
@@ -11587,6 +12013,7 @@ function checkClaudePrerequisites() {
11587
12013
  safetyHook,
11588
12014
  sanitizeUntrusted,
11589
12015
  sanitizeValue,
12016
+ scopeReads,
11590
12017
  scoreTopology,
11591
12018
  securityRelevantChange,
11592
12019
  serializeConfig,