@datasynx/agentic-ai-cartography 2.2.0 → 2.4.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.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import { z } from 'zod';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import http from 'node:http';
4
+ import http, { IncomingMessage, Server } from 'node:http';
5
5
  import { McpServerConfig, HookCallback } from '@anthropic-ai/claude-agent-sdk';
6
6
 
7
7
  /**
@@ -390,14 +390,22 @@ interface DriftAlert {
390
390
  /** ISO-8601 UTC generation time. */
391
391
  generatedAt: string;
392
392
  }
393
- /** One configured drift sink. `url` is required when `type === 'webhook'`. */
393
+ /** One configured drift sink. `url` is required for every type except `stdout`. */
394
394
  interface DriftSinkConfig {
395
- type: 'stdout' | 'webhook';
396
- /** Required when type === 'webhook'. */
395
+ type: 'stdout' | 'webhook' | 'slack' | 'pagerduty' | 'jira';
396
+ /** Required for `webhook`/`slack`/`jira`; optional for `pagerduty` (defaults to the Events API). */
397
397
  url?: string;
398
- /** Optional bearer token; falls back to CARTOGRAPHY_DRIFT_TOKEN. */
398
+ /** Bearer token (`webhook`) / Jira API token (`jira`); falls back to CARTOGRAPHY_DRIFT_TOKEN. */
399
399
  token?: string;
400
400
  timeoutMs?: number;
401
+ /** PagerDuty Events API v2 routing key (rides in the body). Falls back to `token`/CARTOGRAPHY_DRIFT_TOKEN. */
402
+ routingKey?: string;
403
+ /** Jira account email (basic-auth user). */
404
+ email?: string;
405
+ /** Jira target project key, e.g. "OPS". */
406
+ project?: string;
407
+ /** Jira issue type name (default "Task"). */
408
+ issueType?: string;
401
409
  }
402
410
  /**
403
411
  * Opt-in drift-alerting block on {@link CartographyConfig}. Absent → the runner
@@ -420,10 +428,17 @@ declare const DriftConfigSchema: z.ZodObject<{
420
428
  type: z.ZodEnum<{
421
429
  stdout: "stdout";
422
430
  webhook: "webhook";
431
+ slack: "slack";
432
+ pagerduty: "pagerduty";
433
+ jira: "jira";
423
434
  }>;
424
435
  url: z.ZodOptional<z.ZodString>;
425
436
  token: z.ZodOptional<z.ZodString>;
426
437
  timeoutMs: z.ZodOptional<z.ZodNumber>;
438
+ routingKey: z.ZodOptional<z.ZodString>;
439
+ email: z.ZodOptional<z.ZodString>;
440
+ project: z.ZodOptional<z.ZodString>;
441
+ issueType: z.ZodOptional<z.ZodString>;
427
442
  }, z.core.$strip>>>;
428
443
  }, z.core.$strip>;
429
444
  /** Machine-readable result formats shared by `discover` (#67) and `schedule`. */
@@ -1378,6 +1393,92 @@ declare class SqliteStoreBackend implements StoreBackend {
1378
1393
  close(): void;
1379
1394
  }
1380
1395
 
1396
+ /**
1397
+ * `QueryBackend` — the **read-only** query seam for the API server (4.2).
1398
+ *
1399
+ * This is deliberately distinct from {@link StoreBackend} (`src/store/backend.ts`),
1400
+ * which is the central-collector **write/ingest** seam. The two seams have opposite
1401
+ * shapes: ingest merges incoming deltas; this one answers topology questions. A
1402
+ * non-SQLite backend (4.3) implements both. Keeping them separate means the API
1403
+ * never gains a write path and the ingest core never gains a query path.
1404
+ *
1405
+ * Every method takes a {@link TenantContext}. Session resolution is tenant-scoped, so
1406
+ * a caller bound to tenant A can never read tenant B's topology — even by naming a
1407
+ * session id that belongs to B (it resolves to "not found", never B's data). This
1408
+ * mirrors the MCP server's `resolveSession` tenant guard exactly.
1409
+ */
1410
+
1411
+ /** The tenant (org-scope) a request is bound to. `'local'` (DEFAULT_TENANT) until a real org is supplied. */
1412
+ interface TenantContext {
1413
+ tenant: string;
1414
+ }
1415
+ interface NodeQuery {
1416
+ search?: string;
1417
+ types?: readonly string[];
1418
+ limit?: number;
1419
+ offset?: number;
1420
+ }
1421
+ interface DependencyQuery {
1422
+ direction?: 'downstream' | 'upstream' | 'both';
1423
+ maxDepth?: number;
1424
+ }
1425
+ interface NodesResult {
1426
+ nodes: NodeRow[];
1427
+ total: number;
1428
+ limit: number;
1429
+ offset: number;
1430
+ }
1431
+ interface HealthResult {
1432
+ store: 'sqlite';
1433
+ sessions: number;
1434
+ }
1435
+ /** A requested resource (session / diff endpoint) does not exist for this tenant → REST 404. */
1436
+ declare class NotFoundError extends Error {
1437
+ constructor(message: string);
1438
+ }
1439
+ /** Narrow, read-only view of the topology store. Tenant is required on every call. */
1440
+ interface QueryBackend {
1441
+ /** Aggregate, low-token index of the resolved session. Throws {@link NotFoundError} if no session resolves. */
1442
+ summary(ctx: TenantContext, sessionId?: string): GraphSummary;
1443
+ /** Page/search nodes of the resolved session. Throws {@link NotFoundError} if no session resolves. */
1444
+ nodes(ctx: TenantContext, q: NodeQuery, sessionId?: string): NodesResult;
1445
+ /** One node by id (or `undefined` if absent). Throws {@link NotFoundError} if no session resolves. */
1446
+ node(ctx: TenantContext, id: string, sessionId?: string): NodeRow | undefined;
1447
+ /** Dependency traversal from a node. Throws {@link NotFoundError} if no session resolves. */
1448
+ dependencies(ctx: TenantContext, id: string, q: DependencyQuery, sessionId?: string): TraversalResult;
1449
+ /** Compare two sessions (both must belong to the tenant). Throws {@link NotFoundError} on an unknown/foreign id. */
1450
+ diff(ctx: TenantContext, base: string, current: string): TopologyDiff;
1451
+ /** All sessions for this tenant, newest first. */
1452
+ sessions(ctx: TenantContext): SessionRow[];
1453
+ /** Liveness/coverage probe (never resolves a session). */
1454
+ health(ctx: TenantContext): HealthResult;
1455
+ }
1456
+ /**
1457
+ * `QueryBackend` over the local `CartographyDB`. A thin read adapter: the schema,
1458
+ * migrations, and SQL all live in `db.ts`; this only resolves the tenant-scoped
1459
+ * session and forwards. Constructing it adds no state and no schema.
1460
+ */
1461
+ declare class SqliteQueryBackend implements QueryBackend {
1462
+ private readonly db;
1463
+ private readonly defaultSession;
1464
+ constructor(db: CartographyDB, defaultSession?: string | 'latest');
1465
+ /**
1466
+ * Resolve the session id for a request, scoped to `ctx.tenant`. An explicit id must
1467
+ * belong to the tenant or it resolves to undefined (cross-tenant isolation); else the
1468
+ * newest `discover` session for the tenant. Mirrors `resolveSession` in the MCP server.
1469
+ */
1470
+ private resolveSession;
1471
+ summary(ctx: TenantContext, sessionId?: string): GraphSummary;
1472
+ nodes(ctx: TenantContext, q: NodeQuery, sessionId?: string): NodesResult;
1473
+ node(ctx: TenantContext, id: string, sessionId?: string): NodeRow | undefined;
1474
+ dependencies(ctx: TenantContext, id: string, q: DependencyQuery, sessionId?: string): TraversalResult;
1475
+ diff(ctx: TenantContext, base: string, current: string): TopologyDiff;
1476
+ sessions(ctx: TenantContext): SessionRow[];
1477
+ health(ctx: TenantContext): HealthResult;
1478
+ }
1479
+ /** Construct the default SQLite-backed read query backend. */
1480
+ declare function createSqliteQueryBackend(db: CartographyDB, defaultSession?: string | 'latest'): QueryBackend;
1481
+
1381
1482
  /**
1382
1483
  * Global-identity merge core for the central collector (2.12) — pure, no I/O.
1383
1484
  *
@@ -1960,8 +2061,203 @@ interface HttpOptions {
1960
2061
  body: unknown;
1961
2062
  };
1962
2063
  }
2064
+ /**
2065
+ * Start a Streamable HTTP server. A fresh MCP server instance is created per
2066
+ * session via `factory`, so multiple clients can connect concurrently.
2067
+ */
1963
2068
  declare function runHttp(factory: () => McpServer, opts?: HttpOptions): Promise<http.Server>;
1964
2069
 
2070
+ /**
2071
+ * Shared HTTP auth + bind-hardening primitives.
2072
+ *
2073
+ * Extracted verbatim from `src/mcp/transports.ts` so the MCP transport, the REST/
2074
+ * GraphQL API server (4.2), and any future HTTP surface consume **one** provably-
2075
+ * identical implementation of the CVE-2025-66414 guards, the constant-time bearer
2076
+ * compare, and the default Host allowlist. The allowlist — not any one caller — is
2077
+ * the security boundary; centralizing it here keeps every networked surface on the
2078
+ * same posture and makes the behavior unit-testable in isolation.
2079
+ */
2080
+ /** Loopback hosts are safe to bind without an explicit Host allowlist. */
2081
+ declare const LOOPBACK_HOSTS: ReadonlySet<string>;
2082
+ /** True when `host` is a loopback address (safe to bind without an allowlist/token). */
2083
+ declare function isLoopbackHost(host: string): boolean;
2084
+ /** Constant-time comparison to avoid leaking the token via timing. */
2085
+ declare function timingSafeEqual(a: string, b: string): boolean;
2086
+ /**
2087
+ * Extract the bearer token from an Authorization header, if present. Parsed with
2088
+ * linear string ops (no regex) so a user-controlled header can never trigger
2089
+ * polynomial backtracking (ReDoS) — `^Bearer\s+(.+)$` is ambiguous between `\s+`
2090
+ * and `.+` on a long run of spaces.
2091
+ */
2092
+ declare function bearerToken(header: string | undefined): string | undefined;
2093
+ /**
2094
+ * Returns true if the request is authenticated: a request is authenticated when no
2095
+ * token is configured (open loopback dev mode) OR the `Authorization: Bearer` value
2096
+ * is present and constant-time-equal to the configured token. The caller maps a
2097
+ * `false` to a 401.
2098
+ */
2099
+ declare function checkBearer(authorizationHeader: string | undefined, token: string | undefined): boolean;
2100
+ interface BindGuardOptions {
2101
+ host: string;
2102
+ port: number;
2103
+ allowedHosts?: string[];
2104
+ token?: string;
2105
+ }
2106
+ /**
2107
+ * Enforce the CVE-2025-66414 + mandatory-token guards before binding. Throws the
2108
+ * exact errors `runHttp` raised inline, so existing transport behavior is preserved:
2109
+ * a non-loopback bind requires BOTH an explicit `allowedHosts` allowlist AND a token.
2110
+ */
2111
+ declare function assertSafeBind(opts: BindGuardOptions): void;
2112
+ /** Default Host allowlist: the bound host plus the localhost variants, all `:port`. */
2113
+ declare function defaultAllowedHosts(host: string, port: number): string[];
2114
+
2115
+ /**
2116
+ * Per-request tenant resolution for the API server (4.2).
2117
+ *
2118
+ * The tenant (org-scope) is a first-class request property: it is resolved once,
2119
+ * up front, and threaded into every {@link QueryBackend} call so isolation is
2120
+ * structural, not bolted on. A request may name a tenant via the
2121
+ * `X-Cartograph-Tenant` header or a `?tenant=` query param; absent either, it
2122
+ * defaults to the server's configured default (normally `DEFAULT_TENANT='local'`).
2123
+ *
2124
+ * Validation reuses `normalizeTenant` (the single charset-allowlisted validator,
2125
+ * `^[\w.@:+-]{1,128}$`) — but here we **reject** a malformed value with a typed
2126
+ * error (→ HTTP 400) rather than silently falling back, so a client never believes
2127
+ * it is scoped to one tenant while being served another. The raw input is never
2128
+ * reflected into a response.
2129
+ */
2130
+
2131
+ declare const TENANT_HEADER = "x-cartograph-tenant";
2132
+ /** The supplied tenant value did not pass the charset/length allowlist → HTTP 400. */
2133
+ declare class InvalidTenantError extends Error {
2134
+ constructor();
2135
+ }
2136
+ interface TenantOptions {
2137
+ /** Default tenant when the request names none. Defaults to `DEFAULT_TENANT` ('local'). */
2138
+ defaultTenant?: string;
2139
+ /** Header to read the tenant from. Defaults to `x-cartograph-tenant`. */
2140
+ header?: string;
2141
+ }
2142
+ /**
2143
+ * Resolve the tenant from the request header or `?tenant=` query param, else the
2144
+ * configured default. A supplied-but-malformed value throws {@link InvalidTenantError}
2145
+ * (the caller maps it to a 400) instead of silently defaulting.
2146
+ */
2147
+ declare function resolveTenant(req: IncomingMessage, url: URL, opts?: TenantOptions): TenantContext;
2148
+
2149
+ /**
2150
+ * The read-only API HTTP server (4.2), on Node's built-in `http` (zero new runtime dep).
2151
+ *
2152
+ * Request flow mirrors the MCP transport (`src/mcp/transports.ts`): the CVE-2025-66414
2153
+ * bind guards run at startup (shared `assertSafeBind`); per request the Host header is
2154
+ * checked against the allowlist (DNS-rebinding), then the bearer token is verified
2155
+ * **before any backend access**, then the tenant is resolved, then the route dispatches.
2156
+ * REST handlers are pure (`rest.ts`); GraphQL is wired when enabled (`graphql.ts`). One
2157
+ * structured stderr access line per request — never the token, never query values.
2158
+ */
2159
+
2160
+ interface ApiServerOptions extends BindGuardOptions {
2161
+ backend: QueryBackend;
2162
+ version: string;
2163
+ /** CORS Origin allowlist. Default: none (same-origin only). */
2164
+ allowedOrigins?: string[];
2165
+ /** Tenant resolution options (header name / default tenant). */
2166
+ tenant?: TenantOptions;
2167
+ /** Expose `/graphql` (default true). */
2168
+ graphql?: boolean;
2169
+ /** Access logger (stderr). */
2170
+ log?: (msg: string) => void;
2171
+ }
2172
+ /** Start the read-only API server. Resolves once it is listening. */
2173
+ declare function runApi(opts: ApiServerOptions): Promise<http.Server>;
2174
+
2175
+ /**
2176
+ * OpenAPI 3.1 document generation for the read-only API (4.2).
2177
+ *
2178
+ * The document is **generated from the zod response schemas** (`schemas.ts`), never
2179
+ * hand-maintained, so it cannot drift from what the server actually returns. A
2180
+ * committed copy lives at `docs/api/openapi.json`; a test asserts the built document
2181
+ * deep-equals it (drift guard) and validates under `ajv`.
2182
+ *
2183
+ * `zodToJsonSchema` is a small, fail-closed projection covering exactly the zod
2184
+ * constructs `schemas.ts` uses (object/array/string/number/integer/boolean/enum/
2185
+ * literal/record/optional). An unsupported construct throws, so a future schema
2186
+ * change can't be silently mis-projected. (The provider tool layer has its own flat
2187
+ * converter in `src/providers/zod-schema.ts`; this one is recursive and serves the
2188
+ * API's nested response shapes.)
2189
+ */
2190
+
2191
+ /** Project a zod schema to a JSON-Schema (2020-12) fragment. Fail-closed on the unknown. */
2192
+ declare function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown>;
2193
+ interface OpenApiOptions {
2194
+ version: string;
2195
+ }
2196
+ /** Build the OpenAPI 3.1 document from the zod schemas + the static route table. Deterministic. */
2197
+ declare function buildOpenApiDocument(opts: OpenApiOptions): Record<string, unknown>;
2198
+
2199
+ /**
2200
+ * Hand-rolled, zero-dependency GraphQL layer for the read-only API (4.2).
2201
+ *
2202
+ * Mirrors REST over `POST /graphql` (and serves the SDL on `GET /graphql`) without
2203
+ * adding a `graphql`/`apollo` runtime dependency. Resolvers delegate to the same
2204
+ * {@link QueryBackend} and reuse the REST projections (`rest.ts`), so REST and GraphQL
2205
+ * return byte-identical shapes and the consent posture stays in one place. It is
2206
+ * strictly **read-only**: there is no `Mutation` type and a `mutation` document is
2207
+ * rejected. A small tokenizer/parser handles the query subset the schema needs
2208
+ * (fields, arguments, variables, nested selections) and a minimal `__schema`
2209
+ * introspection response keeps GraphiQL-style clients working.
2210
+ */
2211
+
2212
+ interface GraphqlDeps {
2213
+ backend: QueryBackend;
2214
+ }
2215
+ interface GraphqlResult {
2216
+ data?: unknown;
2217
+ errors?: Array<{
2218
+ message: string;
2219
+ }>;
2220
+ }
2221
+ declare const SDL = "# Cartograph read-only GraphQL API (4.2). Mirrors the REST surface.\nschema { query: Query }\n\ntype Query {\n summary(session: String): Summary\n nodes(search: String, types: [String!], limit: Int, offset: Int, session: String): NodeConnection\n node(id: String!, session: String): Node\n dependencies(id: String!, direction: Direction, maxDepth: Int, session: String): Dependencies\n diff(base: String!, current: String!): Diff\n sessions: [Session!]!\n}\n\nenum Direction { downstream upstream both }\n\ntype Totals { nodes: Int! edges: Int! }\ntype Count { key: String! value: Int! }\ntype TopConnected { id: String! name: String! type: String! degree: Int! }\ntype Anomaly { nodeId: String! kind: String! severity: String! reason: String! }\ntype Cost { amount: Float! currency: String! period: String! source: String }\ntype CostRollup { key: String! currency: String! period: String! total: Float! nodes: Int! }\ntype CostCoverage { withCost: Int! total: Int! }\n\ntype Node {\n id: String! type: String! name: String! confidence: Float!\n domain: String subDomain: String qualityScore: Float owner: String cost: Cost tags: [String!]!\n}\ntype DependencyNode {\n id: String! type: String! name: String! confidence: Float!\n domain: String subDomain: String qualityScore: Float owner: String cost: Cost tags: [String!]! depth: Int!\n}\ntype Edge { sourceId: String! targetId: String! relationship: String! confidence: Float! evidence: String! }\n\ntype Summary {\n sessionId: String!\n totals: Totals!\n topConnected: [TopConnected!]!\n anomalies: [Anomaly!]!\n contributors: Int!\n costByDomain: [CostRollup!]!\n costByOwner: [CostRollup!]!\n costCoverage: CostCoverage!\n}\n\ntype NodeConnection { nodes: [Node!]! total: Int! limit: Int! offset: Int! }\ntype Dependencies { root: Node direction: Direction! maxDepth: Int! nodes: [DependencyNode!]! edges: [Edge!]! }\n\ntype SessionEndpoint { sessionId: String! startedAt: String! nodeCount: Int! edgeCount: Int! }\ntype DiffSummary { nodesAdded: Int! nodesRemoved: Int! nodesChanged: Int! edgesAdded: Int! edgesRemoved: Int! }\ntype NodeChange { id: String! changedFields: [String!]! confidenceDelta: Float! }\ntype DiffNodes { added: [Node!]! removed: [Node!]! changed: [NodeChange!]! unchanged: Int! }\ntype DiffEdges { added: [Edge!]! removed: [Edge!]! unchanged: Int! }\ntype DiffAnomalies { added: [Anomaly!]! }\ntype Diff {\n base: SessionEndpoint! current: SessionEndpoint! summary: DiffSummary!\n nodes: DiffNodes! edges: DiffEdges! anomalies: DiffAnomalies!\n}\n\ntype Session { id: String! mode: String! startedAt: String! completedAt: String name: String tenant: String! lastScannedAt: String }\n";
2222
+ /** Execute a `{ query, variables, operationName }` request. Read-only; rejects mutations. */
2223
+ declare function executeGraphql(ctx: TenantContext, body: unknown, deps: GraphqlDeps): Promise<GraphqlResult>;
2224
+ /** `GET /graphql` → the SDL as text/plain. */
2225
+ declare function handleGraphqlGet(): {
2226
+ status: number;
2227
+ body: string;
2228
+ };
2229
+
2230
+ /**
2231
+ * Shared entry logic for the read-only API server (4.2), used by both the dedicated
2232
+ * `cartography-api` binary and the `api` CLI sub-command. Mirrors `src/mcp/start.ts`:
2233
+ * opens the catalog, builds the SQLite query backend, resolves the bearer token from
2234
+ * `--token`/`CARTOGRAPHY_HTTP_TOKEN`, and starts `runApi`. All logging is to stderr;
2235
+ * the token value is never logged (only whether one is set).
2236
+ */
2237
+
2238
+ interface StartApiOptions {
2239
+ dbPath?: string;
2240
+ session?: string | 'latest';
2241
+ port?: number;
2242
+ host?: string;
2243
+ allowedHosts?: string[];
2244
+ allowedOrigins?: string[];
2245
+ token?: string;
2246
+ /** Expose `/graphql` (default true). */
2247
+ graphql?: boolean;
2248
+ /** Default tenant served when a request names none. */
2249
+ tenant?: string;
2250
+ log?: (msg: string) => void;
2251
+ }
2252
+ interface ParsedApiArgs extends StartApiOptions {
2253
+ /** `--help`/`-h` was passed; the caller should print usage and exit 0. */
2254
+ help?: boolean;
2255
+ }
2256
+ /** Parse `cartography-api` argv into StartApiOptions (unit-testable, no side effects). */
2257
+ declare function parseApiArgs(argv: string[]): ParsedApiArgs;
2258
+ /** Open the catalog, build the read backend, and start the API server. Returns the server. */
2259
+ declare function startApi(opts?: StartApiOptions): Promise<Server>;
2260
+
1965
2261
  declare const installedAppsScanner: Scanner;
1966
2262
 
1967
2263
  /** Well-known listening ports → node type + service name. */
@@ -2559,7 +2855,12 @@ declare const SCAN_ARG_PATTERNS: {
2559
2855
  type ScanArgKind = keyof typeof SCAN_ARG_PATTERNS;
2560
2856
  /** Throw if `value` fails the strict pattern for `kind`; otherwise return it. */
2561
2857
  declare function assertSafeScanArg(kind: ScanArgKind, value: string): string;
2562
- /** Redact `user:password@` credentials embedded in any URL/DSN-like string. */
2858
+ /**
2859
+ * Redact `user:password@` credentials embedded in any URL/DSN-like string. The
2860
+ * quantifiers are length-bounded (schemes <64, userinfo/password <256 chars — far
2861
+ * beyond any real DSN) so the pattern is linear and cannot polynomially backtrack
2862
+ * (ReDoS) on adversarial input like `aaaa…` with no `://`.
2863
+ */
2563
2864
  declare function redactSecrets(value: string): string;
2564
2865
  /** Recursively redact secrets from arbitrary metadata before persistence. */
2565
2866
  declare function redactValue(value: unknown): unknown;
@@ -2988,6 +3289,42 @@ interface WebhookSinkOptions {
2988
3289
  token?: string;
2989
3290
  timeoutMs?: number;
2990
3291
  }
3292
+ /** Injectable fetch (defaults to the global) — lets tests deliver without a socket. */
3293
+ type FetchLike = (url: string, init: RequestInit) => Promise<{
3294
+ ok: boolean;
3295
+ status: number;
3296
+ }>;
3297
+ interface PostJsonOptions {
3298
+ url: string;
3299
+ body: unknown;
3300
+ /** Extra request headers (e.g. provider auth). `content-type: application/json` is always set. */
3301
+ headers?: Record<string, string>;
3302
+ timeoutMs?: number;
3303
+ /** Sink name for structured logs (never logs the url/token/body). */
3304
+ sinkName: string;
3305
+ /** Injected fetch for tests; defaults to the global. */
3306
+ fetchImpl?: FetchLike;
3307
+ }
3308
+ /**
3309
+ * Shared outbound JSON POST carrying the load-bearing sink hardening, reused by
3310
+ * {@link WebhookSink} and the Slack/PagerDuty/Jira provider sinks so there is exactly
3311
+ * one egress code path:
3312
+ * - refuses a non-`https:` / non-loopback target ({@link isSecureWebhookUrl}, SSRF/plaintext guard);
3313
+ * - `AbortSignal.timeout` bounded;
3314
+ * - **never throws** for a transient failure (logs and resolves so the runner continues);
3315
+ * - logs only `stripSensitive(url)` (host:port) — never the full url, headers, token, or body.
3316
+ * The body must already be redaction-safe (callers pass `redactValue(...)` output or a
3317
+ * provider payload derived from it).
3318
+ */
3319
+ declare function postJson(opts: PostJsonOptions): Promise<void>;
3320
+ /**
3321
+ * True if `url` is safe to POST to: `https:`, or `http:` only to a loopback host,
3322
+ * or any scheme when the documented `CARTOGRAPHY_ALLOW_INSECURE_SYNC=1` escape
3323
+ * hatch (test-only) is set. An unparseable URL is treated as insecure. Mirrors the
3324
+ * 2.11 `pushDeltas` guard so the drift feature never silently exfiltrates over the
3325
+ * wire in plaintext.
3326
+ */
3327
+ declare function isSecureWebhookUrl(url: string, env?: NodeJS.ProcessEnv): boolean;
2991
3328
  /**
2992
3329
  * Outbound sink: POSTs the alert as JSON to an operator-configured endpoint. The
2993
3330
  * first and only outbound network surface of the drift feature — off by default
@@ -3009,11 +3346,114 @@ declare class WebhookSink implements DriftSink {
3009
3346
  emit(alert: DriftAlert): Promise<void>;
3010
3347
  }
3011
3348
 
3349
+ /**
3350
+ * Provider drift sinks (4.4): Slack / PagerDuty / Jira. Each is a thin {@link DriftSink}
3351
+ * that (1) redacts the alert (`redactValue`), (2) maps it to the provider payload via the
3352
+ * pure adapters in `providers.ts`, and (3) delivers it through the shared {@link postJson}
3353
+ * helper — inheriting the *exact* `WebhookSink` hardening (https-or-loopback SSRF guard,
3354
+ * bounded timeout, never-throw, `stripSensitive` log-only). They differ only in where the
3355
+ * provider places its secret: Slack in the URL, PagerDuty as a `routing_key` in the body,
3356
+ * Jira as an `Authorization: Basic` header.
3357
+ */
3358
+
3359
+ /** Default PagerDuty Events API v2 ingest endpoint. */
3360
+ declare const PAGERDUTY_ENQUEUE_URL = "https://events.pagerduty.com/v2/enqueue";
3361
+ interface BaseSinkOptions {
3362
+ url: string;
3363
+ timeoutMs?: number;
3364
+ /** Injected fetch for tests; defaults to the global. */
3365
+ fetchImpl?: FetchLike;
3366
+ }
3367
+ /** Slack incoming webhook. The configured `url` is the secret; no auth header is sent. */
3368
+ declare class SlackSink implements DriftSink {
3369
+ private readonly opts;
3370
+ readonly name = "slack";
3371
+ constructor(opts: BaseSinkOptions);
3372
+ emit(alert: DriftAlert): Promise<void>;
3373
+ }
3374
+ interface PagerDutySinkOptions extends BaseSinkOptions {
3375
+ /** Events API v2 routing key (rides in the body, PagerDuty's auth model). */
3376
+ routingKey: string;
3377
+ }
3378
+ /** PagerDuty Events API v2 `trigger`. `url` defaults to {@link PAGERDUTY_ENQUEUE_URL}. */
3379
+ declare class PagerDutySink implements DriftSink {
3380
+ private readonly opts;
3381
+ readonly name = "pagerduty";
3382
+ constructor(opts: PagerDutySinkOptions);
3383
+ emit(alert: DriftAlert): Promise<void>;
3384
+ }
3385
+ interface JiraSinkOptions extends BaseSinkOptions {
3386
+ /** Jira account email (basic-auth user). */
3387
+ email: string;
3388
+ /** Jira API token (basic-auth pass). */
3389
+ token: string;
3390
+ /** Target project key (e.g. "OPS"). */
3391
+ project: string;
3392
+ /** Issue type name (default "Task"). */
3393
+ issueType?: string;
3394
+ }
3395
+ /** Jira REST API v2 create-issue, authenticated with `Authorization: Basic base64(email:token)`. */
3396
+ declare class JiraSink implements DriftSink {
3397
+ private readonly opts;
3398
+ readonly name = "jira";
3399
+ constructor(opts: JiraSinkOptions);
3400
+ emit(alert: DriftAlert): Promise<void>;
3401
+ }
3402
+
3403
+ /**
3404
+ * Pure provider payload mappers (4.4): a classified {@link DriftAlert} → the JSON
3405
+ * shape Slack / PagerDuty / Jira each expect. Deterministic, no I/O, no secrets —
3406
+ * they operate on the **already-`redactValue`'d** alert the sinks pass in, and read
3407
+ * only structured fields (severity/counts/item refs), never raw node metadata. The
3408
+ * delivery + auth + SSRF hardening lives in `provider-sink.ts`/`webhook.ts`; these
3409
+ * are just shape transforms, so they are trivially snapshot-testable.
3410
+ */
3411
+
3412
+ interface SlackMessage {
3413
+ text: string;
3414
+ blocks: unknown[];
3415
+ }
3416
+ /** Map an alert to a Slack incoming-webhook message (Block Kit). The webhook URL is the secret. */
3417
+ declare function formatSlack(alert: DriftAlert): SlackMessage;
3418
+ interface PagerDutyEvent {
3419
+ routing_key: string;
3420
+ event_action: 'trigger';
3421
+ dedup_key: string;
3422
+ payload: {
3423
+ summary: string;
3424
+ source: string;
3425
+ severity: 'info' | 'warning' | 'critical';
3426
+ timestamp: string;
3427
+ custom_details: Record<string, unknown>;
3428
+ };
3429
+ }
3430
+ /** Map an alert to a PagerDuty Events API v2 `trigger`. `routingKey` rides in the body (PD's auth model). */
3431
+ declare function formatPagerDuty(alert: DriftAlert, routingKey: string): PagerDutyEvent;
3432
+ interface JiraIssue {
3433
+ fields: {
3434
+ project: {
3435
+ key: string;
3436
+ };
3437
+ issuetype: {
3438
+ name: string;
3439
+ };
3440
+ summary: string;
3441
+ description: string;
3442
+ };
3443
+ }
3444
+ interface JiraOptions {
3445
+ project: string;
3446
+ issueType?: string;
3447
+ }
3448
+ /** Map an alert to a Jira create-issue body. Auth (Basic email:token) is applied by the sink, not here. */
3449
+ declare function formatJira(alert: DriftAlert, opts: JiraOptions): JiraIssue;
3450
+
3012
3451
  /**
3013
3452
  * Construct sinks from config. Absent/empty config → `[new StdoutSink()]` (the
3014
- * local default). A webhook sink's token falls back to `CARTOGRAPHY_DRIFT_TOKEN`
3015
- * when not given explicitly (mirroring `CARTOGRAPHY_HTTP_TOKEN`). A webhook entry
3016
- * without a url is skipped defensively (the schema already rejects it).
3453
+ * local default). Secrets (webhook `token`, Jira token, PagerDuty routing key) fall
3454
+ * back to `CARTOGRAPHY_DRIFT_TOKEN` when not given explicitly (mirroring
3455
+ * `CARTOGRAPHY_HTTP_TOKEN`). A provider entry missing a required field is skipped
3456
+ * with a warning rather than aborting the others — graceful degradation.
3017
3457
  */
3018
3458
  declare function buildSinks(drift?: DriftConfig): DriftSink[];
3019
3459
 
@@ -3429,4 +3869,4 @@ declare function logInfo(message: string, context?: Record<string, unknown>): vo
3429
3869
  declare function logWarn(message: string, context?: Record<string, unknown>): void;
3430
3870
  declare function logError(message: string, context?: Record<string, unknown>): void;
3431
3871
 
3432
- export { ANOMALY_KINDS, ANOMALY_SEVERITIES, type AgentProvider, type AgentRunContext, type AgentTool, type Anomaly, type AnomalyConfig, type AnomalyKind, type AnomalySeverity, type AnomalyThresholds, type AnonViolation, type AnonymizationLevel, type AskUserFn, CLIENTS, CONFIDENCE, COST_PERIODS, type CartographyConfig, CartographyDB, type CartographyMapData, type CentralDbConfig, CentralDbConfigSchema, type ClassifiedItem, type ClassifyInput, type ClassifyResult, type ClientSpec, type Cluster, ClusterSchema, type ComplianceInput, type ComplianceReport, ComplianceReportSchema, type ComplianceRule, ComplianceRuleSchema, type Condition, ConditionSchema, ConfigError, type ConfigFile, ConfigFileSchema, type ConfigFormat, type Connection, ConnectionSchema, type Contributor, type ControlResult, ControlResultSchema, type CostEntry, CostEntrySchema, type CostPeriod, type CostRecord, type CostSource, type CreateMcpServerOptions, type CronFields, CsvCostSource, type CsvCostSourceOptions, DEFAULT_ANOMALY_THRESHOLDS, DEFAULT_FAST_MODEL, DEFAULT_LEAD_MODEL, DEFAULT_SERVER_NAME, DEFAULT_TENANT, DOMAIN_COLORS, DOMAIN_PALETTE, DRIFT_FIELDS, type DataAsset, DataAssetSchema, type DiscoveryEdge, type DiscoveryEvent, type DiscoveryFn, type DiscoveryNode, type DriftAlert, type DriftAlertItem, type DriftConfig, DriftConfigSchema, type DriftField, type DriftItemKind, type DriftRunRow, type DriftSink, type DriftSinkConfig, EDGE_RELATIONSHIPS, type EdgeRelationship, type EdgeRow, EdgeSchema, type EmbeddingProvider, type EnrichResult, type EntryOptions, type EstablishedConn, type EvidenceKind, type FragmentKind, type GraphSummary, type HttpOptions, INGEST_SCHEMA_VERSION, type IngestEnvelope, IngestEnvelopeSchema, type IngestHandler, type IngestOptions, type IngestResponse, type IngestResult, type InstallPlan, type LocalDiscoveryOptions, type LocalDiscoveryResult, type LogEntry, type LogLevel, MCP_BIN, type MatchStrategy, NODE_TYPES, NODE_TYPE_GROUPS, type NlIntent, type NlQueryOptions, type NlQueryResult, type NlRelation, type NodeAttribution, type NodeChange, type NodeIdentity, type NodeRow, NodeSchema, type NodeType, OUTPUT_FORMATS, type OrgKeyOptions, type OrgSummary, type OsKind, type OutputFormat, PACKAGE_NAME, PENDING_STATUSES, PERSONAL, PORT_MAP, PRIVATE_IP, PUSH_SCHEMA_VERSION, type PendingShareRow, type PendingStatus, type PlanOptions, type PolicyResult, type ProviderFactory, type ProviderName, ProviderRegistry, type PushItem, type PushOptions, type PushResult, RELATION_TO_DIRECTION, type ResolveContext, type RuleCheck, RuleCheckSchema, type RuleScope, type Ruleset, RulesetSchema, type RunDriftOptions, SCAN_ARG_PATTERNS, SECURITY_METADATA_KEYS, SEVERITIES, SEVERITY_WEIGHT, SHARING_LEVELS, type ScanArgKind, type ScanContext, type ScanHintParams, type ScanResult, type Scanner, type ScannerPlugin, type ScannerPluginApi, ScannerRegistry, ScannerShape, type ScheduleConfig, ScheduleConfigSchema, type ScheduledRunResult, type Scope, type SearchFn, type SemanticSearchOptions, type ServerEntry, type SessionRow, type Severity, type SharePreview, type SharePreviewEntry, type SharingLevel, SharingLevelSchema, type SharingPolicy, type ShellKind, SqliteStoreBackend, StdoutSink, type StoreBackend, type SyncClassifyOptions, type SyncClassifyResult, type ToolResult, type TopologyDelta, type TopologyDiff, type TopologyInput, type TraversalResult, VectorStore, WebhookSink, type WebhookSinkOptions, applyInstall, applySharingLevel, assertReadOnly, assertSafeScanArg, assignColors, bookmarksScanner, buildCartographyToolHandlers, buildMapData, buildReport, buildSinks, centralDbFromEnv, checkPrerequisites, checkReadOnly, clampText, classify, classifyDrift, cleanupTempFiles, cloudAwsScanner, cloudAzureScanner, cloudGcpScanner, codeAddMcpCommand, computeCentroid, computeClusterBounds, computeIdentity, connectionsScanner, contentHash, createBashTool, createCartographyTools, createClaudeProvider, createDefaultRegistry, createHashEmbedder, createIngestHandler, createLocalEmbedder, createMcpServer, createOllamaProvider, createOpenAIProvider, createScanRunner, createSemanticSearch, currentOs, cursorDeeplink, databasesScanner, deepMerge, defaultConfig, defaultContext, defaultProviderRegistry, defaultRegistry, defaultServerEntry, definePlugin, deriveSessionName, detectAnomalies, detectOrphans, detectShadowIt, diffTopology, edgesToConnections, enrichCosts, evaluateCheck, evaluateRule, evidenceLine, executeNlQuery, exportAll, exportBackstageYAML, exportComplianceReport, exportCostCSV, exportCostSummary, exportDiscoveryApp, exportJGF, exportJSON, extractListeningPorts, filterBySeverity, findAnonViolations, formatComplianceText, generateDependencyMermaid, generateDiffMermaid, generateTopologyMermaid, getClient, getRuleset, globalId, groupByDomain, hexCorners, hexDistance, hexNeighbors, hexRing, hexSpiral, hexToPixel, hmacKey, hostname, ingestEnvelope, installedAppsScanner, isPersonalHost, isReadOnlyCommand, isRemembered, k8sScanner, keyMetaOf, layoutClusters, listClients, listRulesets, loadConfig, loadOrgKey, loadPlugins, loadRuleset, localDiscoveryFn, log, logDebug, logError, logInfo, logWarn, machineId, maxSeverity, mcpServerObject, newAnomalies, nextRun, nodesToAssets, normalizeId, normalizeTenant, orgKeyPath, osUser, parseComposeDeps, parseConfig, parseConnectionString, parseCostCsv, parseCron, parseEstablished, parseNginxUpstreams, parseNlQuery, parseScanHint, pixelToHex, planInstall, portsScanner, previewShare, pseudonymize, pseudonymizeFragment, pseudonymizeString, pushDeltas, readConfigFile, redactConnectionString, redactSecrets, redactValue, renderDiff, resolveEffectiveLevel, resolveNlQuery, resolveSharingLevel, revalidateAnonymized, reversalKey, reversePseudonym, rotateOrgKey, runDiscovery, runDrift, runHttp, runLocalDiscovery, runOnce, runStdio, runSyncClassify, safeEnv, safeJson, safetyHook, sanitizeUntrusted, sanitizeValue, scoreTopology, securityRelevantChange, serializeConfig, serviceConfigScanner, setVerbose, shadeVariant, shapeToJsonSchema, shareHash, splitSegments, stableStringify, stripSensitive, validateScanner, vscodeDeeplink };
3872
+ export { ANOMALY_KINDS, ANOMALY_SEVERITIES, type AgentProvider, type AgentRunContext, type AgentTool, type Anomaly, type AnomalyConfig, type AnomalyKind, type AnomalySeverity, type AnomalyThresholds, type AnonViolation, type AnonymizationLevel, type ApiServerOptions, type AskUserFn, type BindGuardOptions, CLIENTS, CONFIDENCE, COST_PERIODS, type CartographyConfig, CartographyDB, type CartographyMapData, type CentralDbConfig, CentralDbConfigSchema, type ClassifiedItem, type ClassifyInput, type ClassifyResult, type ClientSpec, type Cluster, ClusterSchema, type ComplianceInput, type ComplianceReport, ComplianceReportSchema, type ComplianceRule, ComplianceRuleSchema, type Condition, ConditionSchema, ConfigError, type ConfigFile, ConfigFileSchema, type ConfigFormat, type Connection, ConnectionSchema, type Contributor, type ControlResult, ControlResultSchema, type CostEntry, CostEntrySchema, type CostPeriod, type CostRecord, type CostSource, type CreateMcpServerOptions, type CronFields, CsvCostSource, type CsvCostSourceOptions, DEFAULT_ANOMALY_THRESHOLDS, DEFAULT_FAST_MODEL, DEFAULT_LEAD_MODEL, DEFAULT_SERVER_NAME, DEFAULT_TENANT, DOMAIN_COLORS, DOMAIN_PALETTE, DRIFT_FIELDS, type DataAsset, DataAssetSchema, type DependencyQuery, type DiscoveryEdge, type DiscoveryEvent, type DiscoveryFn, type DiscoveryNode, type DriftAlert, type DriftAlertItem, type DriftConfig, DriftConfigSchema, type DriftField, type DriftItemKind, type DriftRunRow, type DriftSink, type DriftSinkConfig, EDGE_RELATIONSHIPS, type EdgeRelationship, type EdgeRow, EdgeSchema, type EmbeddingProvider, type EnrichResult, type EntryOptions, type EstablishedConn, type EvidenceKind, type FetchLike, type FragmentKind, type GraphSummary, type HealthResult, type HttpOptions, INGEST_SCHEMA_VERSION, type IngestEnvelope, IngestEnvelopeSchema, type IngestHandler, type IngestOptions, type IngestResponse, type IngestResult, type InstallPlan, InvalidTenantError, type JiraIssue, type JiraOptions, JiraSink, type JiraSinkOptions, LOOPBACK_HOSTS, type LocalDiscoveryOptions, type LocalDiscoveryResult, type LogEntry, type LogLevel, MCP_BIN, type MatchStrategy, NODE_TYPES, NODE_TYPE_GROUPS, type NlIntent, type NlQueryOptions, type NlQueryResult, type NlRelation, type NodeAttribution, type NodeChange, type NodeIdentity, type NodeQuery, type NodeRow, NodeSchema, type NodeType, type NodesResult, NotFoundError, OUTPUT_FORMATS, type OrgKeyOptions, type OrgSummary, type OsKind, type OutputFormat, PACKAGE_NAME, PAGERDUTY_ENQUEUE_URL, PENDING_STATUSES, PERSONAL, PORT_MAP, PRIVATE_IP, PUSH_SCHEMA_VERSION, type PagerDutyEvent, PagerDutySink, type PagerDutySinkOptions, type ParsedApiArgs, type PendingShareRow, type PendingStatus, type PlanOptions, type PolicyResult, type PostJsonOptions, type ProviderFactory, type ProviderName, ProviderRegistry, type PushItem, type PushOptions, type PushResult, type QueryBackend, RELATION_TO_DIRECTION, type ResolveContext, type RuleCheck, RuleCheckSchema, type RuleScope, type Ruleset, RulesetSchema, type RunDriftOptions, SCAN_ARG_PATTERNS, SDL, SECURITY_METADATA_KEYS, SEVERITIES, SEVERITY_WEIGHT, SHARING_LEVELS, type ScanArgKind, type ScanContext, type ScanHintParams, type ScanResult, type Scanner, type ScannerPlugin, type ScannerPluginApi, ScannerRegistry, ScannerShape, type ScheduleConfig, ScheduleConfigSchema, type ScheduledRunResult, type Scope, type SearchFn, type SemanticSearchOptions, type ServerEntry, type SessionRow, type Severity, type SharePreview, type SharePreviewEntry, type SharingLevel, SharingLevelSchema, type SharingPolicy, type ShellKind, type SlackMessage, SlackSink, SqliteQueryBackend, SqliteStoreBackend, type StartApiOptions, StdoutSink, type StoreBackend, type SyncClassifyOptions, type SyncClassifyResult, TENANT_HEADER, type TenantContext, type TenantOptions, type ToolResult, type TopologyDelta, type TopologyDiff, type TopologyInput, type TraversalResult, VectorStore, WebhookSink, type WebhookSinkOptions, applyInstall, applySharingLevel, assertReadOnly, assertSafeBind, assertSafeScanArg, assignColors, bearerToken, bookmarksScanner, buildCartographyToolHandlers, buildMapData, buildOpenApiDocument, buildReport, buildSinks, centralDbFromEnv, checkBearer, checkPrerequisites, checkReadOnly, clampText, classify, classifyDrift, cleanupTempFiles, cloudAwsScanner, cloudAzureScanner, cloudGcpScanner, codeAddMcpCommand, computeCentroid, computeClusterBounds, computeIdentity, connectionsScanner, contentHash, createBashTool, createCartographyTools, createClaudeProvider, createDefaultRegistry, createHashEmbedder, createIngestHandler, createLocalEmbedder, createMcpServer, createOllamaProvider, createOpenAIProvider, createScanRunner, createSemanticSearch, createSqliteQueryBackend, currentOs, cursorDeeplink, databasesScanner, deepMerge, defaultAllowedHosts, defaultConfig, defaultContext, defaultProviderRegistry, defaultRegistry, defaultServerEntry, definePlugin, deriveSessionName, detectAnomalies, detectOrphans, detectShadowIt, diffTopology, edgesToConnections, enrichCosts, evaluateCheck, evaluateRule, evidenceLine, executeGraphql, executeNlQuery, exportAll, exportBackstageYAML, exportComplianceReport, exportCostCSV, exportCostSummary, exportDiscoveryApp, exportJGF, exportJSON, extractListeningPorts, filterBySeverity, findAnonViolations, formatComplianceText, formatJira, formatPagerDuty, formatSlack, generateDependencyMermaid, generateDiffMermaid, generateTopologyMermaid, getClient, getRuleset, globalId, groupByDomain, handleGraphqlGet, hexCorners, hexDistance, hexNeighbors, hexRing, hexSpiral, hexToPixel, hmacKey, hostname, ingestEnvelope, installedAppsScanner, isLoopbackHost, isPersonalHost, isReadOnlyCommand, isRemembered, isSecureWebhookUrl, k8sScanner, keyMetaOf, layoutClusters, listClients, listRulesets, loadConfig, loadOrgKey, loadPlugins, loadRuleset, localDiscoveryFn, log, logDebug, logError, logInfo, logWarn, machineId, maxSeverity, mcpServerObject, newAnomalies, nextRun, nodesToAssets, normalizeId, normalizeTenant, orgKeyPath, osUser, parseApiArgs, parseComposeDeps, parseConfig, parseConnectionString, parseCostCsv, parseCron, parseEstablished, parseNginxUpstreams, parseNlQuery, parseScanHint, pixelToHex, planInstall, portsScanner, postJson, previewShare, pseudonymize, pseudonymizeFragment, pseudonymizeString, pushDeltas, readConfigFile, redactConnectionString, redactSecrets, redactValue, renderDiff, resolveEffectiveLevel, resolveNlQuery, resolveSharingLevel, resolveTenant, revalidateAnonymized, reversalKey, reversePseudonym, rotateOrgKey, runApi, runDiscovery, runDrift, runHttp, runLocalDiscovery, runOnce, runStdio, runSyncClassify, safeEnv, safeJson, safetyHook, sanitizeUntrusted, sanitizeValue, scoreTopology, securityRelevantChange, serializeConfig, serviceConfigScanner, setVerbose, shadeVariant, shapeToJsonSchema, shareHash, splitSegments, stableStringify, startApi, stripSensitive, timingSafeEqual, validateScanner, vscodeDeeplink, zodToJsonSchema };