@datasynx/agentic-ai-cartography 1.1.1 → 2.2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/db.ts","../src/types.ts","../src/tools.ts","../src/sanitize.ts","../src/bookmarks.ts","../src/platform.ts","../src/allowlist.ts","../src/logger.ts","../src/scanners/cloud-util.ts","../src/scanners/cloud-aws.ts","../src/scanners/cloud-gcp.ts","../src/scanners/cloud-azure.ts","../src/scanners/k8s.ts","../src/scanners/databases.ts","../src/compliance/types.ts","../src/compliance/engine.ts","../src/diff.ts","../src/anomaly.ts","../src/store/sqlite.ts","../src/central/merge.ts","../src/anonymize.ts","../src/orgkey.ts","../src/central/anonymization.ts","../src/central/ingest.ts","../src/central/server.ts","../src/scanners/bookmarks.ts","../src/sharing.ts","../src/mcp/server.ts","../src/sinks/stdout.ts","../src/sinks/webhook.ts","../src/sinks/index.ts","../src/drift.ts","../src/compliance/rulesets/baseline.ts","../src/compliance/rulesets/cis.ts","../src/compliance/rulesets/soc2.ts","../src/compliance/rulesets/iso27001.ts","../src/compliance/rulesets/registry.ts","../src/nlq/resolve.ts","../src/mcp/transports.ts","../src/semantic/hash.ts","../src/semantic/embeddings.ts","../src/semantic/store.ts","../src/semantic/search.ts","../src/scanners/spi.ts","../src/scanners/types.ts","../src/scanners/installed-apps.ts","../src/scanners/ports.ts","../src/scanners/confidence.ts","../src/scanners/connections.ts","../src/scanners/service-config.ts","../src/scanners/registry.ts","../src/scanners/loader.ts","../src/discovery/local.ts","../src/installer/format.ts","../src/installer/merge.ts","../src/installer/shapes.ts","../src/installer/entry.ts","../src/installer/install.ts","../src/installer/registry.ts","../src/installer/deeplinks.ts","../src/safety.ts","../src/providers/types.ts","../src/audit.ts","../src/providers/claude.ts","../src/providers/shell.ts","../src/providers/zod-schema.ts","../src/providers/audit.ts","../src/providers/loop.ts","../src/providers/openai.ts","../src/providers/ollama.ts","../src/providers/registry.ts","../src/agent.ts","../src/cost.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts","../src/compliance/report.ts","../src/config.ts","../src/schedule.ts","../src/sync/hash.ts","../src/sync/classify.ts","../src/sync/push.ts","../src/sync/index.ts","../src/preflight.ts"],"sourcesContent":["export { CartographyDB, deriveSessionName, DEFAULT_TENANT, normalizeTenant, normalizeId, contentHash, globalId, keyMetaOf } from './db.js';\nexport type { GraphSummary, TraversalResult, OrgSummary } from './db.js';\n// Central collector (2.12): StoreBackend seam + server-mode ingest\nexport { SqliteStoreBackend } from './store/sqlite.js';\nexport type { StoreBackend, NodeIdentity } from './store/backend.js';\nexport { computeIdentity } from './central/merge.js';\nexport { findAnonViolations, revalidateAnonymized } from './central/anonymization.js';\nexport type { AnonymizationLevel, AnonViolation } from './central/anonymization.js';\nexport { IngestEnvelopeSchema, ingestEnvelope, INGEST_SCHEMA_VERSION } from './central/ingest.js';\nexport type { IngestEnvelope, IngestResult, IngestOptions } from './central/ingest.js';\nexport { createIngestHandler } from './central/server.js';\nexport type { IngestHandler, IngestResponse } from './central/server.js';\n// Consent policy + anonymization (2.10)\nexport { SHARING_LEVELS, SharingLevelSchema } from './types.js';\nexport { loadOrgKey, rotateOrgKey, orgKeyPath, hmacKey, reversalKey } from './orgkey.js';\nexport type { OrgKeyOptions } from './orgkey.js';\nexport { pseudonymize, pseudonymizeString, pseudonymizeFragment, reversePseudonym, PRIVATE_IP } from './anonymize.js';\nexport type { FragmentKind } from './anonymize.js';\nexport {\n resolveSharingLevel, resolveEffectiveLevel, applySharingLevel, previewShare, isRemembered,\n} from './sharing.js';\nexport type { SharePreview, SharePreviewEntry } from './sharing.js';\nexport { isPersonalHost, PERSONAL } from './scanners/bookmarks.js';\n// MCP server — the headline interface\nexport { createMcpServer, runStdio, runHttp } from './mcp/index.js';\nexport type { CreateMcpServerOptions, SearchFn, DiscoveryFn, HttpOptions } from './mcp/index.js';\n// Scanner plugin system + deterministic local discovery\nexport {\n ScannerRegistry, defaultRegistry, bookmarksScanner, installedAppsScanner, portsScanner, extractListeningPorts, PORT_MAP,\n cloudAwsScanner, cloudGcpScanner, cloudAzureScanner, k8sScanner, databasesScanner,\n connectionsScanner, parseEstablished, serviceConfigScanner,\n parseNginxUpstreams, parseComposeDeps, parseConnectionString, redactConnectionString,\n safeJson, parseScanHint, buildReport,\n definePlugin, validateScanner, ScannerShape,\n} from './scanners/registry.js';\nexport type { Scanner, ScanContext, ScanResult, ScannerPlugin, ScannerPluginApi, ScanHintParams } from './scanners/registry.js';\nexport { CONFIDENCE, evidenceLine } from './scanners/confidence.js';\nexport type { EvidenceKind } from './scanners/confidence.js';\nexport type { EstablishedConn } from './scanners/connections.js';\nexport { loadPlugins } from './scanners/loader.js';\nexport { runLocalDiscovery, localDiscoveryFn } from './discovery/local.js';\nexport type { LocalDiscoveryOptions, LocalDiscoveryResult } from './discovery/local.js';\n// Semantic search\nexport { createSemanticSearch, createLocalEmbedder, createHashEmbedder, VectorStore } from './semantic/search.js';\nexport type { EmbeddingProvider, SemanticSearchOptions } from './semantic/search.js';\n// Install harness — register the MCP server into any host's native config\nexport {\n planInstall, applyInstall, renderDiff, defaultContext, currentOs,\n parseConfig, serializeConfig, deepMerge, mcpServerObject,\n defaultServerEntry, DEFAULT_SERVER_NAME, PACKAGE_NAME, MCP_BIN,\n CLIENTS, getClient, listClients,\n cursorDeeplink, vscodeDeeplink, codeAddMcpCommand,\n} from './installer/index.js';\nexport type {\n ClientSpec, ConfigFormat, OsKind, ResolveContext, Scope, ServerEntry,\n InstallPlan, PlanOptions, EntryOptions,\n} from './installer/index.js';\nexport { createCartographyTools, stripSensitive, createScanRunner, assertSafeScanArg, redactSecrets, redactValue, clampText, SCAN_ARG_PATTERNS } from './tools.js';\nexport type { ScanArgKind } from './tools.js';\nexport { safetyHook } from './safety.js';\nexport { checkReadOnly, isReadOnlyCommand, assertReadOnly, splitSegments } from './allowlist.js';\nexport type { PolicyResult, ShellKind } from './allowlist.js';\nexport { runDiscovery } from './agent.js';\nexport type { DiscoveryEvent, AskUserFn } from './agent.js';\n// Multi-provider agent layer (AgentProvider seam)\nexport { ProviderRegistry } from './providers/types.js';\nexport type { AgentProvider, AgentRunContext, ProviderFactory, ProviderName } from './providers/types.js';\nexport { defaultProviderRegistry, createDefaultRegistry } from './providers/registry.js';\nexport { createClaudeProvider } from './providers/claude.js';\nexport { createOpenAIProvider } from './providers/openai.js';\nexport { createOllamaProvider } from './providers/ollama.js';\nexport { buildCartographyToolHandlers } from './tools.js';\nexport type { AgentTool, ToolResult } from './tools.js';\nexport { shapeToJsonSchema } from './providers/zod-schema.js';\nexport { createBashTool } from './providers/shell.js';\n// Topology diffing / drift detection\nexport { diffTopology, stableStringify } from './diff.js';\nexport type { TopologyInput, TopologyDelta } from './diff.js';\nexport { classifyDrift, maxSeverity, securityRelevantChange, runDrift, filterBySeverity } from './drift.js';\nexport type { RunDriftOptions } from './drift.js';\nexport { detectAnomalies, detectOrphans, detectShadowIt, newAnomalies, DEFAULT_ANOMALY_THRESHOLDS } from './anomaly.js';\nexport { parseNlQuery, executeNlQuery, resolveNlQuery, RELATION_TO_DIRECTION } from './nlq/index.js';\nexport type { NlRelation, NlIntent, NlQueryResult, NlQueryOptions } from './nlq/index.js';\nexport { enrichCosts, parseCostCsv, CsvCostSource } from './cost.js';\nexport type { CostSource, CostRecord, EnrichResult, MatchStrategy, CsvCostSourceOptions } from './cost.js';\nexport { exportCostCSV, exportCostSummary } from './exporter.js';\nexport type { NodeAttribution } from './db.js';\nexport { scoreTopology, evaluateRule, evaluateCheck, loadRuleset } from './compliance/engine.js';\nexport type { ComplianceInput } from './compliance/engine.js';\nexport { getRuleset, listRulesets } from './compliance/rulesets/registry.js';\nexport { exportComplianceReport } from './exporter.js';\nexport { formatComplianceText } from './compliance/report.js';\nexport {\n ComplianceRuleSchema, RulesetSchema, ComplianceReportSchema, ControlResultSchema,\n RuleCheckSchema, ConditionSchema, SEVERITY_WEIGHT,\n} from './compliance/types.js';\nexport type {\n ComplianceRule, Ruleset, ComplianceReport, ControlResult, Severity, RuleCheck, Condition, RuleScope,\n} from './compliance/types.js';\nexport { DriftConfigSchema } from './types.js';\nexport { buildSinks, StdoutSink, WebhookSink } from './sinks/index.js';\nexport type { DriftSink, WebhookSinkOptions } from './sinks/index.js';\n// Config-file layer + scheduled discovery (2.5)\nexport { loadConfig, readConfigFile, ConfigError } from './config.js';\nexport { parseCron, nextRun, runOnce } from './schedule.js';\nexport type { CronFields, ScheduledRunResult } from './schedule.js';\n// Central-DB pending-review queue + consent-gated sync push (2.11)\nexport { centralDbFromEnv } from './types.js';\nexport { runSyncClassify, classify, shareHash, pushDeltas } from './sync/index.js';\nexport { PUSH_SCHEMA_VERSION } from './sync/push.js';\nexport type { SyncClassifyResult, SyncClassifyOptions } from './sync/index.js';\nexport type { ClassifiedItem, ClassifyResult, ClassifyInput } from './sync/classify.js';\nexport type { PushItem, PushResult, PushOptions } from './sync/push.js';\n// Untrusted-text sanitization (invisible/control-char stripping)\nexport { sanitizeUntrusted, sanitizeValue } from './sanitize.js';\nexport {\n exportAll,\n exportJSON,\n exportJGF,\n exportBackstageYAML,\n exportDiscoveryApp,\n generateTopologyMermaid,\n generateDependencyMermaid,\n generateDiffMermaid,\n} from './exporter.js';\nexport {\n hexToPixel, pixelToHex, hexCorners, hexNeighbors,\n hexDistance, hexRing, hexSpiral,\n} from './hex.js';\nexport {\n groupByDomain, layoutClusters, assignColors,\n computeCentroid, computeClusterBounds, shadeVariant,\n} from './cluster.js';\nexport { nodesToAssets, edgesToConnections, buildMapData } from './mapper.js';\nexport { defaultConfig } from './types.js';\nexport { checkPrerequisites } from './preflight.js';\nexport { safeEnv, hostname, osUser, machineId } from './platform.js';\nexport { cleanupTempFiles } from './bookmarks.js';\nexport { log, logInfo, logError, logWarn, logDebug, setVerbose } from './logger.js';\nexport type { LogLevel, LogEntry } from './logger.js';\nexport type * from './types.js';\n","import Database from 'better-sqlite3';\nimport { mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { z } from 'zod';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS, NODE_TYPE_GROUPS, SharingLevelSchema, CostEntrySchema } from './types.js';\nimport type {\n CartographyConfig, DiscoveryNode, DiscoveryEdge,\n NodeRow, EdgeRow, SessionRow, Connection, Contributor, TopologyDiff, DriftRunRow,\n SharingLevel, SharingPolicy, PendingShareRow, PendingStatus, CostEntry,\n} from './types.js';\n\nimport { scoreTopology } from './compliance/engine.js';\nimport type { Ruleset, ComplianceReport } from './compliance/types.js';\n\n/** Attribution applied by an enrichment pass (3.3). `null` clears the field; `undefined` leaves it unchanged. */\nexport interface NodeAttribution {\n owner?: string | null;\n cost?: CostEntry | null;\n}\nimport { diffTopology } from './diff.js';\nimport type { TopologyDelta } from './diff.js';\nimport { detectAnomalies, newAnomalies, DEFAULT_ANOMALY_THRESHOLDS } from './anomaly.js';\nimport type { Anomaly, AnomalyThresholds, AnomalyConfig } from './types.js';\nimport { sanitizeUntrusted, sanitizeValue } from './sanitize.js';\nimport { hostname, osUser, machineId } from './platform.js';\n\n/** Default tenant for single-user / pre-migration installs. */\nexport const DEFAULT_TENANT = 'local';\n\n/**\n * Normalize an untrusted tenant id: strip invisible/control characters, trim,\n * cap length, and enforce a conservative key charset. Falls back to DEFAULT_TENANT\n * when the input is missing or invalid. The tenant is a data-scoping partition key\n * (not an auth boundary — RBAC is Phase 4), so the rule is shared by the DB and MCP\n * layers via this one helper.\n */\nexport function normalizeTenant(raw?: string | null): string {\n if (raw == null) return DEFAULT_TENANT;\n const cleaned = sanitizeUntrusted(String(raw)).trim().slice(0, 128);\n return /^[\\w.@:+-]{1,128}$/.test(cleaned) ? cleaned : DEFAULT_TENANT;\n}\n\n// ── Global node identity (2.9) ───────────────────────────────────────────────\n\n/** Default ports stripped during id normalization. */\nconst DEFAULT_PORTS = /:(80|443)$/;\n\n/**\n * Deterministic, pure normalization of a node id so the same logical resource\n * yields the same key across machines: trim, lowercase, collapse internal\n * whitespace, strip a single trailing default port (:80/:443).\n */\nexport function normalizeId(id: string): string {\n return id.trim().toLowerCase().replace(/\\s+/g, ' ').replace(DEFAULT_PORTS, '');\n}\n\n/** Stable subset of metadata that feeds the content hash. */\nconst KEY_META_KEYS = ['host', 'port', 'path', 'url'] as const;\n\n/**\n * Extract only the stable key-meta subset (`host`/`port`/`path`/`url`) from a\n * metadata blob (pure). Never the whole blob, which carries machine-local noise\n * such as timestamps and absolute paths.\n */\nexport function keyMetaOf(metadata: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const k of KEY_META_KEYS) {\n if (metadata[k] != null) out[k] = metadata[k];\n }\n return out;\n}\n\n/**\n * Secondary dedup key: sha256 over type + normalized name + sorted key-meta.\n * Catches `id` drift between machines for the same logical resource. Truncated\n * to 32 hex chars (128 bits) — ample for collision resistance at org scale.\n */\nexport function contentHash(type: string, name: string, keyMeta: Record<string, unknown>): string {\n const ordered = Object.keys(keyMeta).sort().map((k) => `${k}=${String(keyMeta[k])}`).join('|');\n const payload = `${type}␞${name.trim().toLowerCase()}␞${ordered}`;\n return createHash('sha256').update(payload).digest('hex').slice(0, 32);\n}\n\n/**\n * Human-readable primary identity: org-scoped normalized id (`{org}:{normalizedId}`).\n * `org` is the session's normalized tenant (2.8's partition) — the `'_'` sentinel\n * isolates org-less sessions into their own namespace so two organizations never\n * collapse onto one logical node even with identical infra.\n */\nexport function globalId(organization: string | undefined, id: string): string {\n const org = organization && organization.trim() ? organization.trim() : '_';\n return `${org}:${normalizeId(id)}`;\n}\n\n/** Parse a JSON column, falling back to `fallback` if the stored value is corrupt. */\nfunction safeJsonParse<T>(raw: string, fallback: T): T {\n try {\n return JSON.parse(raw) as T;\n } catch {\n return fallback;\n }\n}\n\n// ── Row validation schemas ──────────────────────────────────────────────────\n\nconst SessionRowSchema = z.object({\n id: z.string(),\n mode: z.literal('discover'),\n started_at: z.string(),\n completed_at: z.string().nullable().optional(),\n config: z.string(),\n name: z.string().nullable().optional(),\n tenant: z.string().default(DEFAULT_TENANT),\n hostname: z.string().nullable().optional(),\n user: z.string().nullable().optional(),\n machine_id: z.string().nullable().optional(),\n organization: z.string().nullable().optional(),\n last_scanned_at: z.string().nullable().optional(),\n});\n\nconst NodeRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discovered_via: z.string().nullable().optional(),\n discovered_at: z.string(),\n path_id: z.string().nullable().optional(),\n depth: z.number().default(0),\n confidence: z.number().default(0.5),\n metadata: z.string().default('{}'),\n tags: z.string().default('[]'),\n domain: z.string().nullable().optional(),\n sub_domain: z.string().nullable().optional(),\n quality_score: z.number().nullable().optional(),\n owner: z.string().nullable().optional(),\n cost: z.string().nullable().optional(),\n global_id: z.string().nullable().optional(),\n content_hash: z.string().nullable().optional(),\n});\n\n/** Row shape of the `node_contributors` child table (2.9). */\nconst ContributorRowSchema = z.object({\n global_id: z.string(),\n machine_id: z.string(),\n hostname: z.string(),\n user: z.string(),\n organization: z.string().nullable().optional(),\n at: z.string(),\n confidence: z.number().default(0.5),\n});\n\n/** Row shape of the `drift_runs` table (2.5 scheduled discovery). */\nconst DriftRunRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n base_session_id: z.string().nullable().optional(),\n ran_at: z.string(),\n nodes_added: z.number(),\n nodes_removed: z.number(),\n nodes_changed: z.number(),\n edges_added: z.number(),\n edges_removed: z.number(),\n delta: z.string(),\n});\n\nconst EdgeRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n source_id: z.string(),\n target_id: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string().nullable().optional(),\n confidence: z.number().default(0.5),\n discovered_at: z.string(),\n});\n\n/** Row shape of the `pending_shares` review queue (2.11). */\nconst PendingShareRowSchema = z.object({\n content_hash: z.string(),\n session_id: z.string(),\n node_id: z.string().nullable().optional(),\n kind: z.enum(['node', 'edge']),\n payload: z.string(),\n status: z.enum(['pending', 'approved', 'shared', 'withheld']),\n decided_by: z.enum(['user', 'rule']).nullable().optional(),\n created_at: z.string(),\n decided_at: z.string().nullable().optional(),\n shared_at: z.string().nullable().optional(),\n});\n\nconst EventRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n task_id: z.string().nullable().optional(),\n timestamp: z.string(),\n event_type: z.string(),\n process: z.string(),\n pid: z.number(),\n target: z.string().nullable().optional(),\n target_type: z.string().nullable().optional(),\n port: z.number().nullable().optional(),\n duration_ms: z.number().nullable().optional(),\n command: z.string().nullable().optional(),\n result_bytes: z.number().nullable().optional(),\n});\n\nconst TaskRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n description: z.string().nullable().optional(),\n started_at: z.string(),\n completed_at: z.string().nullable().optional(),\n steps: z.string().default('[]'),\n involved_services: z.string().default('[]'),\n status: z.enum(['active', 'completed', 'cancelled']),\n});\n\nconst WorkflowRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n name: z.string().nullable().optional(),\n pattern: z.string(),\n task_ids: z.string().default('[]'),\n occurrences: z.number().default(1),\n first_seen: z.string(),\n last_seen: z.string(),\n avg_duration_ms: z.number().nullable().optional(),\n involved_services: z.string().default('[]'),\n});\n\nconst ConnectionRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n source_asset_id: z.string(),\n target_asset_id: z.string(),\n type: z.string().nullable().optional(),\n created_at: z.string(),\n});\n\nexport interface ConnectionRow extends Connection {\n sessionId: string;\n createdAt: string;\n}\n\n/** Aggregate, low-token index of a topology — used for progressive disclosure. */\nexport interface GraphSummary {\n sessionId: string;\n totals: { nodes: number; edges: number };\n nodesByType: Record<string, number>;\n nodesByDomain: Record<string, number>;\n edgesByRelationship: Record<string, number>;\n topConnected: Array<{ id: string; name: string; type: string; degree: number }>;\n /** Standing structural anomalies (orphans / shadow-IT), computed at read time (3.6). */\n anomalies: Anomaly[];\n /** Distinct machines that contributed to this session's nodes (2.9 attribution). */\n contributors: number;\n /** Cost rolled up by domain, bucketed by (currency, period) — never FX-normalized (3.3). */\n costByDomain: Array<{ domain: string; currency: string; period: string; total: number; nodes: number }>;\n /** Cost rolled up by owner, bucketed by (currency, period) (3.3). */\n costByOwner: Array<{ owner: string; currency: string; period: string; total: number; nodes: number }>;\n /** Nodes carrying a cost vs. total — attribution coverage (3.3). */\n costCoverage: { withCost: number; total: number };\n}\n\n/**\n * Org-wide aggregate index (2.12) — the central-collector analogue of\n * {@link GraphSummary}. Scoped to a single tenant (`org`) rather than one session,\n * so it merges every machine's contribution into one organization-wide topology.\n */\nexport interface OrgSummary {\n org: string;\n totals: { nodes: number; edges: number };\n nodesByType: Record<string, number>;\n nodesByDomain: Record<string, number>;\n edgesByRelationship: Record<string, number>;\n topConnected: Array<{ id: string; name: string; type: string; degree: number }>;\n /** Distinct machines that contributed to this org's nodes (2.9 attribution, org-wide). */\n contributors: number;\n}\n\n/** Map a node type to its semantic group (or 'other' if ungrouped). */\nfunction typeGroup(type: string): string {\n for (const [group, types] of Object.entries(NODE_TYPE_GROUPS)) {\n if ((types as readonly string[]).includes(type)) return group;\n }\n return 'other';\n}\n\n/**\n * Derive a deterministic, human-friendly session label from its graph summary,\n * e.g. `\"infra+data · 42 nodes · 2026-06-11\"`. Pure: same summary + timestamp\n * always yields the same name. No LLM call.\n */\nexport function deriveSessionName(summary: GraphSummary, startedAt: string): string {\n const date = startedAt.slice(0, 10);\n const count = summary.totals.nodes;\n if (count === 0) return `empty · 0 nodes · ${date}`;\n\n const byGroup = new Map<string, number>();\n for (const [type, n] of Object.entries(summary.nodesByType)) {\n const g = typeGroup(type);\n byGroup.set(g, (byGroup.get(g) ?? 0) + n);\n }\n const topGroups = [...byGroup.entries()]\n .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n .slice(0, 2)\n .map(([g]) => g);\n\n const noun = count === 1 ? 'node' : 'nodes';\n return `${topGroups.join('+')} · ${count} ${noun} · ${date}`;\n}\n\n/** Result of a recursive dependency traversal. */\nexport interface TraversalResult {\n root?: NodeRow;\n direction: 'downstream' | 'upstream' | 'both';\n maxDepth: number;\n nodes: Array<NodeRow & { depth: number }>;\n edges: EdgeRow[];\n}\n\n// ── DB Row Types ──\n\nexport interface EventRow {\n id: string;\n sessionId: string;\n taskId?: string;\n timestamp: string;\n eventType: string;\n process: string;\n pid: number;\n target?: string;\n targetType?: string;\n port?: number;\n durationMs?: number;\n command?: string;\n resultBytes?: number;\n}\n\nexport interface TaskRow {\n id: string;\n sessionId: string;\n description?: string;\n startedAt: string;\n completedAt?: string;\n steps: string;\n involvedServices: string;\n status: 'active' | 'completed' | 'cancelled';\n}\n\nexport interface WorkflowRow {\n id: string;\n sessionId: string;\n name?: string;\n pattern: string;\n taskIds: string;\n occurrences: number;\n firstSeen: string;\n lastSeen: string;\n avgDurationMs: number;\n involvedServices: string;\n}\n\nconst SCHEMA = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\nPRAGMA busy_timeout = 5000;\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n mode TEXT NOT NULL CHECK (mode IN ('discover')),\n started_at TEXT NOT NULL,\n completed_at TEXT,\n config TEXT NOT NULL DEFAULT '{}',\n name TEXT,\n tenant TEXT NOT NULL DEFAULT 'local',\n hostname TEXT,\n user TEXT,\n machine_id TEXT,\n organization TEXT,\n last_scanned_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT NOT NULL,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n discovered_via TEXT,\n discovered_at TEXT NOT NULL,\n path_id TEXT,\n depth INTEGER DEFAULT 0,\n confidence REAL DEFAULT 0.5,\n metadata TEXT NOT NULL DEFAULT '{}',\n tags TEXT NOT NULL DEFAULT '[]',\n domain TEXT,\n sub_domain TEXT,\n quality_score REAL,\n owner TEXT,\n cost TEXT,\n tenant TEXT NOT NULL DEFAULT 'local',\n global_id TEXT,\n content_hash TEXT,\n PRIMARY KEY (id, session_id)\n);\n\nCREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL,\n tenant TEXT NOT NULL DEFAULT 'local'\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n relationship TEXT NOT NULL,\n evidence TEXT,\n confidence REAL DEFAULT 0.5,\n discovered_at TEXT NOT NULL,\n tenant TEXT NOT NULL DEFAULT 'local'\n);\n\nCREATE TABLE IF NOT EXISTS activity_events (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n task_id TEXT,\n timestamp TEXT NOT NULL,\n event_type TEXT NOT NULL,\n process TEXT NOT NULL,\n pid INTEGER NOT NULL,\n target TEXT,\n target_type TEXT,\n port INTEGER,\n duration_ms INTEGER,\n command TEXT,\n result_bytes INTEGER,\n tenant TEXT NOT NULL DEFAULT 'local'\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n description TEXT,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n steps TEXT NOT NULL DEFAULT '[]',\n involved_services TEXT NOT NULL DEFAULT '[]',\n status TEXT DEFAULT 'active' CHECK (status IN ('active','completed','cancelled')),\n tenant TEXT NOT NULL DEFAULT 'local'\n);\n\nCREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n name TEXT,\n pattern TEXT NOT NULL,\n task_ids TEXT NOT NULL DEFAULT '[]',\n occurrences INTEGER DEFAULT 1,\n first_seen TEXT NOT NULL,\n last_seen TEXT NOT NULL,\n avg_duration_ms INTEGER,\n involved_services TEXT NOT NULL DEFAULT '[]',\n tenant TEXT NOT NULL DEFAULT 'local'\n);\n\nCREATE TABLE IF NOT EXISTS node_approvals (\n pattern TEXT PRIMARY KEY,\n action TEXT NOT NULL CHECK (action IN ('save','ignore','auto')),\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS sharing_policy (\n pattern TEXT PRIMARY KEY, -- '*' row holds the global default\n level TEXT NOT NULL CHECK (level IN ('none','anonymized','full')),\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS pseudonym_reversal (\n token TEXT PRIMARY KEY,\n ciphertext TEXT NOT NULL, -- base64(iv ‖ tag ‖ AES-256-GCM(plaintext)) under reversalKey(orgKey)\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS pending_shares (\n content_hash TEXT PRIMARY KEY, -- sha256 over the policy-transformed payload (stable dedup key)\n session_id TEXT NOT NULL REFERENCES sessions(id),\n node_id TEXT, -- original node id (NULL for edge rows)\n kind TEXT NOT NULL CHECK (kind IN ('node','edge')),\n payload TEXT NOT NULL, -- JSON of the already-anonymized projection (exactly what would leave)\n status TEXT NOT NULL CHECK (status IN ('pending','approved','shared','withheld')),\n decided_by TEXT CHECK (decided_by IN ('user','rule')),\n created_at TEXT NOT NULL,\n decided_at TEXT,\n shared_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS node_contributors (\n global_id TEXT NOT NULL,\n machine_id TEXT NOT NULL,\n hostname TEXT NOT NULL,\n user TEXT NOT NULL,\n organization TEXT,\n at TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.5,\n PRIMARY KEY (global_id, machine_id)\n);\n\nCREATE TABLE IF NOT EXISTS drift_runs (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n base_session_id TEXT,\n ran_at TEXT NOT NULL,\n nodes_added INTEGER NOT NULL,\n nodes_removed INTEGER NOT NULL,\n nodes_changed INTEGER NOT NULL,\n edges_added INTEGER NOT NULL,\n edges_removed INTEGER NOT NULL,\n delta TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(session_id, type);\nCREATE INDEX IF NOT EXISTS idx_edges_session ON edges(session_id);\nCREATE INDEX IF NOT EXISTS idx_edges_source ON edges(session_id, source_id);\nCREATE INDEX IF NOT EXISTS idx_edges_target ON edges(session_id, target_id);\nCREATE INDEX IF NOT EXISTS idx_events_session ON activity_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_task ON activity_events(task_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_lookup ON connections(session_id, source_asset_id, target_asset_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_tenant ON sessions(tenant);\nCREATE INDEX IF NOT EXISTS idx_nodes_tenant_session ON nodes(tenant, session_id);\nCREATE INDEX IF NOT EXISTS idx_edges_tenant_session ON edges(tenant, session_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_global ON nodes(global_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_content ON nodes(content_hash);\nCREATE INDEX IF NOT EXISTS idx_contrib_global ON node_contributors(global_id);\nCREATE INDEX IF NOT EXISTS idx_drift_runs_session ON drift_runs(session_id);\nCREATE INDEX IF NOT EXISTS idx_drift_runs_ran_at ON drift_runs(ran_at);\nCREATE INDEX IF NOT EXISTS idx_pending_status ON pending_shares(status);\nCREATE INDEX IF NOT EXISTS idx_pending_session ON pending_shares(session_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_tenant_global ON nodes(tenant, global_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_hash);\nCREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);\n`;\n\nexport class CartographyDB {\n private db: Database.Database;\n /** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */\n private readonly anomalyEnabled: boolean;\n private readonly anomalyThresholds: AnomalyThresholds;\n\n constructor(dbPath: string, opts?: { anomaly?: AnomalyConfig }) {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n this.anomalyEnabled = opts?.anomaly?.enabled ?? true;\n this.anomalyThresholds = opts?.anomaly ?? DEFAULT_ANOMALY_THRESHOLDS;\n this.migrate();\n }\n\n private migrate(): void {\n const version = (this.db.pragma('user_version', { simple: true }) as number);\n if (version === 0) {\n this.db.exec(SCHEMA);\n this.db.pragma('user_version = 14');\n return;\n } else if (version === 1) {\n // v1 → v2: add hex map columns to nodes + connections table\n const cols = (this.db.prepare(\"PRAGMA table_info(nodes)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN domain TEXT');\n if (!cols.includes('sub_domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN sub_domain TEXT');\n if (!cols.includes('quality_score')) this.db.exec('ALTER TABLE nodes ADD COLUMN quality_score REAL');\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n CREATE INDEX IF NOT EXISTS idx_connections_lookup ON connections(session_id, source_asset_id, target_asset_id);\n `);\n this.db.pragma('user_version = 3');\n }\n if (version === 2) {\n // v2 → v3: add composite index for connection upsert lookups\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_connections_lookup ON connections(session_id, source_asset_id, target_asset_id)');\n this.db.pragma('user_version = 3');\n }\n // v3 → v4: add graph-traversal indexes (idempotent for any pre-v4 DB)\n const current = this.db.pragma('user_version', { simple: true }) as number;\n if (current < 4) {\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(session_id, type);\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(session_id, source_id);\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(session_id, target_id);\n `);\n this.db.pragma('user_version = 4');\n }\n // v4 → v5: human-friendly session names (idempotent column add)\n const v4 = this.db.pragma('user_version', { simple: true }) as number;\n if (v4 < 5) {\n const cols = (this.db.prepare(\"PRAGMA table_info(sessions)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('name')) this.db.exec('ALTER TABLE sessions ADD COLUMN name TEXT');\n this.db.pragma('user_version = 5');\n }\n // v5 → v6: audit-log columns on activity_events (idempotent column adds)\n const v5 = this.db.pragma('user_version', { simple: true }) as number;\n if (v5 < 6) {\n const cols = (this.db.prepare(\"PRAGMA table_info(activity_events)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('command')) this.db.exec('ALTER TABLE activity_events ADD COLUMN command TEXT');\n if (!cols.includes('result_bytes')) this.db.exec('ALTER TABLE activity_events ADD COLUMN result_bytes INTEGER');\n this.db.pragma('user_version = 6');\n }\n // v6 → v7: tenant partition column on every session-scoped table (idempotent column adds).\n // `ALTER TABLE … ADD COLUMN tenant TEXT NOT NULL DEFAULT 'local'` backfills every existing\n // row with the default, so a v6 single-tenant DB upgrades in place with no data loss.\n const v6 = this.db.pragma('user_version', { simple: true }) as number;\n if (v6 < 7) {\n const tables = ['sessions', 'nodes', 'edges', 'connections', 'activity_events', 'tasks', 'workflows'] as const;\n for (const t of tables) {\n const cols = (this.db.prepare(`PRAGMA table_info(${t})`).all() as Array<{ name: string }>).map((c) => c.name);\n // PRAGMA table_info returns no rows for a non-existent table; skip those\n // (a real v6 catalog always has every table — this guards partial fixtures).\n if (cols.length > 0 && !cols.includes('tenant')) {\n this.db.exec(`ALTER TABLE ${t} ADD COLUMN tenant TEXT NOT NULL DEFAULT '${DEFAULT_TENANT}'`);\n }\n }\n const hasTable = (name: string): boolean =>\n (this.db.prepare(`PRAGMA table_info(${name})`).all() as unknown[]).length > 0;\n if (hasTable('sessions')) this.db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_tenant ON sessions(tenant)');\n if (hasTable('nodes')) this.db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_tenant_session ON nodes(tenant, session_id)');\n if (hasTable('edges')) this.db.exec('CREATE INDEX IF NOT EXISTS idx_edges_tenant_session ON edges(tenant, session_id)');\n this.db.pragma('user_version = 7');\n }\n // v7 → v8: source attribution + global node identity + contributor list (2.9).\n // Additive only: new session/node columns are nullable, `node_contributors` is new,\n // so a v7 (tenant-aware) DB upgrades in place with no data loss. `global_id`/\n // `content_hash` backfill lazily on the next upsertNode (no destructive backfill).\n const v7 = this.db.pragma('user_version', { simple: true }) as number;\n if (v7 < 8) {\n const sc = (this.db.prepare('PRAGMA table_info(sessions)').all() as Array<{ name: string }>).map((c) => c.name);\n if (sc.length > 0) {\n for (const col of ['hostname', 'user', 'machine_id', 'organization']) {\n if (!sc.includes(col)) this.db.exec(`ALTER TABLE sessions ADD COLUMN ${col} TEXT`);\n }\n }\n const nc = (this.db.prepare('PRAGMA table_info(nodes)').all() as Array<{ name: string }>).map((c) => c.name);\n if (nc.length > 0) {\n if (!nc.includes('global_id')) this.db.exec('ALTER TABLE nodes ADD COLUMN global_id TEXT');\n if (!nc.includes('content_hash')) this.db.exec('ALTER TABLE nodes ADD COLUMN content_hash TEXT');\n }\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS node_contributors (\n global_id TEXT NOT NULL,\n machine_id TEXT NOT NULL,\n hostname TEXT NOT NULL,\n user TEXT NOT NULL,\n organization TEXT,\n at TEXT NOT NULL,\n confidence REAL NOT NULL DEFAULT 0.5,\n PRIMARY KEY (global_id, machine_id)\n );\n CREATE INDEX IF NOT EXISTS idx_contrib_global ON node_contributors(global_id);\n `);\n // Node indexes only when `nodes` exists (guards partial fixtures, mirroring 2.8).\n if (nc.length > 0) {\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_nodes_global ON nodes(global_id);\n CREATE INDEX IF NOT EXISTS idx_nodes_content ON nodes(content_hash);\n `);\n }\n this.db.pragma('user_version = 8');\n }\n // v8 → v9: incremental-discovery observability (2.1). Adds nullable\n // `sessions.last_scanned_at` so an in-place rescan records when it last ran.\n // Additive/nullable: a v8 DB upgrades in place with no data loss and old rows\n // keep `last_scanned_at = NULL` until their next rescan.\n const v8 = this.db.pragma('user_version', { simple: true }) as number;\n if (v8 < 9) {\n const sc = (this.db.prepare('PRAGMA table_info(sessions)').all() as Array<{ name: string }>).map((c) => c.name);\n // PRAGMA table_info returns no rows for a non-existent table; skip those\n // (a real v8 catalog always has `sessions` — this guards partial fixtures).\n if (sc.length > 0 && !sc.includes('last_scanned_at')) {\n this.db.exec('ALTER TABLE sessions ADD COLUMN last_scanned_at TEXT');\n }\n this.db.pragma('user_version = 9');\n }\n // v9 → v10: per-run drift persistence for scheduled discovery (2.5). Additive\n // only — a new `drift_runs` table + two indexes; no existing table is altered,\n // so a v9 DB upgrades in place with no data loss. Idempotent via IF NOT EXISTS.\n const v9 = this.db.pragma('user_version', { simple: true }) as number;\n if (v9 < 10) {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS drift_runs (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n base_session_id TEXT,\n ran_at TEXT NOT NULL,\n nodes_added INTEGER NOT NULL,\n nodes_removed INTEGER NOT NULL,\n nodes_changed INTEGER NOT NULL,\n edges_added INTEGER NOT NULL,\n edges_removed INTEGER NOT NULL,\n delta TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_drift_runs_session ON drift_runs(session_id);\n CREATE INDEX IF NOT EXISTS idx_drift_runs_ran_at ON drift_runs(ran_at);\n `);\n this.db.pragma('user_version = 10');\n }\n // v10 → v11: persistent consent policy + admin-reversible anonymization (2.10).\n // Additive only — two new standalone tables (`sharing_policy`, `pseudonym_reversal`),\n // no existing table is altered, so a v10 DB upgrades in place with no data loss.\n // Idempotent via IF NOT EXISTS. The org key lives on disk (`~/.cartography/org-key`),\n // never in the DB; only the AES-256-GCM ciphertext of reversal plaintexts is stored.\n const v10 = this.db.pragma('user_version', { simple: true }) as number;\n if (v10 < 11) {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS sharing_policy (\n pattern TEXT PRIMARY KEY,\n level TEXT NOT NULL CHECK (level IN ('none','anonymized','full')),\n created_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS pseudonym_reversal (\n token TEXT PRIMARY KEY,\n ciphertext TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n `);\n this.db.pragma('user_version = 11');\n }\n // v11 → v12: central-DB pending-review queue (2.11). Additive only — one new\n // standalone `pending_shares` table + two indexes; no existing table is altered,\n // so a v11 DB upgrades in place with no data loss. Idempotent via IF NOT EXISTS.\n // Nothing is ever pushed without an explicit approval/remembered rule, so an\n // unconfigured install never writes to this table.\n const v11 = this.db.pragma('user_version', { simple: true }) as number;\n if (v11 < 12) {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS pending_shares (\n content_hash TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n node_id TEXT,\n kind TEXT NOT NULL CHECK (kind IN ('node','edge')),\n payload TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('pending','approved','shared','withheld')),\n decided_by TEXT CHECK (decided_by IN ('user','rule')),\n created_at TEXT NOT NULL,\n decided_at TEXT,\n shared_at TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_pending_status ON pending_shares(status);\n CREATE INDEX IF NOT EXISTS idx_pending_session ON pending_shares(session_id);\n `);\n this.db.pragma('user_version = 12');\n }\n // v12 → v13: central-collector ingest substrate (2.12). Additive only — three\n // new indexes that make the org-scoped merge lookups (by `(tenant, global_id)`\n // primary and `(tenant, content_hash)` secondary) and the org-wide contributor\n // count O(log n) at organization scale. No existing table is altered and the\n // `org`/`global_id`/`content_hash` columns + `node_contributors` table already\n // exist from 2.8/2.9, so a v12 DB upgrades in place with no data loss.\n // Idempotent via IF NOT EXISTS; the indexes only attach when `nodes` exists\n // (guards partial fixtures, mirroring the 2.8/2.9 blocks above).\n const v12 = this.db.pragma('user_version', { simple: true }) as number;\n if (v12 < 13) {\n const hasNodes = (this.db.prepare('PRAGMA table_info(nodes)').all() as unknown[]).length > 0;\n if (hasNodes) {\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_nodes_tenant_global ON nodes(tenant, global_id);\n CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_hash);\n `);\n }\n const hasContrib = (this.db.prepare('PRAGMA table_info(node_contributors)').all() as unknown[]).length > 0;\n if (hasContrib) {\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id)');\n }\n this.db.pragma('user_version = 13');\n }\n // v13 → v14: cost-attribution columns on nodes (3.3). Additive, idempotent\n // `table_info`-guarded ALTERs + an owner index; old rows read back as\n // owner/cost = NULL → undefined. No data is rewritten.\n const v13 = this.db.pragma('user_version', { simple: true }) as number;\n if (v13 < 14) {\n const hasNodes = (this.db.prepare('PRAGMA table_info(nodes)').all() as Array<{ name: string }>);\n if (hasNodes.length > 0) {\n const cols = hasNodes.map((c) => c.name);\n if (!cols.includes('owner')) this.db.exec('ALTER TABLE nodes ADD COLUMN owner TEXT');\n if (!cols.includes('cost')) this.db.exec('ALTER TABLE nodes ADD COLUMN cost TEXT');\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner)');\n }\n this.db.pragma('user_version = 14');\n }\n }\n\n close(): void {\n this.db.pragma('optimize');\n this.db.close();\n }\n\n /**\n * Advanced: the underlying better-sqlite3 connection. Used by the optional\n * semantic-search layer to load the `sqlite-vec` extension and manage its\n * virtual table. Prefer the typed methods above for everything else.\n */\n rawConnection(): Database.Database {\n return this.db;\n }\n\n // ── Sessions ────────────────────────────\n\n /**\n * Create a discovery session, stamping its tenant from (in precedence order)\n * the explicit `tenantId` arg → `config.organization` → DEFAULT_TENANT. The\n * tenant is normalized once here; every child row written under this session\n * inherits it via {@link tenantOf}.\n */\n createSession(mode: 'discover', config: CartographyConfig, tenantId?: string): string {\n const id = crypto.randomUUID();\n const tenant = normalizeTenant(tenantId ?? config.organization);\n // 2.9 source attribution: stamp where/who ran the discovery. `organization` is\n // the raw provenance value; `tenant` (2.8) is its normalized partition form.\n this.db.prepare(\n `INSERT INTO sessions (id, mode, started_at, config, tenant, hostname, user, machine_id, organization)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`\n ).run(\n id, mode, new Date().toISOString(), JSON.stringify(config), tenant,\n hostname(), osUser(), machineId(), config.organization ?? null,\n );\n return id;\n }\n\n /** The tenant that owns a session (DEFAULT_TENANT if the session is unknown). */\n private tenantOf(sessionId: string): string {\n const r = this.db.prepare('SELECT tenant FROM sessions WHERE id = ?').get(sessionId) as { tenant?: string } | undefined;\n return r?.tenant ?? DEFAULT_TENANT;\n }\n\n endSession(id: string): void {\n this.db.prepare('UPDATE sessions SET completed_at = ? WHERE id = ?')\n .run(new Date().toISOString(), id);\n }\n\n getSession(id: string): SessionRow | undefined {\n const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n /**\n * Resolve the newest session, optionally constrained to a `mode` and/or\n * `tenantId`. Omitting `tenantId` preserves the original (unscoped) behavior;\n * passing one returns only that tenant's sessions, which is how the tenant\n * boundary is enforced at session resolution.\n */\n getLatestSession(mode?: string, tenantId?: string): SessionRow | undefined {\n const clauses: string[] = [];\n const params: unknown[] = [];\n if (mode) { clauses.push('mode = ?'); params.push(mode); }\n if (tenantId !== undefined) { clauses.push('tenant = ?'); params.push(tenantId); }\n const where = clauses.length ? `WHERE ${clauses.join(' AND ')} ` : '';\n const row = this.db.prepare(`SELECT * FROM sessions ${where}ORDER BY rowid DESC LIMIT 1`).get(...params) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getSessions(tenantId?: string): SessionRow[] {\n const rows = tenantId !== undefined\n ? this.db.prepare('SELECT * FROM sessions WHERE tenant = ? ORDER BY rowid DESC').all(tenantId) as Record<string, unknown>[]\n : this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC').all() as Record<string, unknown>[];\n return rows.map(r => this.mapSession(r));\n }\n\n private mapSession(r: Record<string, unknown>): SessionRow {\n const v = SessionRowSchema.parse(r);\n return {\n id: v.id,\n mode: v.mode,\n startedAt: v.started_at,\n completedAt: v.completed_at ?? undefined,\n config: v.config,\n name: v.name ?? undefined,\n tenant: v.tenant,\n hostname: v.hostname ?? undefined,\n user: v.user ?? undefined,\n machineId: v.machine_id ?? undefined,\n organization: v.organization ?? undefined,\n lastScannedAt: v.last_scanned_at ?? undefined,\n };\n }\n\n /** Record that a session was (re-)scanned now (ISO 8601 UTC). */\n touchSession(id: string): void {\n this.db.prepare('UPDATE sessions SET last_scanned_at = ? WHERE id = ?')\n .run(new Date().toISOString(), id);\n }\n\n /** Set (or clear) a session's human-friendly name. */\n setSessionName(id: string, name: string): void {\n this.db.prepare('UPDATE sessions SET name = ? WHERE id = ?').run(name, id);\n }\n\n /**\n * Compare two discovery sessions and report drift (added/removed/changed nodes\n * and added/removed edges). Read-only; no schema changes. Throws if either\n * session id does not exist.\n */\n diffSessions(baseId: string, currentId: string): TopologyDiff {\n const base = this.getSession(baseId);\n if (!base) throw new Error(`Base session not found: ${baseId}`);\n const current = this.getSession(currentId);\n if (!current) throw new Error(`Current session not found: ${currentId}`);\n\n const baseData = { nodes: this.getNodes(baseId), edges: this.getEdges(baseId) };\n const curData = { nodes: this.getNodes(currentId), edges: this.getEdges(currentId) };\n const delta = diffTopology(baseData, curData);\n\n // 3.6: anomalies new in `current` vs `base` — the delta a scheduled sink (3.1)\n // alerts on. Reuses each session's summary degree computation (no extra query).\n const baseAnoms = this.getGraphSummary(baseId).anomalies;\n const curAnoms = this.getGraphSummary(currentId).anomalies;\n\n return {\n base: { sessionId: baseId, startedAt: base.startedAt, nodeCount: baseData.nodes.length, edgeCount: baseData.edges.length },\n current: { sessionId: currentId, startedAt: current.startedAt, nodeCount: curData.nodes.length, edgeCount: curData.edges.length },\n ...delta,\n anomalies: { base: baseAnoms, current: curAnoms, added: newAnomalies(baseAnoms, curAnoms) },\n };\n }\n\n /**\n * Score a session against a compliance ruleset (3.4) — a thin wrapper over the\n * pure `scoreTopology` engine (mirrors `diffSessions`). Throws only when the\n * session id is unknown; the engine never throws on data shape.\n */\n scoreSession(sessionId: string, ruleset: Ruleset, opts?: { now?: string }): ComplianceReport {\n if (!this.getSession(sessionId)) throw new Error(`Session not found: ${sessionId}`);\n return scoreTopology({ nodes: this.getNodes(sessionId), edges: this.getEdges(sessionId) }, ruleset, opts);\n }\n\n // ── Scheduled discovery: prior-session selection + drift runs (2.5) ──────────\n\n /**\n * The most recent session of `mode` (and optionally `tenantId`) other than\n * `excludeId`. Used by scheduled discovery to pick an unambiguous diff base\n * (newest-first by `rowid`), avoiding the `getLatestSession` just-created-session\n * ambiguity. Returns `undefined` when no such prior session exists.\n */\n getPreviousSession(excludeId: string, mode?: string, tenantId?: string): SessionRow | undefined {\n const clauses: string[] = ['id != ?'];\n const params: unknown[] = [excludeId];\n if (mode) { clauses.push('mode = ?'); params.push(mode); }\n if (tenantId !== undefined) { clauses.push('tenant = ?'); params.push(tenantId); }\n const row = this.db\n .prepare(`SELECT * FROM sessions WHERE ${clauses.join(' AND ')} ORDER BY rowid DESC LIMIT 1`)\n .get(...params) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n /**\n * Persist one scheduled-discovery drift run: the summary counts plus the full\n * {@link TopologyDelta} (for audit/replay). Returns the generated row id.\n * `ranAt` is stamped now (ISO 8601 UTC).\n */\n recordDriftRun(sessionId: string, baseSessionId: string | undefined, delta: TopologyDelta): string {\n const id = crypto.randomUUID();\n const s = delta.summary;\n this.db.prepare(`\n INSERT INTO drift_runs\n (id, session_id, base_session_id, ran_at,\n nodes_added, nodes_removed, nodes_changed, edges_added, edges_removed, delta)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, baseSessionId ?? null, new Date().toISOString(),\n s.nodesAdded, s.nodesRemoved, s.nodesChanged, s.edgesAdded, s.edgesRemoved,\n JSON.stringify(delta),\n );\n return id;\n }\n\n /** Recent drift runs, newest-first (`LIMIT`, default 50). */\n getDriftRuns(limit = 50): DriftRunRow[] {\n const rows = this.db\n .prepare('SELECT * FROM drift_runs ORDER BY rowid DESC LIMIT ?')\n .all(Math.max(1, Math.floor(limit))) as Record<string, unknown>[];\n return rows.map((r) => this.mapDriftRun(r));\n }\n\n /** The most recent drift run, or `undefined` when none has been recorded. */\n getLatestDriftRun(): DriftRunRow | undefined {\n const row = this.db\n .prepare('SELECT * FROM drift_runs ORDER BY rowid DESC LIMIT 1')\n .get() as Record<string, unknown> | undefined;\n return row ? this.mapDriftRun(row) : undefined;\n }\n\n private mapDriftRun(r: Record<string, unknown>): DriftRunRow {\n const v = DriftRunRowSchema.parse(r);\n const emptyDelta: TopologyDelta = {\n nodes: { added: [], removed: [], changed: [], unchanged: 0 },\n edges: { added: [], removed: [], unchanged: 0 },\n summary: { nodesAdded: 0, nodesRemoved: 0, nodesChanged: 0, edgesAdded: 0, edgesRemoved: 0 },\n };\n return {\n id: v.id,\n sessionId: v.session_id,\n baseSessionId: v.base_session_id ?? undefined,\n ranAt: v.ran_at,\n summary: {\n nodesAdded: v.nodes_added,\n nodesRemoved: v.nodes_removed,\n nodesChanged: v.nodes_changed,\n edgesAdded: v.edges_added,\n edgesRemoved: v.edges_removed,\n },\n delta: safeJsonParse<TopologyDelta>(v.delta, emptyDelta),\n };\n }\n\n // ── Nodes ───────────────────────────────\n\n upsertNode(sessionId: string, node: DiscoveryNode, depth = 0, attribution?: Contributor): void {\n // Sanitize untrusted free-text before it enters the catalog (and later an LLM\n // context): strip invisible/control characters that could hide prompt injection.\n const tenant = this.tenantOf(sessionId);\n\n // 2.9 global identity. The org-scope is the session's tenant (2.8's normalized\n // organization partition), so two organizations never collapse onto one node.\n const ch = contentHash(node.type, node.name, keyMetaOf(node.metadata ?? {}));\n // Catch id drift first: a different node.id from another machine that hashes to\n // the same content under the same tenant reuses that existing logical identity.\n const drift = this.db.prepare(\n `SELECT global_id FROM nodes\n WHERE content_hash = ? AND COALESCE(global_id, '') LIKE ?\n LIMIT 1`,\n ).get(ch, `${globalId(tenant, '')}%`) as { global_id: string } | undefined;\n const gid = drift?.global_id ?? globalId(tenant, node.id);\n\n this.db.prepare(`\n INSERT OR REPLACE INTO nodes\n (id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,\n domain, sub_domain, quality_score, owner, cost, tenant, global_id, content_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n node.id, sessionId, node.type, sanitizeUntrusted(node.name), node.discoveredVia,\n new Date().toISOString(), depth, node.confidence,\n JSON.stringify(sanitizeValue(node.metadata ?? {})),\n JSON.stringify((node.tags ?? []).map(sanitizeUntrusted)),\n node.domain != null ? sanitizeUntrusted(node.domain) : null,\n node.subDomain != null ? sanitizeUntrusted(node.subDomain) : null,\n node.qualityScore ?? null,\n node.owner != null ? sanitizeUntrusted(node.owner) : null,\n node.cost ? JSON.stringify(CostEntrySchema.parse(node.cost)) : null,\n tenant, gid, ch,\n );\n\n if (attribution) {\n // (global_id, machine_id) conflict → keep highest confidence + latest observation.\n // Free-text attribution is sanitized like name/tags (it may enter an LLM context).\n this.db.prepare(`\n INSERT INTO node_contributors (global_id, machine_id, hostname, user, organization, at, confidence)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(global_id, machine_id) DO UPDATE SET\n confidence = MAX(confidence, excluded.confidence),\n at = excluded.at,\n hostname = excluded.hostname,\n user = excluded.user,\n organization = excluded.organization\n `).run(\n gid, attribution.machineId,\n sanitizeUntrusted(attribution.hostname), sanitizeUntrusted(attribution.user),\n attribution.organization != null ? sanitizeUntrusted(attribution.organization) : null,\n attribution.at, attribution.confidence,\n );\n }\n }\n\n /** All contributors that observed a logical node, ordered by observation time. */\n getContributors(globalId: string): Contributor[] {\n const rows = this.db.prepare(\n 'SELECT * FROM node_contributors WHERE global_id = ? ORDER BY at',\n ).all(globalId) as Record<string, unknown>[];\n return rows.map((r) => {\n const v = ContributorRowSchema.parse(r);\n return {\n machineId: v.machine_id,\n hostname: v.hostname,\n user: v.user,\n organization: v.organization ?? undefined,\n at: v.at,\n confidence: v.confidence,\n };\n });\n }\n\n getNodes(sessionId: string, opts?: { limit?: number; offset?: number }): NodeRow[] {\n let sql = 'SELECT * FROM nodes WHERE session_id = ?';\n if (opts?.limit) {\n sql += ` LIMIT ${opts.limit}`;\n if (opts.offset) sql += ` OFFSET ${opts.offset}`;\n }\n const rows = this.db.prepare(sql).all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n getNodeCount(sessionId: string): number {\n const row = this.db.prepare('SELECT COUNT(*) as cnt FROM nodes WHERE session_id = ?').get(sessionId) as { cnt: number };\n return row.cnt;\n }\n\n private mapNode(r: Record<string, unknown>): NodeRow {\n const v = NodeRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n type: v.type,\n name: v.name,\n discoveredVia: v.discovered_via ?? '',\n discoveredAt: v.discovered_at,\n depth: v.depth,\n confidence: v.confidence,\n metadata: safeJsonParse<Record<string, unknown>>(v.metadata, {}),\n tags: safeJsonParse<string[]>(v.tags, []),\n pathId: v.path_id ?? undefined,\n domain: v.domain ?? undefined,\n subDomain: v.sub_domain ?? undefined,\n qualityScore: v.quality_score ?? undefined,\n owner: v.owner ?? undefined,\n cost: v.cost ? safeJsonParse<CostEntry | undefined>(v.cost, undefined) : undefined,\n globalId: v.global_id ?? undefined,\n contentHash: v.content_hash ?? undefined,\n };\n }\n\n /**\n * Update only the cost/owner of an existing node, without touching any other\n * field (unlike upsertNode's INSERT OR REPLACE) — the idempotent enrichment\n * primitive (3.3). `undefined` leaves a field unchanged; `null` clears it.\n * No-op (returns false) if the node is absent. Cost is re-validated before write.\n */\n enrichNodeAttribution(sessionId: string, nodeId: string, attr: NodeAttribution): boolean {\n const sets: string[] = [];\n const vals: unknown[] = [];\n if (attr.owner !== undefined) {\n sets.push('owner = ?');\n vals.push(attr.owner == null ? null : sanitizeUntrusted(attr.owner));\n }\n if (attr.cost !== undefined) {\n sets.push('cost = ?');\n vals.push(attr.cost == null ? null : JSON.stringify(CostEntrySchema.parse(attr.cost)));\n }\n if (sets.length === 0) return false;\n const info = this.db.prepare(\n `UPDATE nodes SET ${sets.join(', ')} WHERE session_id = ? AND id = ?`,\n ).run(...vals, sessionId, nodeId);\n return info.changes > 0;\n }\n\n deleteNode(sessionId: string, nodeId: string): void {\n this.db.prepare('DELETE FROM nodes WHERE session_id = ? AND id = ?').run(sessionId, nodeId);\n // Remove orphaned edges\n this.db.prepare(\n 'DELETE FROM edges WHERE session_id = ? AND (source_id = ? OR target_id = ?)'\n ).run(sessionId, nodeId, nodeId);\n }\n\n // ── Edges ───────────────────────────────\n\n insertEdge(sessionId: string, edge: DiscoveryEdge): void {\n const id = crypto.randomUUID();\n const tenant = this.tenantOf(sessionId);\n this.db.prepare(`\n INSERT OR IGNORE INTO edges\n (id, session_id, source_id, target_id, relationship, evidence, confidence, discovered_at, tenant)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, edge.sourceId, edge.targetId,\n edge.relationship, sanitizeUntrusted(edge.evidence), edge.confidence,\n new Date().toISOString(), tenant,\n );\n }\n\n /**\n * Delete every edge matching the logical key (source, target, relationship)\n * within a session. `insertEdge` writes a random PK, so logical identity is the\n * only way to prune an edge that disappeared while both endpoints survived.\n */\n deleteEdgeByKey(sessionId: string, sourceId: string, targetId: string, relationship: string): void {\n this.db.prepare(\n 'DELETE FROM edges WHERE session_id = ? AND source_id = ? AND target_id = ? AND relationship = ?'\n ).run(sessionId, sourceId, targetId, relationship);\n }\n\n /**\n * Apply a precomputed {@link TopologyDelta} to one session in a single\n * transaction (2.1 incremental discovery): prune removed nodes (cascading their\n * edges via {@link deleteNode}), upsert added/changed nodes, delete removed edges\n * by logical key, insert added edges, and stamp `last_scanned_at`. Unchanged rows\n * are left untouched (stable `discovered_at`).\n *\n * `attribution` (2.9) is forwarded to every added/changed node's upsert so a\n * rescan keeps/appends the running machine's contributor instead of leaving it\n * out. Unchanged nodes are not re-upserted, so their existing contributors\n * survive the rescan. `NodeRow extends DiscoveryNode`, so the delta rows pass\n * straight into `upsertNode` with no mapper.\n */\n applyTopologyDelta(sessionId: string, delta: TopologyDelta, attribution?: Contributor): void {\n const apply = this.db.transaction(() => {\n for (const n of delta.nodes.removed) this.deleteNode(sessionId, n.id); // cascades orphan edges\n for (const n of delta.nodes.added) {\n this.upsertNode(sessionId, n, n.depth, attribution ? { ...attribution, confidence: n.confidence } : undefined);\n }\n for (const c of delta.nodes.changed) {\n this.upsertNode(sessionId, c.after, c.after.depth, attribution ? { ...attribution, confidence: c.after.confidence } : undefined);\n }\n for (const e of delta.edges.removed) this.deleteEdgeByKey(sessionId, e.sourceId, e.targetId, e.relationship);\n for (const e of delta.edges.added) this.insertEdge(sessionId, e);\n this.touchSession(sessionId);\n });\n apply();\n }\n\n getEdges(sessionId: string, opts?: { limit?: number; offset?: number }): EdgeRow[] {\n let sql = 'SELECT * FROM edges WHERE session_id = ?';\n if (opts?.limit) {\n sql += ` LIMIT ${opts.limit}`;\n if (opts.offset) sql += ` OFFSET ${opts.offset}`;\n }\n const rows = this.db.prepare(sql).all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = EdgeRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n sourceId: v.source_id,\n targetId: v.target_id,\n relationship: v.relationship,\n evidence: v.evidence ?? '',\n confidence: v.confidence,\n discoveredAt: v.discovered_at,\n };\n });\n }\n\n // ── Events ──────────────────────────────\n\n insertEvent(\n sessionId: string,\n event: Pick<EventRow, 'eventType' | 'process' | 'pid' | 'target' | 'targetType' | 'port'>\n & Partial<Pick<EventRow, 'command' | 'resultBytes'>>,\n taskId?: string,\n ): void {\n const id = crypto.randomUUID();\n const tenant = this.tenantOf(sessionId);\n this.db.prepare(`\n INSERT INTO activity_events\n (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, taskId ?? null, new Date().toISOString(),\n event.eventType, event.process, event.pid,\n event.target ?? null, event.targetType ?? null, event.port ?? null,\n event.command ?? null, event.resultBytes ?? null, tenant,\n );\n }\n\n getEvents(sessionId: string, since?: string): EventRow[] {\n const rows = since\n ? this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp').all(sessionId, since) as Record<string, unknown>[]\n : this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = EventRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n taskId: v.task_id ?? undefined,\n timestamp: v.timestamp,\n eventType: v.event_type,\n process: v.process,\n pid: v.pid,\n target: v.target ?? undefined,\n targetType: v.target_type ?? undefined,\n port: v.port ?? undefined,\n durationMs: v.duration_ms ?? undefined,\n command: v.command ?? undefined,\n resultBytes: v.result_bytes ?? undefined,\n };\n });\n }\n\n // ── Tasks ───────────────────────────────\n\n startTask(sessionId: string, description?: string): string {\n const id = crypto.randomUUID();\n const tenant = this.tenantOf(sessionId);\n this.db.prepare(`\n INSERT INTO tasks (id, session_id, description, started_at, steps, involved_services, status, tenant)\n VALUES (?, ?, ?, ?, '[]', '[]', 'active', ?)\n `).run(id, sessionId, description ?? null, new Date().toISOString(), tenant);\n return id;\n }\n\n endCurrentTask(sessionId: string): void {\n this.db.prepare(`\n UPDATE tasks SET status = 'completed', completed_at = ?\n WHERE session_id = ? AND status = 'active'\n `).run(new Date().toISOString(), sessionId);\n }\n\n updateTaskDescription(sessionId: string, description: string): void {\n this.db.prepare(`\n UPDATE tasks SET description = ?\n WHERE session_id = ? AND status = 'active'\n `).run(description, sessionId);\n }\n\n getActiveTask(sessionId: string): TaskRow | undefined {\n const row = this.db.prepare(\n \"SELECT * FROM tasks WHERE session_id = ? AND status = 'active' LIMIT 1\"\n ).get(sessionId) as Record<string, unknown> | undefined;\n return row ? this.mapTask(row) : undefined;\n }\n\n getTasks(sessionId: string): TaskRow[] {\n const rows = this.db.prepare('SELECT * FROM tasks WHERE session_id = ? ORDER BY started_at').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapTask(r));\n }\n\n private mapTask(r: Record<string, unknown>): TaskRow {\n const v = TaskRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n description: v.description ?? undefined,\n startedAt: v.started_at,\n completedAt: v.completed_at ?? undefined,\n steps: v.steps,\n involvedServices: v.involved_services,\n status: v.status,\n };\n }\n\n // ── Workflows ───────────────────────────\n\n insertWorkflow(sessionId: string, data: Omit<WorkflowRow, 'id'>): void {\n const id = crypto.randomUUID();\n const tenant = this.tenantOf(sessionId);\n this.db.prepare(`\n INSERT INTO workflows\n (id, session_id, name, pattern, task_ids, occurrences,\n first_seen, last_seen, avg_duration_ms, involved_services, tenant)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, data.name ?? null, data.pattern,\n data.taskIds, data.occurrences,\n data.firstSeen, data.lastSeen, data.avgDurationMs,\n data.involvedServices, tenant,\n );\n }\n\n getWorkflows(sessionId: string): WorkflowRow[] {\n const rows = this.db.prepare('SELECT * FROM workflows WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = WorkflowRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n name: v.name ?? undefined,\n pattern: v.pattern,\n taskIds: v.task_ids,\n occurrences: v.occurrences,\n firstSeen: v.first_seen,\n lastSeen: v.last_seen,\n avgDurationMs: v.avg_duration_ms ?? 0,\n involvedServices: v.involved_services,\n };\n });\n }\n\n // ── Connections (user-created hex map links) ─────────────────────────────\n\n upsertConnection(sessionId: string, conn: Omit<Connection, 'id'>): string {\n // Idempotent: same source+target+type = same connection\n const existing = this.db.prepare(\n 'SELECT id FROM connections WHERE session_id = ? AND source_asset_id = ? AND target_asset_id = ?'\n ).get(sessionId, conn.sourceAssetId, conn.targetAssetId) as { id: string } | undefined;\n if (existing) return existing.id;\n const id = crypto.randomUUID();\n const tenant = this.tenantOf(sessionId);\n this.db.prepare(`\n INSERT INTO connections (id, session_id, source_asset_id, target_asset_id, type, created_at, tenant)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `).run(id, sessionId, conn.sourceAssetId, conn.targetAssetId, conn.type ?? null, new Date().toISOString(), tenant);\n return id;\n }\n\n getConnections(sessionId: string): ConnectionRow[] {\n const rows = this.db.prepare('SELECT * FROM connections WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = ConnectionRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n sourceAssetId: v.source_asset_id,\n targetAssetId: v.target_asset_id,\n type: v.type ?? undefined,\n createdAt: v.created_at,\n };\n });\n }\n\n deleteConnection(sessionId: string, connectionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ? AND id = ?').run(sessionId, connectionId);\n }\n\n // ── Approvals ───────────────────────────\n\n setApproval(pattern: string, action: 'save' | 'ignore' | 'auto'): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO node_approvals (pattern, action, created_at) VALUES (?, ?, ?)\n `).run(pattern, action, new Date().toISOString());\n }\n\n getApproval(pattern: string): string | undefined {\n const row = this.db.prepare('SELECT action FROM node_approvals WHERE pattern = ?').get(pattern) as { action: string } | undefined;\n return row?.action;\n }\n\n // ── Sharing policy (2.10 consent) ───────────────────────────────────────────\n\n /**\n * Set (or replace) the sharing level for a pattern. The `'*'` pattern is the\n * global default; any other pattern is an override (glob over the node id).\n * Validated via {@link SharingLevelSchema} before write; `created_at` is ISO UTC.\n */\n setSharingLevel(pattern: string, level: SharingLevel): void {\n const valid = SharingLevelSchema.parse(level);\n this.db.prepare(\n 'INSERT OR REPLACE INTO sharing_policy (pattern, level, created_at) VALUES (?, ?, ?)',\n ).run(pattern, valid, new Date().toISOString());\n }\n\n /**\n * The full sharing policy: the `'*'` row resolves to `defaultLevel` (`'none'`\n * when absent — the opt-in floor), every other row becomes an override. The\n * glob-precedence resolution itself lives in `src/sharing.ts` so it is unit\n * testable in isolation; this returns the raw policy it consumes.\n */\n getSharingPolicy(): SharingPolicy {\n const rows = this.db.prepare('SELECT pattern, level FROM sharing_policy').all() as Array<{ pattern: string; level: string }>;\n let defaultLevel: SharingLevel = 'none';\n const overrides: { pattern: string; level: SharingLevel }[] = [];\n for (const r of rows) {\n const level = SharingLevelSchema.parse(r.level);\n if (r.pattern === '*') defaultLevel = level;\n else overrides.push({ pattern: r.pattern, level });\n }\n return { defaultLevel, overrides };\n }\n\n /** Remove a pattern override. The global default (`'*'`) cannot be cleared this way. */\n clearSharingOverride(pattern: string): void {\n this.db.prepare(\"DELETE FROM sharing_policy WHERE pattern = ? AND pattern != '*'\").run(pattern);\n }\n\n // ── Pseudonym reversal map (2.10 admin-reversible anonymization) ─────────────\n\n /**\n * Persist the encrypted plaintext behind a pseudonym token. Idempotent: the\n * token is deterministic, so repeated writes `INSERT OR REPLACE` and never grow\n * the table. `ciphertext` is base64(iv ‖ tag ‖ AES-256-GCM(plaintext)).\n */\n saveReversal(token: string, ciphertext: string): void {\n this.db.prepare(\n 'INSERT OR REPLACE INTO pseudonym_reversal (token, ciphertext, created_at) VALUES (?, ?, ?)',\n ).run(token, ciphertext, new Date().toISOString());\n }\n\n /** Read the stored ciphertext for a pseudonym token (admin reversal path). */\n getReversal(token: string): string | undefined {\n const row = this.db.prepare('SELECT ciphertext FROM pseudonym_reversal WHERE token = ?').get(token) as { ciphertext: string } | undefined;\n return row?.ciphertext;\n }\n\n // ── Pending-review share queue (2.11 central-DB sync) ────────────────────────\n\n /**\n * Enqueue one proposed share item. Idempotent via `INSERT OR IGNORE` on the\n * `content_hash` PK: re-classifying the same (transformed) item never duplicates\n * a row nor resets an existing decision. `payload` is the already-policy-\n * transformed projection (the exact bytes a push would send) — never raw node\n * data for `anonymized`/`none` items.\n */\n enqueuePending(item: {\n contentHash: string;\n sessionId: string;\n nodeId?: string;\n kind: 'node' | 'edge';\n payload: unknown;\n status: PendingStatus;\n decidedBy?: 'user' | 'rule';\n }): void {\n const now = new Date().toISOString();\n const decided = item.status === 'pending' ? null : now;\n this.db.prepare(`\n INSERT OR IGNORE INTO pending_shares\n (content_hash, session_id, node_id, kind, payload, status, decided_by, created_at, decided_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n item.contentHash, item.sessionId, item.nodeId ?? null, item.kind,\n JSON.stringify(item.payload), item.status, item.decidedBy ?? null, now, decided,\n );\n }\n\n /** Queued share items, optionally filtered by status and/or session. */\n getPendingShares(filter?: { status?: PendingStatus; sessionId?: string }): PendingShareRow[] {\n const clauses: string[] = [];\n const params: unknown[] = [];\n if (filter?.status) { clauses.push('status = ?'); params.push(filter.status); }\n if (filter?.sessionId) { clauses.push('session_id = ?'); params.push(filter.sessionId); }\n const where = clauses.length ? `WHERE ${clauses.join(' AND ')} ` : '';\n const rows = this.db.prepare(`SELECT * FROM pending_shares ${where}ORDER BY rowid`).all(...params) as Record<string, unknown>[];\n return rows.map((r) => this.mapPendingShare(r));\n }\n\n /** Queue size by status (every status key present, zero-filled). */\n countPendingByStatus(): Record<PendingStatus, number> {\n const out: Record<PendingStatus, number> = { pending: 0, approved: 0, shared: 0, withheld: 0 };\n const rows = this.db.prepare('SELECT status, COUNT(*) c FROM pending_shares GROUP BY status').all() as Array<{ status: string; c: number }>;\n for (const r of rows) {\n if (r.status in out) out[r.status as PendingStatus] = r.c;\n }\n return out;\n }\n\n /** `content_hash` values already pushed (status `shared`) — for re-share suppression. */\n getSharedHashes(): Set<string> {\n const rows = this.db.prepare(\"SELECT content_hash FROM pending_shares WHERE status = 'shared'\").all() as Array<{ content_hash: string }>;\n return new Set(rows.map((r) => r.content_hash));\n }\n\n /**\n * Transition one queued item. Stamps `decided_at` on any non-`pending` status and\n * `shared_at` when moving to `shared`. `decidedBy` records the actor (`'user'` or\n * `'rule'`) for the audit trail.\n */\n setPendingStatus(contentHash: string, status: PendingStatus, decidedBy?: 'user' | 'rule'): void {\n const now = new Date().toISOString();\n const sharedAt = status === 'shared' ? now : null;\n this.db.prepare(`\n UPDATE pending_shares\n SET status = ?, decided_by = COALESCE(?, decided_by), decided_at = ?, shared_at = COALESCE(?, shared_at)\n WHERE content_hash = ?\n `).run(status, decidedBy ?? null, now, sharedAt, contentHash);\n }\n\n /** Approved items cleared to push (FIFO), optionally capped by `limit`. */\n getApprovedShares(limit?: number): PendingShareRow[] {\n let sql = \"SELECT * FROM pending_shares WHERE status = 'approved' ORDER BY rowid\";\n if (limit) sql += ` LIMIT ${Math.max(1, Math.floor(limit))}`;\n const rows = this.db.prepare(sql).all() as Record<string, unknown>[];\n return rows.map((r) => this.mapPendingShare(r));\n }\n\n private mapPendingShare(r: Record<string, unknown>): PendingShareRow {\n const v = PendingShareRowSchema.parse(r);\n return {\n contentHash: v.content_hash,\n sessionId: v.session_id,\n nodeId: v.node_id ?? undefined,\n kind: v.kind,\n payload: safeJsonParse<unknown>(v.payload, null),\n status: v.status,\n decidedBy: v.decided_by ?? undefined,\n createdAt: v.created_at,\n decidedAt: v.decided_at ?? undefined,\n sharedAt: v.shared_at ?? undefined,\n };\n }\n\n // ── Pruning ──────────────────────────────\n\n /**\n * Delete a session and all its associated data (nodes, edges, events, tasks, workflows, connections).\n */\n deleteSession(sessionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM workflows WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM activity_events WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM tasks WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM edges WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM nodes WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);\n }\n\n /**\n * Prune sessions older than the given ISO date string. Returns count of deleted sessions.\n */\n pruneSessions(olderThan: string): number {\n const rows = this.db.prepare(\n 'SELECT id FROM sessions WHERE started_at < ?'\n ).all(olderThan) as { id: string }[];\n for (const row of rows) {\n this.deleteSession(row.id);\n }\n return rows.length;\n }\n\n // ── Graph queries (read-only context layer) ─────────────────────────────────\n\n /** Fetch a single node by id within a session. */\n getNode(sessionId: string, nodeId: string): NodeRow | undefined {\n const row = this.db.prepare('SELECT * FROM nodes WHERE session_id = ? AND id = ?')\n .get(sessionId, nodeId) as Record<string, unknown> | undefined;\n return row ? this.mapNode(row) : undefined;\n }\n\n /** Batch-fetch nodes by id, keyed for O(1) lookup. Chunked to stay under SQLite's bind-variable limit. */\n getNodesByIds(sessionId: string, ids: readonly string[]): Map<string, NodeRow> {\n const out = new Map<string, NodeRow>();\n for (let i = 0; i < ids.length; i += 900) {\n const chunk = ids.slice(i, i + 900);\n const placeholders = chunk.map(() => '?').join(',');\n const rows = this.db.prepare(\n `SELECT * FROM nodes WHERE session_id = ? AND id IN (${placeholders})`,\n ).all(sessionId, ...chunk) as Record<string, unknown>[];\n for (const r of rows) { const n = this.mapNode(r); out.set(n.id, n); }\n }\n return out;\n }\n\n /** Fetch all nodes of one or more types. */\n getNodesByType(sessionId: string, types: readonly string[]): NodeRow[] {\n if (types.length === 0) return [];\n const placeholders = types.map(() => '?').join(',');\n const rows = this.db.prepare(\n `SELECT * FROM nodes WHERE session_id = ? AND type IN (${placeholders})`,\n ).all(sessionId, ...types) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n /**\n * Lexical search over node id, name, domain, sub-domain and tags.\n * Case-insensitive substring match — the deterministic fallback for semantic search.\n */\n searchNodes(sessionId: string, query: string, opts?: { types?: readonly string[]; limit?: number }): NodeRow[] {\n const q = `%${query.trim().toLowerCase()}%`;\n const params: unknown[] = [sessionId, q, q, q, q, q];\n let sql = `\n SELECT * FROM nodes\n WHERE session_id = ?\n AND (\n lower(id) LIKE ? OR lower(name) LIKE ?\n OR lower(COALESCE(domain, '')) LIKE ?\n OR lower(COALESCE(sub_domain, '')) LIKE ?\n OR lower(tags) LIKE ?\n )`;\n if (opts?.types && opts.types.length > 0) {\n sql += ` AND type IN (${opts.types.map(() => '?').join(',')})`;\n params.push(...opts.types);\n }\n sql += ' ORDER BY confidence DESC';\n if (opts?.limit) sql += ` LIMIT ${Math.max(1, Math.floor(opts.limit))}`;\n const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n /**\n * Traverse the dependency graph from a node using a recursive CTE with a\n * path-based cycle guard. `downstream` follows source→target (what the node\n * depends on / points to); `upstream` follows target→source (what depends on it).\n */\n getDependencies(\n sessionId: string,\n nodeId: string,\n opts: { direction?: 'downstream' | 'upstream' | 'both'; maxDepth?: number } = {},\n ): TraversalResult {\n const direction = opts.direction ?? 'downstream';\n const maxDepth = Math.max(1, Math.min(opts.maxDepth ?? 8, 64));\n const root = this.getNode(sessionId, nodeId);\n\n const depthById = new Map<string, number>();\n const collect = (dir: 'downstream' | 'upstream'): void => {\n // SEP = newline; node ids never contain newlines, so the path guard is collision-free.\n const [from, to] = dir === 'downstream' ? ['source_id', 'target_id'] : ['target_id', 'source_id'];\n const sql = `\n WITH RECURSIVE walk(node_id, depth, path) AS (\n SELECT ?, 0, char(10) || ? || char(10)\n UNION ALL\n SELECT e.${to}, w.depth + 1, w.path || e.${to} || char(10)\n FROM edges e JOIN walk w ON e.${from} = w.node_id\n WHERE e.session_id = ?\n AND w.depth < ?\n AND instr(w.path, char(10) || e.${to} || char(10)) = 0\n )\n SELECT node_id, MIN(depth) AS depth FROM walk WHERE node_id != ? GROUP BY node_id`;\n const rows = this.db.prepare(sql).all(nodeId, nodeId, sessionId, maxDepth, nodeId) as Array<{ node_id: string; depth: number }>;\n for (const r of rows) {\n const prev = depthById.get(r.node_id);\n if (prev === undefined || r.depth < prev) depthById.set(r.node_id, r.depth);\n }\n };\n\n if (direction === 'both') { collect('downstream'); collect('upstream'); }\n else collect(direction);\n\n const byId = this.getNodesByIds(sessionId, [...depthById.keys()]);\n const nodes = [...depthById.entries()]\n .map(([id, depth]) => { const n = byId.get(id); return n ? { ...n, depth } : undefined; })\n .filter((n): n is NodeRow & { depth: number } => n !== undefined)\n .sort((a, b) => a.depth - b.depth);\n\n // Edges that lie within the reached subgraph (including the root).\n const reachable = new Set<string>([nodeId, ...depthById.keys()]);\n const edges = this.getEdges(sessionId).filter(e => reachable.has(e.sourceId) && reachable.has(e.targetId));\n\n return { root, direction, maxDepth, nodes, edges };\n }\n\n /** Lightweight aggregate index of the whole topology — the progressive-disclosure summary. */\n getGraphSummary(sessionId: string): GraphSummary {\n const totals = {\n nodes: (this.db.prepare('SELECT COUNT(*) c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c,\n edges: (this.db.prepare('SELECT COUNT(*) c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c,\n };\n const byType: Record<string, number> = {};\n for (const r of this.db.prepare('SELECT type, COUNT(*) c FROM nodes WHERE session_id = ? GROUP BY type').all(sessionId) as Array<{ type: string; c: number }>) {\n byType[r.type] = r.c;\n }\n const byDomain: Record<string, number> = {};\n for (const r of this.db.prepare(\"SELECT COALESCE(domain, '(none)') d, COUNT(*) c FROM nodes WHERE session_id = ? GROUP BY d\").all(sessionId) as Array<{ d: string; c: number }>) {\n byDomain[r.d] = r.c;\n }\n const byRelationship: Record<string, number> = {};\n for (const r of this.db.prepare('SELECT relationship rel, COUNT(*) c FROM edges WHERE session_id = ? GROUP BY rel').all(sessionId) as Array<{ rel: string; c: number }>) {\n byRelationship[r.rel] = r.c;\n }\n // 3.6: degree for ALL nodes in one aggregate (the old top-10 query dropped the\n // LIMIT). The same rows feed `topConnected` and the per-node degree map the\n // anomaly engine consumes — no second scan.\n const degreeRows = this.db.prepare(`\n SELECT n.id, n.name, n.type, n.confidence, COUNT(e.id) AS degree\n FROM nodes n\n LEFT JOIN edges e ON e.session_id = n.session_id AND (e.source_id = n.id OR e.target_id = n.id)\n WHERE n.session_id = ?\n GROUP BY n.id, n.name, n.type\n `).all(sessionId) as Array<{ id: string; name: string; type: string; confidence: number; degree: number }>;\n\n const degree = new Map<string, number>();\n for (const r of degreeRows) degree.set(r.id, r.degree);\n\n // Reproduces the old `degree DESC, confidence DESC` order + a stable id tiebreaker.\n const topConnected = [...degreeRows]\n .sort((a, b) => b.degree - a.degree || b.confidence - a.confidence || a.id.localeCompare(b.id))\n .slice(0, 10)\n .map(({ id, name, type, degree }) => ({ id, name, type, degree }));\n\n const anomalies = this.anomalyEnabled\n ? detectAnomalies(this.getNodes(sessionId), degree, this.anomalyThresholds)\n : [];\n\n // 2.9: distinct machines that contributed to this session's nodes (by global_id).\n const contributors = (this.db.prepare(`\n SELECT COUNT(DISTINCT c.machine_id) AS n\n FROM node_contributors c\n JOIN nodes n ON n.global_id = c.global_id\n WHERE n.session_id = ?\n `).get(sessionId) as { n: number }).n;\n\n // 3.3 FinOps rollups — bucketed by (currency, period) so mixed currencies/periods\n // are never silently summed; only cost-bearing rows are scanned.\n const costByDomain = this.db.prepare(`\n SELECT COALESCE(domain, '(none)') domain,\n json_extract(cost, '$.currency') currency,\n json_extract(cost, '$.period') period,\n SUM(json_extract(cost, '$.amount')) total,\n COUNT(*) nodes\n FROM nodes\n WHERE session_id = ? AND cost IS NOT NULL\n GROUP BY domain, currency, period\n ORDER BY total DESC\n `).all(sessionId) as GraphSummary['costByDomain'];\n const costByOwner = this.db.prepare(`\n SELECT COALESCE(owner, '(unowned)') owner,\n json_extract(cost, '$.currency') currency,\n json_extract(cost, '$.period') period,\n SUM(json_extract(cost, '$.amount')) total,\n COUNT(*) nodes\n FROM nodes\n WHERE session_id = ? AND cost IS NOT NULL\n GROUP BY owner, currency, period\n ORDER BY total DESC\n `).all(sessionId) as GraphSummary['costByOwner'];\n const withCost = (this.db.prepare(\n 'SELECT COUNT(*) c FROM nodes WHERE session_id = ? AND cost IS NOT NULL',\n ).get(sessionId) as { c: number }).c;\n\n return {\n sessionId, totals, nodesByType: byType, nodesByDomain: byDomain,\n edgesByRelationship: byRelationship, topConnected, anomalies, contributors,\n costByDomain, costByOwner, costCoverage: { withCost, total: totals.nodes },\n };\n }\n\n // ── Central collector store (2.12) ──────────────────────────────────────────\n\n /**\n * Resolve (creating once) the synthetic collector session that owns every\n * central node/edge for a tenant. Central nodes are merged by `(org, global_id)`,\n * not by session, so they live under a single deterministic session id\n * (`central:{org}`) — this satisfies the existing `(id, session_id)` node PK and\n * the `session_id` foreign key without a destructive schema change. Idempotent.\n */\n ensureCentralSession(org: string): string {\n const tenant = normalizeTenant(org);\n const id = `central:${tenant}`;\n const existing = this.db.prepare('SELECT id FROM sessions WHERE id = ?').get(id) as { id: string } | undefined;\n if (existing) return id;\n this.db.prepare(\n `INSERT INTO sessions (id, mode, started_at, config, tenant, organization)\n VALUES (?, 'discover', ?, '{}', ?, ?)`\n ).run(id, new Date().toISOString(), tenant, org);\n return id;\n }\n\n /**\n * Find an existing central node within a tenant by its primary identity\n * (`global_id`), returning its stored `id` so a merge keeps a single row.\n */\n findCentralNodeIdByGlobalId(org: string, gid: string): string | undefined {\n const tenant = normalizeTenant(org);\n const row = this.db.prepare(\n 'SELECT id FROM nodes WHERE tenant = ? AND global_id = ? LIMIT 1',\n ).get(tenant, gid) as { id: string } | undefined;\n return row?.id;\n }\n\n /**\n * Secondary merge lookup: an existing central node in the tenant whose\n * `content_hash` matches (catches `id` drift between machines for the same\n * logical resource). Returns its stored `id` and `global_id`.\n */\n findCentralNodeByContentHash(org: string, ch: string): { id: string; globalId: string } | undefined {\n const tenant = normalizeTenant(org);\n const row = this.db.prepare(\n 'SELECT id, global_id FROM nodes WHERE tenant = ? AND content_hash = ? LIMIT 1',\n ).get(tenant, ch) as { id: string; global_id: string | null } | undefined;\n if (!row || row.global_id == null) return undefined;\n return { id: row.id, globalId: row.global_id };\n }\n\n /**\n * Merge one incoming node into the central store for a tenant and append the\n * contributor (2.12). Resolves identity by `(tenant, global_id)` primary, then\n * `(tenant, content_hash)` secondary; on a hit it keeps the existing row's id\n * (so the logical node stays a single row), unions tags, keeps the higher\n * confidence, and merges metadata (incoming values win on key conflict). The\n * incoming `globalId`/`contentHash` are precomputed by the merge core so they\n * are consistent with what the lookups used. Returns whether a new row was\n * created or an existing one was merged. Runs in one transaction.\n */\n upsertCentralNode(\n org: string,\n node: DiscoveryNode,\n identity: { globalId: string; contentHash: string },\n contributor: Contributor,\n ): 'created' | 'merged' {\n const tenant = normalizeTenant(org);\n const sessionId = this.ensureCentralSession(tenant);\n const txn = this.db.transaction((): 'created' | 'merged' => {\n // Resolve the merge target: primary by global_id, secondary by content_hash.\n let targetId = this.findCentralNodeIdByGlobalId(tenant, identity.globalId);\n let outcome: 'created' | 'merged' = 'merged';\n if (!targetId) {\n const byHash = this.findCentralNodeByContentHash(tenant, identity.contentHash);\n targetId = byHash?.id;\n }\n if (!targetId) { targetId = node.id; outcome = 'created'; }\n\n const existing = this.getCentralNode(tenant, sessionId, targetId);\n // Union tags + metadata and keep the higher confidence (mirrors local.ts:44).\n const mergedTags = Array.from(new Set([...(existing?.tags ?? []), ...(node.tags ?? [])]));\n const mergedMeta = { ...(existing?.metadata ?? {}), ...(node.metadata ?? {}) };\n const confidence = Math.max(existing?.confidence ?? 0, node.confidence);\n\n this.db.prepare(`\n INSERT OR REPLACE INTO nodes\n (id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,\n domain, sub_domain, quality_score, tenant, global_id, content_hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n targetId, sessionId, node.type, sanitizeUntrusted(node.name), node.discoveredVia,\n existing?.discoveredAt ?? new Date().toISOString(), 0, confidence,\n JSON.stringify(sanitizeValue(mergedMeta)),\n JSON.stringify(mergedTags.map(sanitizeUntrusted)),\n node.domain != null ? sanitizeUntrusted(node.domain) : (existing?.domain ?? null),\n node.subDomain != null ? sanitizeUntrusted(node.subDomain) : (existing?.subDomain ?? null),\n node.qualityScore ?? existing?.qualityScore ?? null,\n tenant, identity.globalId, identity.contentHash,\n );\n\n // Append/update the contributor (keyed by (global_id, machine_id)).\n this.db.prepare(`\n INSERT INTO node_contributors (global_id, machine_id, hostname, user, organization, at, confidence)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(global_id, machine_id) DO UPDATE SET\n confidence = MAX(confidence, excluded.confidence),\n at = excluded.at,\n hostname = excluded.hostname,\n user = excluded.user,\n organization = excluded.organization\n `).run(\n identity.globalId, contributor.machineId,\n sanitizeUntrusted(contributor.hostname), sanitizeUntrusted(contributor.user),\n contributor.organization != null ? sanitizeUntrusted(contributor.organization) : tenant,\n contributor.at, contributor.confidence,\n );\n return outcome;\n });\n return txn();\n }\n\n /** Insert an edge into the central store for a tenant (idempotent on logical key). */\n insertCentralEdge(org: string, edge: DiscoveryEdge): void {\n const tenant = normalizeTenant(org);\n const sessionId = this.ensureCentralSession(tenant);\n // Skip a logical duplicate (insertEdge writes a random PK, so INSERT OR IGNORE\n // never dedups by (source,target,relationship) on its own).\n const dup = this.db.prepare(\n 'SELECT 1 FROM edges WHERE tenant = ? AND source_id = ? AND target_id = ? AND relationship = ? LIMIT 1',\n ).get(tenant, edge.sourceId, edge.targetId, edge.relationship);\n if (dup) return;\n this.insertEdge(sessionId, edge);\n }\n\n /** A central node by tenant + stored id (the merge target after identity resolution). */\n getCentralNode(org: string, sessionId: string, nodeId: string): NodeRow | undefined {\n const tenant = normalizeTenant(org);\n const row = this.db.prepare(\n 'SELECT * FROM nodes WHERE tenant = ? AND session_id = ? AND id = ?',\n ).get(tenant, sessionId, nodeId) as Record<string, unknown> | undefined;\n return row ? this.mapNode(row) : undefined;\n }\n\n /** All contributors for a logical (global_id) node across an org. */\n getContributorsByGlobalId(gid: string): Contributor[] {\n return this.getContributors(gid);\n }\n\n /**\n * Org-wide aggregate summary (2.12) — the central analogue of\n * {@link getGraphSummary}, scoped `WHERE tenant = ?` so it merges every machine's\n * contribution into one organization-wide view. Cross-tenant isolation is\n * structural: org A's rows never appear in org B's counts.\n */\n getOrgSummary(org: string): OrgSummary {\n const tenant = normalizeTenant(org);\n const totals = {\n nodes: (this.db.prepare('SELECT COUNT(*) c FROM nodes WHERE tenant = ?').get(tenant) as { c: number }).c,\n edges: (this.db.prepare('SELECT COUNT(*) c FROM edges WHERE tenant = ?').get(tenant) as { c: number }).c,\n };\n const byType: Record<string, number> = {};\n for (const r of this.db.prepare('SELECT type, COUNT(*) c FROM nodes WHERE tenant = ? GROUP BY type').all(tenant) as Array<{ type: string; c: number }>) {\n byType[r.type] = r.c;\n }\n const byDomain: Record<string, number> = {};\n for (const r of this.db.prepare(\"SELECT COALESCE(domain, '(none)') d, COUNT(*) c FROM nodes WHERE tenant = ? GROUP BY d\").all(tenant) as Array<{ d: string; c: number }>) {\n byDomain[r.d] = r.c;\n }\n const byRelationship: Record<string, number> = {};\n for (const r of this.db.prepare('SELECT relationship rel, COUNT(*) c FROM edges WHERE tenant = ? GROUP BY rel').all(tenant) as Array<{ rel: string; c: number }>) {\n byRelationship[r.rel] = r.c;\n }\n const topConnected = (this.db.prepare(`\n SELECT n.id, n.name, n.type, COUNT(e.id) AS degree\n FROM nodes n\n LEFT JOIN edges e ON e.tenant = n.tenant AND (e.source_id = n.id OR e.target_id = n.id)\n WHERE n.tenant = ?\n GROUP BY n.id, n.name, n.type\n ORDER BY degree DESC, n.confidence DESC\n LIMIT 10\n `).all(tenant) as Array<{ id: string; name: string; type: string; degree: number }>);\n // Distinct machines that contributed to this org's nodes (org-wide attribution).\n const contributors = (this.db.prepare(`\n SELECT COUNT(DISTINCT c.machine_id) AS n\n FROM node_contributors c\n JOIN nodes n ON n.global_id = c.global_id\n WHERE n.tenant = ?\n `).get(tenant) as { n: number }).n;\n\n return { org: tenant, totals, nodesByType: byType, nodesByDomain: byDomain, edgesByRelationship: byRelationship, topConnected, contributors };\n }\n\n // ── Stats ───────────────────────────────\n\n getStats(sessionId: string): { nodes: number; edges: number; events: number; tasks: number } {\n const nodes = (this.db.prepare('SELECT COUNT(*) as c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const edges = (this.db.prepare('SELECT COUNT(*) as c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const events = (this.db.prepare('SELECT COUNT(*) as c FROM activity_events WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const tasks = (this.db.prepare('SELECT COUNT(*) as c FROM tasks WHERE session_id = ?').get(sessionId) as { c: number }).c;\n return { nodes, edges, events, tasks };\n }\n}\n","import { z } from 'zod';\n// Type-only import (erased at runtime — no value cycle with diff.ts, which\n// imports its node/edge types from this module).\nimport type { TopologyDelta } from './diff.js';\n\n// ── Enums ────────────────────────────────\n\nexport const NODE_TYPES = [\n 'host', 'database_server', 'database', 'table',\n 'web_service', 'api_endpoint', 'cache_server',\n 'message_broker', 'queue', 'topic',\n 'container', 'pod', 'k8s_cluster',\n 'config_file', 'saas_tool', 'unknown',\n] as const;\nexport type NodeType = typeof NODE_TYPES[number];\n\n/**\n * Semantic groupings of node types — the single source of truth shared by the MCP\n * resource layer (services/databases) and the exporters (layer assignment). Each\n * node type belongs to at most one group; anything ungrouped is treated as \"other\".\n */\nexport const NODE_TYPE_GROUPS = {\n saas: ['saas_tool'],\n web: ['web_service', 'api_endpoint'],\n data: ['database_server', 'database', 'table', 'cache_server'],\n messaging: ['message_broker', 'queue', 'topic'],\n infra: ['host', 'container', 'pod', 'k8s_cluster'],\n config: ['config_file'],\n} as const satisfies Record<string, readonly NodeType[]>;\n\nexport const EDGE_RELATIONSHIPS = [\n 'connects_to', 'reads_from', 'writes_to',\n 'calls', 'contains', 'depends_on',\n] as const;\nexport type EdgeRelationship = typeof EDGE_RELATIONSHIPS[number];\n\n// ── Zod Schemas ──────────────────────────\n\n/** ISO-4217-ish currency code (3 uppercase letters). */\nconst CurrencyCode = z.string().regex(/^[A-Z]{3}$/, 'ISO-4217 currency code, e.g. \"USD\"');\n\n/** Billing period the amount covers. Cross-period rollup is bucketed, never normalized (5.1). */\nexport const COST_PERIODS = ['hourly', 'daily', 'monthly', 'yearly'] as const;\nexport type CostPeriod = typeof COST_PERIODS[number];\n\n/** Attributed cost for one billing period (3.3). Amount is in `currency` per `period`. */\nexport const CostEntrySchema = z.object({\n amount: z.number().nonnegative().describe('Spend for one `period`, in `currency`'),\n currency: CurrencyCode,\n period: z.enum(COST_PERIODS),\n source: z.string().optional().describe('Provenance, e.g. \"csv:billing-2026-06\", \"aws-ce\"'),\n});\nexport type CostEntry = z.infer<typeof CostEntrySchema>;\n\nexport const NodeSchema = z.object({\n id: z.string().describe('Format: \"{type}:{host}:{port}\" or \"{type}:{name}\"'),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1).default(0.5),\n metadata: z.record(z.string(), z.unknown()).default({}),\n tags: z.array(z.string()).default([]),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n owner: z.string().optional().describe('Owning team/person, e.g. from an `Owner`/`Team` tag (3.3)'),\n cost: CostEntrySchema.optional().describe('Attributed cost for one billing period (3.3)'),\n});\nexport type DiscoveryNode = z.infer<typeof NodeSchema>;\n\nexport const EdgeSchema = z.object({\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1).default(0.5),\n});\nexport type DiscoveryEdge = z.infer<typeof EdgeSchema>;\n\n// ── Sharing policy (2.10 consent + anonymization) ─────────────────────────────\n\n/**\n * Per-employee sharing levels, ordered most-private → least-private:\n * - `none` — share nothing (the opt-in default; nothing leaves the machine).\n * - `anonymized` — pseudonymize identifying fields (host/user/path/private-IP) via\n * an org-keyed, admin-reversible HMAC while preserving topology shape.\n * - `full` — share the raw record verbatim.\n */\nexport const SHARING_LEVELS = ['none', 'anonymized', 'full'] as const;\nexport const SharingLevelSchema = z.enum(SHARING_LEVELS);\nexport type SharingLevel = typeof SHARING_LEVELS[number];\n\n/**\n * A resolved sharing policy: the global `defaultLevel` (the `'*'` row) plus\n * remembered pattern overrides (glob over the node id). The most-specific\n * matching override wins; the default applies when nothing matches.\n */\nexport interface SharingPolicy {\n defaultLevel: SharingLevel;\n overrides: { pattern: string; level: SharingLevel }[];\n}\n\n// ── Cartography Map Types ────────────────\n\nexport const DataAssetSchema = z.object({\n id: z.string(),\n name: z.string(),\n domain: z.string(),\n subDomain: z.string().optional(),\n qualityScore: z.number().min(0).max(100).optional(),\n metadata: z.record(z.string(), z.unknown()).default({}),\n position: z.object({ q: z.number(), r: z.number() }),\n});\nexport type DataAsset = z.infer<typeof DataAssetSchema>;\n\nexport const ClusterSchema = z.object({\n id: z.string(),\n label: z.string(),\n domain: z.string(),\n color: z.string(),\n assetIds: z.array(z.string()),\n centroid: z.object({ x: z.number(), y: z.number() }),\n});\nexport type Cluster = z.infer<typeof ClusterSchema>;\n\nexport const ConnectionSchema = z.object({\n id: z.string(),\n sourceAssetId: z.string(),\n targetAssetId: z.string(),\n type: z.string().optional(),\n});\nexport type Connection = z.infer<typeof ConnectionSchema>;\n\nexport interface CartographyMapData {\n assets: DataAsset[];\n clusters: Cluster[];\n connections: Connection[];\n meta: { exportedAt: string; theme: 'light' | 'dark' };\n}\n\n/** Navy → medium blue → periwinkle → teal/cyan palette */\nexport const DOMAIN_COLORS: Record<string, string> = {\n 'Quality Control': '#1a2744',\n 'Supply Chain': '#1e3a6e',\n 'Marketing': '#6a7fb5',\n 'Finance': '#3a8a8a',\n 'HR': '#2a5a9a',\n 'Logistics': '#0e7490',\n 'Sales': '#1d4ed8',\n 'Engineering': '#4338ca',\n 'Operations': '#0891b2',\n 'Data Layer': '#1e3352',\n 'Web / API': '#1a3a1a',\n 'Messaging': '#2a1a3a',\n 'Infrastructure': '#0f2a40',\n 'Other': '#374151',\n};\n\n/** Ordered palette for dynamic domain assignment */\nexport const DOMAIN_PALETTE = [\n '#1a2e5a', '#1e3a8a', '#1d4ed8', '#2563eb', '#3b82f6',\n '#6366f1', '#818cf8', '#7c9fc3', '#0e7490', '#0891b2',\n '#06b6d4', '#22d3ee', '#0d9488', '#14b8a6', '#2dd4bf', '#5eead4',\n] as const;\n\n// ── DB Row Types ─────────────────────────\n\nexport interface NodeRow extends DiscoveryNode {\n sessionId: string;\n discoveredAt: string;\n depth: number;\n pathId?: string;\n /**\n * Org-scoped human-readable global identity (`{tenant}:{normalizedId}`); the\n * same logical resource collapses to one `globalId` across machines (2.9).\n */\n globalId?: string;\n /** Secondary dedup key (sha256 over type + name + key-meta) that catches `id` drift between machines (2.9). */\n contentHash?: string;\n}\n\nexport interface EdgeRow extends DiscoveryEdge {\n id: string;\n sessionId: string;\n discoveredAt: string;\n pathId?: string;\n}\n\nexport interface SessionRow {\n id: string;\n mode: 'discover';\n startedAt: string;\n completedAt?: string;\n config: string;\n /** Human-friendly, deterministically-derived label (e.g. \"infra+data · 42 nodes · 2026-06-11\"). */\n name?: string;\n /** Tenant/organization partition this session belongs to. Defaults to `'local'`. */\n tenant: string;\n /**\n * Source attribution captured at session creation (2.9). Local-only — these\n * identifying fields never leave the machine; off-machine sharing (2.11) and\n * anonymization/consent (2.10) are deferred.\n */\n hostname?: string;\n user?: string;\n machineId?: string;\n /**\n * Raw `--org` / `config.organization` value as supplied (provenance). The\n * normalized form is {@link tenant} — the org-scope partition introduced by 2.8.\n */\n organization?: string;\n /**\n * ISO 8601 UTC timestamp of the last in-place rescan of this session (2.1).\n * `undefined`/NULL until the session is rescanned via incremental discovery —\n * the freshness signal for scheduled discovery (2.5) to build on.\n */\n lastScannedAt?: string;\n}\n\n/**\n * One observation of a logical node from a single machine. Accumulated in the\n * `node_contributors` table (keyed by `(global_id, machine_id)`); never anonymized\n * in 2.9 (that is 2.10) and never transmitted off-machine in 2.9 (that is 2.11).\n */\nexport interface Contributor {\n machineId: string;\n hostname: string;\n user: string;\n /** Effective org-scope of the contribution (the session's tenant). */\n organization?: string;\n /** ISO 8601 UTC timestamp of the contributing observation. */\n at: string;\n /** Confidence of the observation that produced this contribution (0–1). */\n confidence: number;\n}\n\n// ── Diff / Drift ─────────────────────────\n\n/**\n * Node fields whose change marks a node as `changed` in a topology diff.\n * `confidence` is deliberately excluded — it fluctuates between scans (noise)\n * and is reported separately as `confidenceDelta` rather than triggering drift.\n */\nexport const DRIFT_FIELDS = ['type', 'name', 'domain', 'subDomain', 'qualityScore', 'metadata', 'tags', 'owner', 'cost'] as const;\nexport type DriftField = typeof DRIFT_FIELDS[number];\n\nexport interface NodeChange {\n id: string;\n before: NodeRow;\n after: NodeRow;\n /** Which of DRIFT_FIELDS differ between `before` and `after`. */\n changedFields: DriftField[];\n /** Informational confidence delta (after − before); does not itself trigger drift. */\n confidenceDelta: number;\n}\n\n// ── Anomalies (3.6) ──────────────────────\n\nexport const ANOMALY_KINDS = ['orphan', 'shadow-it'] as const;\nexport type AnomalyKind = typeof ANOMALY_KINDS[number];\n\nexport const ANOMALY_SEVERITIES = ['low', 'medium', 'high'] as const;\nexport type AnomalySeverity = typeof ANOMALY_SEVERITIES[number];\n\n/** A standing structural anomaly within a single topology snapshot. Deterministic. */\nexport interface Anomaly {\n /** The flagged node, structured id \"{type}:{id}\" — never raw free-text. */\n nodeId: string;\n kind: AnomalyKind;\n severity: AnomalySeverity;\n /** Stable, human-readable explanation built only from nodeId + numeric scores. */\n reason: string;\n}\n\n/** Resolved anomaly thresholds (defaults in `DEFAULT_ANOMALY_THRESHOLDS` unless overridden by config). */\nexport interface AnomalyThresholds {\n /** Degree at or below which a node is a weak-link orphan candidate (0 = isolated). */\n orphanWeakDegree: number;\n /** Confidence (0–1) below which an undomained node is shadow-IT. */\n shadowConfidence: number;\n /** qualityScore (0–100) below which an undomained node is shadow-IT. */\n shadowQuality: number;\n}\n\nexport interface AnomalyConfig extends AnomalyThresholds {\n /** When false, the engine short-circuits to an empty array (rollback flag). */\n enabled: boolean;\n}\n\n/**\n * Default anomaly thresholds. Defined here (not in `anomaly.ts`) so `defaultConfig`\n * can reference them without a runtime cycle; `anomaly.ts` re-exports this constant.\n */\nexport const DEFAULT_ANOMALY_THRESHOLDS: AnomalyThresholds = {\n orphanWeakDegree: 1,\n shadowConfidence: 0.4,\n shadowQuality: 40,\n};\n\nexport interface TopologyDiff {\n base: { sessionId: string; startedAt: string; nodeCount: number; edgeCount: number };\n current: { sessionId: string; startedAt: string; nodeCount: number; edgeCount: number };\n nodes: { added: NodeRow[]; removed: NodeRow[]; changed: NodeChange[]; unchanged: number };\n edges: { added: EdgeRow[]; removed: EdgeRow[]; unchanged: number };\n summary: {\n nodesAdded: number; nodesRemoved: number; nodesChanged: number;\n edgesAdded: number; edgesRemoved: number;\n };\n /** Standing anomalies in base vs current, plus those newly appearing in current (3.6). */\n anomalies: { base: Anomaly[]; current: Anomaly[]; added: Anomaly[] };\n}\n\n// ── Drift alerts (3.1) ───────────────────\n\n/** Severity rank, ascending. `maxSeverity` and threshold filtering rely on this order. */\nexport const SEVERITIES = ['info', 'warning', 'critical'] as const;\nexport type Severity = typeof SEVERITIES[number];\n\n/**\n * Free-form metadata keys (case-insensitive) whose change escalates a node-changed\n * item to `critical`. Security-/exposure-relevant signals live only in the\n * free-form `metadata` blob (there are no first-class security node fields).\n */\nexport const SECURITY_METADATA_KEYS = [\n 'publicexposure', 'public', 'exposed', 'iamrole', 'role', 'encryption',\n 'encrypted', 'tls', 'tlsenabled', 'ports', 'openports', 'auth', 'authentication',\n] as const;\n\nexport type DriftItemKind =\n | 'node-added' | 'node-removed' | 'node-changed' | 'edge-added' | 'edge-removed';\n\nexport interface DriftAlertItem {\n kind: DriftItemKind;\n /** Node id, or \"source -rel-> target\" for edges. */\n ref: string;\n /** Human-readable node/edge name for display. */\n label: string;\n nodeType?: NodeType;\n severity: Severity;\n /** Present for node-changed; subset of DRIFT_FIELDS that differ. */\n changedFields?: DriftField[];\n /** Present for node-changed; metadata keys that triggered escalation. */\n securityFields?: string[];\n}\n\nexport interface DriftAlert {\n base: TopologyDiff['base'];\n current: TopologyDiff['current'];\n summary: TopologyDiff['summary'];\n /** Overall severity = max severity across items (info when no items). */\n severity: Severity;\n items: DriftAlertItem[];\n /** ISO-8601 UTC generation time. */\n generatedAt: string;\n}\n\n/** One configured drift sink. `url` is required when `type === 'webhook'`. */\nexport interface DriftSinkConfig {\n type: 'stdout' | 'webhook';\n /** Required when type === 'webhook'. */\n url?: string;\n /** Optional bearer token; falls back to CARTOGRAPHY_DRIFT_TOKEN. */\n token?: string;\n timeoutMs?: number;\n}\n\n/**\n * Opt-in drift-alerting block on {@link CartographyConfig}. Absent → the runner\n * defaults to a single `stdout` sink at `minSeverity: 'info'` (everything stays\n * local; no outbound traffic unless a `webhook` sink is explicitly configured).\n */\nexport interface DriftConfig {\n /** Items below this severity are dropped before dispatch. Default 'info'. */\n minSeverity: Severity;\n sinks: DriftSinkConfig[];\n}\n\n/** Validate an externally-supplied drift block (CLI/env/future file loader). */\nexport const DriftConfigSchema = z.object({\n minSeverity: z.enum(SEVERITIES).default('info'),\n sinks: z.array(z.object({\n type: z.enum(['stdout', 'webhook']),\n url: z.string().url().optional(),\n token: z.string().optional(),\n timeoutMs: z.number().int().positive().optional(),\n })).default([{ type: 'stdout' }]),\n}).superRefine((cfg, ctx) => {\n for (const [i, s] of cfg.sinks.entries()) {\n if (s.type === 'webhook' && !s.url) {\n ctx.addIssue({ code: 'custom', path: ['sinks', i, 'url'], message: 'webhook sink requires a url' });\n }\n }\n});\n\n// ── Schedule / config file (2.5) ─────────\n\n/** Machine-readable result formats shared by `discover` (#67) and `schedule`. */\nexport const OUTPUT_FORMATS = ['text', 'json', 'stream-json'] as const;\nexport type OutputFormat = typeof OUTPUT_FORMATS[number];\n\n/**\n * A recurring-discovery schedule, read from a JSON config file. The `cron`\n * string is a 5-field expression (min hour dom month dow) validated by\n * `parseCron` in `schedule.ts`; the Zod schema only enforces non-emptiness so\n * the config layer stays decoupled from the cron grammar.\n */\nexport const ScheduleConfigSchema = z\n .object({\n /** 5-field cron expression (min hour dom month dow). Validated by schedule.ts. */\n cron: z.string().min(1),\n /** Discovery entry points for the scheduled scan (falls back to the file-level / default). */\n entryPoints: z.array(z.string().min(1)).nonempty().optional(),\n /** Machine-readable result format for `--watch`/`--once` output. */\n outputFormat: z.enum(OUTPUT_FORMATS).default('json'),\n /** Catalog path the scheduled runs read/write (falls back to the file-level / default). */\n dbPath: z.string().min(1).optional(),\n })\n .strict();\nexport type ScheduleConfig = z.infer<typeof ScheduleConfigSchema>;\n\n// ── Central-DB sync (2.11) ───────────────\n\n/**\n * Outbound central-DB connection (2.11). The first egress path Cartograph has:\n * after a scan, consented, policy-transformed deltas are pushed to this ingest\n * endpoint over bearer-auth HTTPS. Presence of `url` *is* the feature flag — when\n * absent the entire sync pipeline short-circuits and nothing ever networks.\n *\n * `.strict()` so a typo'd key in `config.json` fails loudly. The `token` is an\n * opaque secret (never logged, never serialized into a payload); `org` is forwarded\n * as a header so the central side (2.12) can scope ingest by tenant.\n */\nexport const CentralDbConfigSchema = z\n .object({\n /** Ingest endpoint. Must be `https:` for any non-loopback host (see push.ts). */\n url: z.string().url(),\n /** Opaque bearer token sent as `Authorization: Bearer <token>`. Never logged. */\n token: z.string().min(1),\n /** Org/tenant routing hint forwarded to the ingest API. */\n org: z.string().min(1).optional(),\n /** Items per push batch (default 100). */\n batchSize: z.number().int().positive().max(1000).optional(),\n })\n .strict();\nexport type CentralDbConfig = z.infer<typeof CentralDbConfigSchema>;\n\n/**\n * Read a {@link CentralDbConfig} from environment variables\n * (`CARTOGRAPHY_CENTRAL_URL`/`_TOKEN`/`_ORG`), letting CI / secret-managers inject\n * the token without a file. Returns a partial — only the keys actually present —\n * so it composes field-wise over a `config.json` block. Never throws.\n */\nexport function centralDbFromEnv(env: NodeJS.ProcessEnv = process.env): Partial<CentralDbConfig> {\n const out: Partial<CentralDbConfig> = {};\n if (env.CARTOGRAPHY_CENTRAL_URL) out.url = env.CARTOGRAPHY_CENTRAL_URL;\n if (env.CARTOGRAPHY_CENTRAL_TOKEN) out.token = env.CARTOGRAPHY_CENTRAL_TOKEN;\n if (env.CARTOGRAPHY_CENTRAL_ORG) out.org = env.CARTOGRAPHY_CENTRAL_ORG;\n return out;\n}\n\n/**\n * Lifecycle status of one queued share item (2.11):\n * - `pending` — new/unmatched, awaiting the employee's explicit review.\n * - `approved` — cleared to leave (by `sync review`, or auto by a remembered rule).\n * - `shared` — successfully pushed to the central ingest endpoint.\n * - `withheld` — explicitly suppressed; never leaves.\n *\n * The load-bearing privacy invariant: only `approved` rows are ever pushed.\n */\nexport const PENDING_STATUSES = ['pending', 'approved', 'shared', 'withheld'] as const;\nexport type PendingStatus = typeof PENDING_STATUSES[number];\n\n/**\n * One row of the `pending_shares` review queue (2.11). `payload` is the *already\n * policy-transformed* (anonymized/dropped) projection from `previewShare` — never\n * raw node data for `anonymized`/`none` items — so what is queued is exactly what\n * may leave. Keyed by `contentHash` (a hash of that transformed payload).\n */\nexport interface PendingShareRow {\n contentHash: string;\n sessionId: string;\n nodeId?: string;\n kind: 'node' | 'edge';\n /** Policy-transformed payload (the exact bytes a push would send). */\n payload: unknown;\n status: PendingStatus;\n /** Who decided: `'user'` (interactive review) or `'rule'` (remembered policy). */\n decidedBy?: 'user' | 'rule';\n createdAt: string;\n decidedAt?: string;\n sharedAt?: string;\n}\n\n/**\n * Top-level shape of a `cartography.config.json` file. `.strict()` rejects\n * unknown keys so typos fail loudly rather than being silently ignored. WS 2.11\n * (central-org sync) extends this same schema with a `centralDb` block.\n */\nexport const ConfigFileSchema = z\n .object({\n schedule: ScheduleConfigSchema.optional(),\n entryPoints: z.array(z.string().min(1)).nonempty().optional(),\n dbPath: z.string().min(1).optional(),\n organization: z.string().min(1).optional(),\n centralDb: CentralDbConfigSchema.optional(),\n })\n .strict();\nexport type ConfigFile = z.infer<typeof ConfigFileSchema>;\n\n/**\n * One persisted scheduled-discovery run (2.5). Records what changed between this\n * run's session and the prior one, with the full {@link TopologyDelta} for audit\n * and the summary counts for fast querying. `baseSessionId` is `undefined` on the\n * very first run (no prior topology — everything is `added`).\n */\nexport interface DriftRunRow {\n id: string;\n sessionId: string;\n baseSessionId?: string;\n /** ISO 8601 UTC timestamp this run was recorded. */\n ranAt: string;\n summary: {\n nodesAdded: number;\n nodesRemoved: number;\n nodesChanged: number;\n edgesAdded: number;\n edgesRemoved: number;\n };\n delta: TopologyDelta;\n}\n\n// ── Config ───────────────────────────────\n\n/**\n * Agent backend selectable via `--provider` / `CARTOGRAPHY_PROVIDER`. Defined here\n * (in the dependency-free types module) and re-exported from `providers/types.ts`\n * so `defaultConfig` can reference it without a runtime cycle.\n */\nexport type ProviderName = 'claude' | 'openai' | 'ollama';\n\nexport interface CartographyConfig {\n maxDepth: number;\n maxTurns: number;\n entryPoints: string[];\n /** Agent backend. Defaults to `'claude'`; selected by `--provider` / `CARTOGRAPHY_PROVIDER`. */\n provider: ProviderName;\n /** Lead/discovery model. Back-compat alias for `models.lead` (kept in sync by defaultConfig). */\n agentModel: string;\n /** Model roles: `lead` drives discovery, `fast` powers cheaper helper tasks (e.g. chat). */\n models: { lead: string; fast: string };\n organization?: string;\n outputDir: string;\n dbPath: string;\n verbose: boolean;\n /** Max characters of a single scan-tool response returned to the agent (guards the context window). */\n maxToolResponseBytes: number;\n /** Explicit allowlist of scanner plugin package names to load (opt-in / consent-first). Default `[]`. */\n plugins: string[];\n /**\n * Optional recurring-discovery schedule (2.5), populated from a config file by\n * `loadConfig`. `undefined` for every existing/CLI caller — additive only.\n */\n schedule?: ScheduleConfig;\n /**\n * Optional central-DB outbound sync target (2.11). `undefined` for every caller\n * unless configured via `config.json` (`centralDb` block), the\n * `CARTOGRAPHY_CENTRAL_*` env vars, or an explicit override. Absent = the sync\n * pipeline is fully inert (no classify, no queue, no push).\n */\n centralDb?: CentralDbConfig;\n /**\n * Optional anomaly-detection thresholds (3.6). `undefined` for every existing\n * caller — `defaultConfig` populates it from `DEFAULT_ANOMALY_THRESHOLDS`, and the\n * engine falls back to those defaults when absent (optional-deps-degrade).\n */\n anomaly?: AnomalyConfig;\n /**\n * Optional drift-alerting block (3.1). `undefined` for every existing/CLI caller\n * (additive only); when absent the drift runner defaults to a local `stdout` sink.\n * No outbound traffic unless an operator configures a `webhook` sink.\n */\n drift?: DriftConfig;\n}\n\n/** Default lead (discovery) model. */\nexport const DEFAULT_LEAD_MODEL = 'claude-sonnet-4-5-20250929';\n/** Default fast model for helper tasks (chat, summaries). */\nexport const DEFAULT_FAST_MODEL = 'claude-haiku-4-5-20251001';\n\nexport function defaultConfig(overrides: Partial<CartographyConfig> = {}): CartographyConfig {\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n const base: CartographyConfig = {\n maxDepth: 8,\n maxTurns: 50,\n entryPoints: ['localhost'],\n provider: 'claude',\n agentModel: DEFAULT_LEAD_MODEL,\n models: { lead: DEFAULT_LEAD_MODEL, fast: DEFAULT_FAST_MODEL },\n outputDir: './cartography-output',\n dbPath: `${home}/.cartography/cartography.db`,\n verbose: false,\n maxToolResponseBytes: 100_000,\n plugins: [],\n anomaly: { enabled: true, ...DEFAULT_ANOMALY_THRESHOLDS },\n };\n const merged = { ...base, ...overrides };\n // Keep the invariant agentModel === models.lead so existing agentModel readers\n // and the new role config stay consistent. An explicit `models` override wins;\n // otherwise a legacy `agentModel` override flows into the lead role.\n const lead = overrides.models?.lead ?? merged.agentModel;\n const fast = overrides.models?.fast ?? merged.models.fast;\n\n // 2.11 central-DB sync: assemble the optional `centralDb` block field-wise from\n // (low→high) the explicit override < env. A field set in `overrides.centralDb`\n // (e.g. file-derived in loadConfig) wins; env fills the remaining fields, so a\n // file `url` + env `token` compose. Validate the result with `CentralDbConfigSchema`;\n // on failure (or no `url`) drop it and warn — the feature stays inert, never crashes.\n const centralDb = resolveCentralDb(overrides.centralDb);\n\n const out: CartographyConfig = { ...merged, agentModel: lead, models: { lead, fast } };\n // Only a validated block lands; a dropped/invalid one is removed entirely so the\n // feature stays inert rather than carrying through a malformed override.\n if (centralDb) out.centralDb = centralDb;\n else delete out.centralDb;\n return out;\n}\n\n/**\n * Resolve the effective `centralDb` from an explicit (override/file) partial layered\n * over `CARTOGRAPHY_CENTRAL_*` env. Field-wise merge (override wins per field), then\n * `safeParse`. Returns `undefined` (and warns to stderr) when no `url` is present or\n * the assembled block fails validation — so a missing/invalid config degrades to an\n * inert feature rather than a crash. The token is never written to the warning.\n */\nfunction resolveCentralDb(explicit?: Partial<CentralDbConfig>): CentralDbConfig | undefined {\n const env = centralDbFromEnv();\n const assembled: Partial<CentralDbConfig> = { ...env, ...explicit };\n if (assembled.url === undefined && assembled.token === undefined && assembled.org === undefined && assembled.batchSize === undefined) {\n return undefined; // nothing configured anywhere — feature off\n }\n const parsed = CentralDbConfigSchema.safeParse(assembled);\n if (!parsed.success) {\n const detail = parsed.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`).join('; ');\n process.stderr.write(`[cartography] ignoring invalid centralDb config: ${detail}\\n`);\n return undefined;\n }\n return parsed.data;\n}\n","import { z } from 'zod';\nimport type { CartographyDB } from './db.js';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS } from './types.js';\nimport { sanitizeUntrusted } from './sanitize.js';\nimport { scanAllBookmarks, scanAllHistory } from './bookmarks.js';\nimport { logDebug } from './logger.js';\nimport {\n IS_WIN, IS_MAC, IS_LINUX, HOME, PLATFORM,\n run, commandExists, findFiles, dbScanDirs,\n scanListeningPorts, scanProcesses,\n scanWindowsPrograms, scanWindowsDbServices,\n} from './platform.js';\nimport type { Scanner, ScanContext } from './scanners/types.js';\nimport { cloudAwsScanner } from './scanners/cloud-aws.js';\nimport { cloudGcpScanner } from './scanners/cloud-gcp.js';\nimport { cloudAzureScanner } from './scanners/cloud-azure.js';\nimport { k8sScanner } from './scanners/k8s.js';\nimport { databasesScanner } from './scanners/databases.js';\n\n// Type-only import — no runtime dependency on SDK at module parse time\nimport type { McpServerConfig } from '@anthropic-ai/claude-agent-sdk';\n\n/**\n * Circuit breaker for sequential CLI scans.\n * After `threshold` consecutive failures, remaining commands are skipped.\n */\nexport function createScanRunner(\n runFn: (cmd: string, opts?: { timeout?: number; env?: NodeJS.ProcessEnv }) => string,\n opts: { timeout?: number; env?: NodeJS.ProcessEnv; threshold?: number } = {},\n) {\n const threshold = opts.threshold ?? 3;\n let consecutiveFailures = 0;\n let tripped = false;\n\n return (cmd: string): string => {\n if (tripped) {\n logDebug(`Circuit breaker: skipping \"${cmd}\" (${consecutiveFailures} consecutive failures)`);\n return '(skipped — circuit breaker: too many consecutive failures)';\n }\n const result = runFn(cmd, { timeout: opts.timeout ?? 20_000, env: opts.env });\n if (!result) {\n consecutiveFailures++;\n if (consecutiveFailures >= threshold) {\n tripped = true;\n logDebug(`Circuit breaker tripped after ${threshold} failures, last command: \"${cmd}\"`);\n }\n return '(error or not available)';\n }\n consecutiveFailures = 0;\n return result;\n };\n}\n\nexport interface CartographyToolsOptions {\n /** Called when the agent needs a human answer. Return the user's response. */\n onAskUser?: (question: string, context?: string) => Promise<string>;\n /** Max characters of a single tool response (sanitized + truncated). Default 100 000. */\n maxResponseBytes?: number;\n}\n\n/**\n * Sanitize untrusted scan output (strip invisible/control characters) and cap it\n * at `max` characters so a single verbose scan can never blow the agent's context\n * window. Appends an explicit truncation notice when clamped.\n */\nexport function clampText(raw: string, max: number): string {\n const clean = sanitizeUntrusted(raw);\n if (clean.length <= max) return clean;\n return clean.slice(0, max) +\n `\\n\\n… [output truncated: ${clean.length - max} more characters omitted — narrow the scan (e.g. a namespace/region/hint) or query the catalog instead]`;\n}\n\nexport function stripSensitive(target: string): string {\n const raw = target.trim();\n if (!raw) return raw;\n try {\n const url = new URL(raw.startsWith('http') ? raw : `tcp://${raw}`);\n const stripped = `${url.hostname}${url.port ? ':' + url.port : ''}`;\n return stripped || raw;\n } catch {\n const stripped = raw\n .replace(/\\/.*$/, '')\n .replace(/\\?.*$/, '')\n .replace(/@.*:/, ':');\n return stripped || raw;\n }\n}\n\n// ── Argument hardening for shell-backed scan tools ───────────────────────────\n\n/**\n * Strict patterns for the cloud/k8s scan parameters that get spliced into a\n * shell command. `run()` re-checks the final command against the read-only\n * allowlist, but values are validated here first so an injection payload never\n * reaches the shell — not even a read-only one (e.g. `; cat ~/.ssh/id_rsa`)\n * that the allowlist would otherwise permit and which could disclose files.\n */\nexport const SCAN_ARG_PATTERNS = {\n 'k8s-namespace': /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/,\n 'aws-region': /^[A-Za-z0-9-]+$/,\n 'aws-profile': /^[A-Za-z0-9_.-]+$/,\n 'gcp-project': /^[a-z0-9][a-z0-9.-]*(:[a-z0-9][a-z0-9-]*)?$/,\n 'azure-subscription': /^[0-9a-fA-F-]+$/,\n 'azure-resource-group': /^[A-Za-z0-9_.()-]+$/,\n} as const;\n\nexport type ScanArgKind = keyof typeof SCAN_ARG_PATTERNS;\n\n/** Throw if `value` fails the strict pattern for `kind`; otherwise return it. */\nexport function assertSafeScanArg(kind: ScanArgKind, value: string): string {\n if (!SCAN_ARG_PATTERNS[kind].test(value)) {\n throw new Error(`Invalid ${kind} \"${value}\": contains characters that are not allowed`);\n }\n return value;\n}\n\n/** Redact `user:password@` credentials embedded in any URL/DSN-like string. */\nexport function redactSecrets(value: string): string {\n return value.replace(/([a-z][a-z0-9+.-]*:\\/\\/[^:@/\\s]+):[^@/\\s]+@/gi, '$1:***@');\n}\n\n/** Recursively redact secrets from arbitrary metadata before persistence. */\nexport function redactValue(value: unknown): unknown {\n if (typeof value === 'string') return redactSecrets(value);\n if (Array.isArray(value)) return value.map(redactValue);\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = redactValue(v);\n return out;\n }\n return value;\n}\n\n/**\n * Tool annotations — untrusted hints for MCP hosts (host-side gating), never a\n * security boundary. Mirror the server-side query tools in src/mcp/server.ts.\n */\nconst READ_SCAN = { readOnlyHint: true, openWorldHint: true } as const; // scanners reach external/system state\nconst READ_LOCAL = { readOnlyHint: true, openWorldHint: false } as const; // reads the local catalog / asks the user\nconst WRITE_CATALOG = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\n\n/** The MCP text-content shape every tool handler returns. */\nexport interface ToolResult {\n content: { type: 'text'; text: string }[];\n}\n\n/**\n * Provider-neutral tool definition: a zod input shape plus a handler that returns\n * the MCP text-content shape. This is the single source of truth for the discovery\n * tools — the Claude provider wraps these via the SDK `tool()` factory, while\n * OpenAI/Ollama convert `inputShape` to JSON Schema and call `handler` directly.\n */\nexport interface AgentTool {\n name: string;\n description: string;\n /** Raw zod shape (ZodRawShape) for the tool input. */\n inputShape: z.ZodRawShape;\n /** Host-side gating hints (never a security boundary). */\n annotations: Record<string, boolean>;\n handler: (args: Record<string, unknown>) => Promise<ToolResult>;\n}\n\n/**\n * Build the discovery tool handlers with **no** SDK dependency. Returns the same\n * handler bodies used by the Claude path, exposed neutrally so any provider can\n * reuse them. `buildCartographyToolDefinitions` wraps these for the Claude SDK.\n */\nexport async function buildCartographyToolHandlers(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n): Promise<AgentTool[]> {\n const maxResponseBytes = opts.maxResponseBytes ?? 100_000;\n /** Wrap sanitized + truncated scan output in the MCP text-content shape. */\n const textResult = (raw: string): ToolResult => ({ content: [{ type: 'text' as const, text: clampText(raw, maxResponseBytes) }] });\n\n /**\n * Run a registry {@link Scanner} as an agent tool: detect → scan → return a\n * structured node summary plus the raw report. The deterministic scanners are\n * the single source of truth for cloud/k8s/db discovery; this wrapper delegates\n * to them so the agent path stays in lock-step with `run_discovery`.\n */\n const runScannerTool = async (scanner: Scanner, hint: string): Promise<ToolResult> => {\n const ctx: ScanContext = { hint, platform: PLATFORM, run };\n if (!(await scanner.detect(ctx))) return textResult(`(${scanner.title}: CLI not available)`);\n const result = await scanner.scan(ctx);\n const structured = `=== NODES (${result.nodes.length}) / EDGES (${result.edges.length}) ===\\n` +\n JSON.stringify({ nodes: result.nodes, edges: result.edges });\n return textResult([structured, result.report ?? ''].filter(Boolean).join('\\n\\n'));\n };\n\n /** Local declarative helper mirroring the SDK `tool()` signature, but SDK-free. */\n const tool = (\n name: string,\n description: string,\n inputShape: z.ZodRawShape,\n handler: (args: Record<string, unknown>) => Promise<ToolResult>,\n extra: { annotations: Record<string, boolean> },\n ): AgentTool => ({ name, description, inputShape, annotations: extra.annotations, handler });\n\n const tools: AgentTool[] = [\n tool('save_node', 'Save an infrastructure node to the catalog', {\n id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1),\n metadata: z.record(z.string(), z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n }, async (args) => {\n const node = {\n id: stripSensitive(args['id'] as string),\n type: args['type'] as typeof NODE_TYPES[number],\n name: args['name'] as string,\n discoveredVia: args['discoveredVia'] as string,\n confidence: args['confidence'] as number,\n metadata: redactValue((args['metadata'] as Record<string, unknown>) ?? {}) as Record<string, unknown>,\n tags: (args['tags'] as string[]) ?? [],\n domain: args['domain'] as string | undefined,\n subDomain: args['subDomain'] as string | undefined,\n qualityScore: args['qualityScore'] as number | undefined,\n };\n db.upsertNode(sessionId, node);\n return { content: [{ type: 'text', text: `✓ Node: ${node.id}` }] };\n }, { annotations: WRITE_CATALOG }),\n\n tool('save_edge', 'Save a relationship (edge) between two nodes — ALWAYS save edges when connections are clear', {\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertEdge(sessionId, {\n sourceId: args['sourceId'] as string,\n targetId: args['targetId'] as string,\n relationship: args['relationship'] as typeof EDGE_RELATIONSHIPS[number],\n evidence: redactSecrets(args['evidence'] as string),\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ ${args['sourceId']}→${args['targetId']}` }] };\n }, { annotations: WRITE_CATALOG }),\n\n tool('get_catalog', 'Get the current catalog — use before save_node to avoid duplicates', {\n includeEdges: z.boolean().default(true),\n }, async (args) => {\n const nodes = db.getNodes(sessionId);\n const edges = (args['includeEdges'] as boolean) ? db.getEdges(sessionId) : [];\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: { nodes: nodes.length, edges: edges.length },\n nodeIds: nodes.map(n => n.id),\n }),\n }],\n };\n }, { annotations: READ_LOCAL }),\n\n tool('ask_user', 'Ask the user a question — for clarifications, missing context, or consent (e.g. before scanning browser history)', {\n question: z.string().describe('The question for the user (clear and specific)'),\n context: z.string().optional().describe('Optional context explaining why this is relevant'),\n }, async (args) => {\n const question = args['question'] as string;\n const context = args['context'] as string | undefined;\n\n if (opts.onAskUser) {\n const answer = await opts.onAskUser(question, context);\n return { content: [{ type: 'text', text: answer }] };\n }\n\n // Fallback when not interactive (piped input, daemon, etc.)\n return {\n content: [{ type: 'text', text: '(Non-interactive mode — please continue without this information)' }],\n };\n }, { annotations: READ_LOCAL }),\n\n tool('scan_bookmarks', 'Scan all browser bookmarks — hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)', {\n minConfidence: z.number().min(0).max(1).default(0.5).optional(),\n }, async () => {\n const hosts = await scanAllBookmarks();\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: hosts.length,\n hosts: hosts.map(h => ({\n hostname: h.hostname,\n port: h.port,\n protocol: h.protocol,\n source: h.source,\n })),\n note: 'Hostnames only — no paths, no personal data. Classify each as a business tool (save_node) or ignore (social media, news, shopping).',\n }),\n }],\n };\n }, { annotations: READ_SCAN }),\n\n tool('scan_browser_history', 'Scan browser history — anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.', {\n minVisits: z.number().min(1).default(3).optional().describe('Minimum visit count to include a host (filters rarely-visited sites)'),\n }, async (args) => {\n const minVisits = (args['minVisits'] as number | undefined) ?? 3;\n const hosts = await scanAllHistory();\n const filtered = hosts.filter(h => h.visitCount >= minVisits);\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: filtered.length,\n note: 'Anonymized — hostnames only, no URLs, no paths, no personal data. Classify business tools as saas_tool nodes.',\n hosts: filtered.map(h => ({\n hostname: h.hostname,\n visitCount: h.visitCount,\n protocol: h.protocol,\n source: h.source,\n })),\n }),\n }],\n };\n }, { annotations: READ_SCAN }),\n\n tool('scan_local_databases', 'Scan for local database files and running DB servers — PostgreSQL databases, MySQL, SQLite files from installed apps', {\n deep: z.boolean().default(false).optional().describe('Also search home directory recursively for SQLite/DB files (slower)'),\n }, async (args) => {\n const deep = (args['deep'] as boolean | undefined) ?? false;\n return runScannerTool(databasesScanner, deep ? 'deep' : '');\n }, { annotations: READ_SCAN }),\n\n tool('scan_k8s_resources', 'Scan Kubernetes cluster via kubectl — 100% readonly (get, describe)', {\n namespace: z.string().regex(SCAN_ARG_PATTERNS['k8s-namespace'], 'invalid Kubernetes namespace').optional().describe('Filter by namespace — empty = all namespaces'),\n }, async (args) => {\n const ns = args['namespace'] as string | undefined;\n if (ns) assertSafeScanArg('k8s-namespace', ns);\n return runScannerTool(k8sScanner, ns ? `namespace=${ns}` : '');\n }, { annotations: READ_SCAN }),\n\n tool('scan_aws_resources', 'Scan AWS infrastructure via AWS CLI — 100% readonly (describe, list)', {\n region: z.string().regex(SCAN_ARG_PATTERNS['aws-region'], 'invalid AWS region').optional().describe('AWS Region — default: AWS_DEFAULT_REGION or profile'),\n profile: z.string().regex(SCAN_ARG_PATTERNS['aws-profile'], 'invalid AWS profile').optional().describe('AWS CLI profile'),\n }, async (args) => {\n const region = args['region'] as string | undefined;\n const profile = args['profile'] as string | undefined;\n if (region) assertSafeScanArg('aws-region', region);\n if (profile) assertSafeScanArg('aws-profile', profile);\n const hint = [region ? `region=${region}` : '', profile ? `profile=${profile}` : ''].filter(Boolean).join(' ');\n return runScannerTool(cloudAwsScanner, hint);\n }, { annotations: READ_SCAN }),\n\n tool('scan_gcp_resources', 'Scan Google Cloud Platform via gcloud CLI — 100% readonly (list, describe)', {\n project: z.string().regex(SCAN_ARG_PATTERNS['gcp-project'], 'invalid GCP project id').optional().describe('GCP Project ID — default: current gcloud project'),\n }, async (args) => {\n const project = args['project'] as string | undefined;\n if (project) assertSafeScanArg('gcp-project', project);\n return runScannerTool(cloudGcpScanner, project ? `project=${project}` : '');\n }, { annotations: READ_SCAN }),\n\n tool('scan_azure_resources', 'Scan Azure infrastructure via az CLI — 100% readonly (list, show)', {\n subscription: z.string().regex(SCAN_ARG_PATTERNS['azure-subscription'], 'invalid Azure subscription id').optional().describe('Azure Subscription ID'),\n resourceGroup: z.string().regex(SCAN_ARG_PATTERNS['azure-resource-group'], 'invalid Azure resource group').optional().describe('Filter by resource group'),\n }, async (args) => {\n const sub = args['subscription'] as string | undefined;\n const rg = args['resourceGroup'] as string | undefined;\n if (sub) assertSafeScanArg('azure-subscription', sub);\n if (rg) assertSafeScanArg('azure-resource-group', rg);\n const hint = [sub ? `subscription=${sub}` : '', rg ? `resource-group=${rg}` : ''].filter(Boolean).join(' ');\n return runScannerTool(cloudAzureScanner, hint);\n }, { annotations: READ_SCAN }),\n\n tool('scan_installed_apps', 'Scan all installed apps and tools — IDEs, office, dev tools, business apps, databases', {\n searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. \"hubspot windsurf cursor\")'),\n }, async (args) => {\n const hint = args['searchHint'] as string | undefined;\n const results: Record<string, string> = {};\n results['PLATFORM'] = `${PLATFORM} (${IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux'})`;\n\n if (IS_MAC) {\n // macOS: scan /Applications\n results['APPLICATIONS'] = run('ls /Applications/ 2>/dev/null | head -200') || '(empty)';\n results['USER_APPLICATIONS'] = run('ls ~/Applications/ 2>/dev/null | head -100') || '(empty)';\n // Homebrew\n results['BREW_CASKS'] = run('brew list --cask 2>/dev/null | head -100') || '(brew not installed)';\n results['BREW_FORMULAE'] = run('brew list --formula 2>/dev/null | head -150') || '(brew not installed)';\n // Spotlight — find .app bundles\n results['SPOTLIGHT_APPS'] = run('mdfind \"kMDItemKind == \\'Application\\'\" 2>/dev/null | grep -v \"^/System\" | grep -v \"^/Library/Apple\" | head -100') || '(Spotlight not available)';\n } else if (IS_LINUX) {\n // Linux: dpkg, snap, flatpak, .desktop files\n results['DPKG'] = run('dpkg --list 2>/dev/null | awk \\'{print $2}\\' | head -200') || '(dpkg not available)';\n results['SNAP'] = run('snap list 2>/dev/null | head -50') || '(snap not available)';\n results['FLATPAK'] = run('flatpak list 2>/dev/null | head -50') || '(flatpak not available)';\n results['DESKTOP_FILES'] = run('ls /usr/share/applications/*.desktop ~/.local/share/applications/*.desktop 2>/dev/null | xargs -I{} basename {} .desktop 2>/dev/null | head -100') || '(no .desktop files)';\n results['RPM'] = run('rpm -qa 2>/dev/null | head -200') || '(rpm not available)';\n } else if (IS_WIN) {\n // Windows: winget, registry, Get-Package\n results['WINGET'] = run('winget list --accept-source-agreements', { timeout: 20_000 }) || '(winget not available)';\n results['INSTALLED_PROGRAMS'] = scanWindowsPrograms() || '(registry scan failed)';\n // Chocolatey\n results['CHOCO'] = run('choco list --local-only', { timeout: 15_000 }) || '(chocolatey not installed)';\n // Scoop\n results['SCOOP'] = run('scoop list', { timeout: 15_000 }) || '(scoop not installed)';\n }\n\n // ── Check known dev/business tools via cross-platform commandExists ──\n const knownTools = [\n // IDEs & Editors\n 'code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'vim', 'nvim', 'emacs', 'nano', 'sublime_text', 'atom',\n 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm', 'rubymine', 'appcode',\n // Dev Tools\n 'git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible',\n 'node', 'npm', 'npx', 'yarn', 'pnpm', 'bun', 'deno',\n 'python', 'python3', 'pip', 'pip3', 'pipenv', 'poetry', 'conda',\n 'ruby', 'gem', 'bundler', 'rails',\n 'java', 'mvn', 'gradle', 'kotlin',\n 'go', 'cargo', 'rustc',\n 'php', 'composer',\n 'dotnet',\n // Databases\n 'psql', 'mysql', 'mysqladmin', 'mongo', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client',\n // Cloud CLIs\n 'aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler',\n // Infra\n 'vagrant', 'packer', 'consul', 'vault', 'nomad',\n // Communication / SaaS\n 'slack', 'discord', 'zoom', 'teams', 'skype', 'telegram', 'signal',\n // Browsers\n 'google-chrome', 'chromium', 'firefox', 'safari', 'brave', 'opera', 'edge',\n // Windows-specific\n ...(IS_WIN ? ['pwsh', 'powershell', 'wsl', 'winget', 'choco', 'scoop', 'notepad++'] : []),\n // Monitoring / Analytics\n 'datadog-agent', 'newrelic-agent', 'prometheus', 'grafana-cli',\n // Other tools\n 'ngrok', 'stripe', 'supabase', 'neon',\n ];\n\n const found: string[] = [];\n const notFound: string[] = [];\n for (const t of knownTools) {\n const r = commandExists(t);\n if (r) found.push(`${t}: ${r}`);\n else notFound.push(t);\n }\n results['TOOLS_FOUND'] = found.join('\\n') || '(none found)';\n results['TOOLS_NOT_FOUND'] = notFound.join(', ');\n\n // Hint-based search: targeted lookup for user-specified tools\n if (hint) {\n const terms = hint.split(/[\\s,]+/).filter(Boolean);\n const hintResults: string[] = [];\n for (const term of terms) {\n const safe = term.replace(/[^a-zA-Z0-9._-]/g, '');\n if (!safe) continue;\n // First try commandExists\n const cmdPath = commandExists(safe);\n if (cmdPath) {\n hintResults.push(`${term}: ${cmdPath}`);\n continue;\n }\n // Platform-specific fallback search\n let fallback = '';\n if (IS_WIN) {\n fallback = run(\n `Get-ChildItem -Path 'C:\\\\Program Files','C:\\\\Program Files (x86)','${HOME}\\\\AppData\\\\Local\\\\Programs' ` +\n `-Recurse -Depth 3 -Filter '*${safe}*' -ErrorAction SilentlyContinue | ` +\n `Select-Object -First 5 -ExpandProperty FullName`,\n { timeout: 10_000 },\n );\n } else if (IS_MAC) {\n fallback = run(`mdfind -name \"${safe}\" 2>/dev/null | head -5`);\n } else {\n fallback = run(`find /usr/bin /usr/local/bin /opt/homebrew/bin ~/.local/bin /Applications ~/Applications 2>/dev/null -iname \"*${safe}*\" -maxdepth 3 2>/dev/null | head -5`);\n }\n hintResults.push(fallback ? `${term}: ${fallback}` : `${term}: (not found)`);\n }\n results['HINT_SEARCH'] = hintResults.join('\\n');\n }\n\n const out = Object.entries(results)\n .map(([k, v]) => `=== ${k} ===\\n${v}`)\n .join('\\n\\n');\n\n return textResult(out);\n }, { annotations: READ_SCAN }),\n ];\n\n return tools;\n}\n\n/**\n * Build the in-process discovery tool definitions for the Claude SDK (with\n * read/write annotations). Wraps the neutral handlers from\n * `buildCartographyToolHandlers` through the SDK `tool()` factory, preserving the\n * exact behavior the Claude path has always had. Exposed for host-side gating and\n * structural tests; consumed by `createCartographyTools`.\n */\nexport async function buildCartographyToolDefinitions(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n) {\n // Dynamically import the SDK so a missing package doesn't crash at load time.\n const { tool } = await import('@anthropic-ai/claude-agent-sdk');\n const handlers = await buildCartographyToolHandlers(db, sessionId, opts);\n // The neutral handler signature (Record<string,unknown> → ToolResult) is structurally\n // compatible with the SDK's per-tool callback; the cast bridges the generic gap.\n type SdkHandler = Parameters<typeof tool>[3];\n return handlers.map((t) =>\n tool(t.name, t.description, t.inputShape, t.handler as unknown as SdkHandler, { annotations: t.annotations }),\n );\n}\n\nexport async function createCartographyTools(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n): Promise<McpServerConfig> {\n const { createSdkMcpServer } = await import('@anthropic-ai/claude-agent-sdk');\n const tools = await buildCartographyToolDefinitions(db, sessionId, opts);\n return createSdkMcpServer({ name: 'cartography', version: '0.1.0', tools });\n}\n","/**\n * Sanitization of untrusted text before it enters the catalog or an LLM context\n * window. Discovery ingests text from sources outside our control — browser\n * bookmark titles, command output, scanner reports — which can carry hidden\n * prompt-injection payloads using invisible Unicode (zero-width spaces,\n * bidi/format controls, soft hyphens) or stray control characters.\n *\n * `sanitizeUntrusted` strips those while preserving ordinary whitespace\n * (tab/0x09, line feed/0x0A, carriage return/0x0D) and NFC-normalizes the\n * result. It is a no-op for normal ASCII/printable text.\n *\n * The set of stripped code points is defined numerically (below) rather than as\n * a regex of literal invisible characters, so the source stays pure ASCII and\n * auditable.\n */\n\n// Inclusive code-point ranges to remove.\nconst STRIP_RANGES: ReadonlyArray<readonly [number, number]> = [\n // C0 controls except 0x09 (tab), 0x0A (LF), 0x0D (CR)\n [0x00, 0x08], [0x0b, 0x0c], [0x0e, 0x1f],\n [0x7f, 0x7f], // DEL\n [0x80, 0x9f], // C1 controls\n [0x00ad, 0x00ad], // soft hyphen\n [0x200b, 0x200f], // ZWSP, ZWNJ, ZWJ, LRM, RLM\n [0x202a, 0x202e], // bidi embeddings & overrides\n [0x2060, 0x2064], // word joiner, invisible math operators\n [0x2066, 0x2069], // bidi isolates\n [0x206a, 0x206f], // deprecated format characters\n [0xfeff, 0xfeff], // BOM / ZWNBSP\n];\n\nconst STRIP = new Set<number>();\nfor (const [start, end] of STRIP_RANGES) {\n for (let cp = start; cp <= end; cp++) STRIP.add(cp);\n}\n\n/** Strip invisible/control characters and NFC-normalize untrusted text. */\nexport function sanitizeUntrusted(text: string): string {\n if (!text) return text;\n let out = '';\n for (const ch of text.normalize('NFC')) {\n if (!STRIP.has(ch.codePointAt(0) as number)) out += ch;\n }\n return out;\n}\n\n/** Recursively apply `sanitizeUntrusted` to every string in an arbitrary value. */\nexport function sanitizeValue(value: unknown): unknown {\n if (typeof value === 'string') return sanitizeUntrusted(value);\n if (Array.isArray(value)) return value.map(sanitizeValue);\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = sanitizeValue(v);\n return out;\n }\n return value;\n}\n","import { tmpdir } from 'node:os';\nimport { existsSync, readFileSync, readdirSync, copyFileSync, statSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { HOME, IS_WIN, IS_MAC, browserBasePaths, firefoxBaseDirs } from './platform.js';\n\n/**\n * Remove orphaned temp files from previous bookmark/history scans.\n * Call at startup to prevent /tmp accumulation after crashes.\n */\nexport function cleanupTempFiles(): number {\n let cleaned = 0;\n const tmp = tmpdir();\n try {\n for (const f of readdirSync(tmp)) {\n if (f.startsWith('cartograph_') && f.endsWith('.sqlite')) {\n try {\n unlinkSync(join(tmp, f));\n cleaned++;\n } catch { /* file in use or already gone */ }\n }\n }\n } catch { /* tmpdir not readable */ }\n return cleaned;\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface BookmarkHost {\n hostname: string;\n port: number;\n protocol: 'http' | 'https';\n source: string;\n}\n\nexport interface HistoryHost extends BookmarkHost {\n visitCount: number;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nexport function extractHost(rawUrl: string, source: string): BookmarkHost | null {\n try {\n const u = new URL(rawUrl);\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;\n const protocol = u.protocol === 'https:' ? 'https' as const : 'http' as const;\n // Strip: no paths, no params, no credentials — hostname only\n const port = u.port ? parseInt(u.port, 10) : (protocol === 'https' ? 443 : 80);\n const hostname = u.hostname.toLowerCase();\n if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1') return null;\n return { hostname, port, protocol, source };\n } catch {\n return null;\n }\n}\n\n// Chrome/Edge/Brave JSON format\nexport interface ChromeNode {\n type?: string;\n url?: string;\n children?: ChromeNode[];\n}\n\nexport function walkChrome(node: ChromeNode, source: string, out: BookmarkHost[]): void {\n if (node.type === 'url' && node.url) {\n const h = extractHost(node.url, source);\n if (h) out.push(h);\n }\n if (node.children) {\n for (const child of node.children) walkChrome(child, source, out);\n }\n}\n\nexport function readChromeLike(filePath: string, source: string): BookmarkHost[] {\n if (!existsSync(filePath)) return [];\n try {\n const raw = JSON.parse(readFileSync(filePath, 'utf8')) as {\n roots: Record<string, ChromeNode>;\n };\n const out: BookmarkHost[] = [];\n for (const root of Object.values(raw.roots)) {\n if (root) walkChrome(root, source, out);\n }\n return out;\n } catch {\n return [];\n }\n}\n\nasync function queryBrowserDb<T>(srcPath: string, tmpPrefix: string, query: string): Promise<T[]> {\n if (!existsSync(srcPath)) return [];\n const tmp = join(tmpdir(), `cartograph_${tmpPrefix}_${Date.now()}.sqlite`);\n try {\n copyFileSync(srcPath, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n try {\n return db.prepare(query).all() as T[];\n } finally {\n db.close();\n }\n } catch {\n return [];\n } finally {\n try { unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\nexport async function readFirefoxBookmarks(profileDir: string): Promise<BookmarkHost[]> {\n const rows = await queryBrowserDb<{ url: string }>(\n join(profileDir, 'places.sqlite'), 'ff_bm',\n `SELECT DISTINCT p.url FROM moz_places p\n JOIN moz_bookmarks b ON b.fk = p.id\n WHERE b.type = 1 AND p.url NOT LIKE 'place:%' LIMIT 3000`,\n );\n return rows.map(r => extractHost(r.url, 'firefox')).filter((h): h is BookmarkHost => h !== null);\n}\n\nexport async function readFirefoxHistory(profileDir: string): Promise<HistoryHost[]> {\n const rows = await queryBrowserDb<{ url: string; visit_count: number }>(\n join(profileDir, 'places.sqlite'), 'ff_hist',\n `SELECT url, visit_count FROM moz_places\n WHERE url NOT LIKE 'place:%' AND visit_count > 0\n ORDER BY visit_count DESC LIMIT 5000`,\n );\n return rows\n .map(r => {\n const h = extractHost(r.url, 'firefox');\n return h ? { ...h, visitCount: r.visit_count } : null;\n })\n .filter((h): h is HistoryHost => h !== null);\n}\n\nexport async function readChromiumHistory(historyPath: string, source: string): Promise<HistoryHost[]> {\n const rows = await queryBrowserDb<{ url: string; visit_count: number }>(\n historyPath, 'ch_hist',\n `SELECT url, visit_count FROM urls\n WHERE hidden = 0 AND visit_count > 0\n ORDER BY visit_count DESC LIMIT 5000`,\n );\n return rows\n .map(r => {\n const h = extractHost(r.url, source);\n return h ? { ...h, visitCount: r.visit_count } : null;\n })\n .filter((h): h is HistoryHost => h !== null);\n}\n\n// ── Platform paths ────────────────────────────────────────────────────────────\n// Uses centralized platform.ts for Linux/macOS/Windows browser base paths.\n\nconst IS_LINUX = !IS_MAC && !IS_WIN;\n\n// Browser bookmark file paths (multiple profiles supported)\nexport function chromeLikePaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'Bookmarks');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n // Also check Profile 1, Profile 2, etc.\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'Bookmarks');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\nexport function chromeLikeHistoryPaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'History');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'History');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\n// Get browser bases from centralized platform module\nconst BROWSER_BASES = browserBasePaths();\n\nconst CHROME_BASE = BROWSER_BASES.chrome;\nconst CHROMIUM_BASE = BROWSER_BASES.chromium;\nconst EDGE_BASE = BROWSER_BASES.edge;\nconst BRAVE_BASE = BROWSER_BASES.brave;\nconst VIVALDI_BASE = BROWSER_BASES.vivaldi;\nconst OPERA_BASE = BROWSER_BASES.opera;\n\n// Snap / Flatpak variants (Linux only)\nconst CHROMIUM_SNAP_BASE = join(HOME, 'snap', 'chromium', 'common', 'chromium');\nconst CHROMIUM_FLATPAK_BASE = join(HOME, '.var', 'app', 'org.chromium.Chromium', 'config', 'chromium');\nconst CHROME_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.google.Chrome', 'config', 'google-chrome');\nconst BRAVE_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.brave.Browser', 'config', 'BraveSoftware', 'Brave-Browser');\nconst EDGE_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.microsoft.Edge', 'config', 'microsoft-edge');\n\nfunction firefoxProfileDirs(): string[] {\n const bases = firefoxBaseDirs();\n const dirs: string[] = [];\n for (const base of bases) {\n if (!existsSync(base)) continue;\n try {\n for (const d of readdirSync(base)) {\n const full = join(base, d);\n try {\n if (statSync(full).isDirectory() && existsSync(join(full, 'places.sqlite'))) {\n dirs.push(full);\n }\n } catch { /* ignore */ }\n }\n } catch { /* ignore */ }\n }\n return dirs;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport async function scanAllBookmarks(): Promise<BookmarkHost[]> {\n const all: BookmarkHost[] = [];\n\n // Standard browser paths\n for (const p of chromeLikePaths(CHROME_BASE)) all.push(...readChromeLike(p, 'chrome'));\n for (const p of chromeLikePaths(CHROMIUM_BASE)) all.push(...readChromeLike(p, 'chromium'));\n for (const p of chromeLikePaths(EDGE_BASE)) all.push(...readChromeLike(p, 'edge'));\n for (const p of chromeLikePaths(BRAVE_BASE)) all.push(...readChromeLike(p, 'brave'));\n for (const p of chromeLikePaths(VIVALDI_BASE)) all.push(...readChromeLike(p, 'vivaldi'));\n for (const p of chromeLikePaths(OPERA_BASE)) all.push(...readChromeLike(p, 'opera'));\n\n // Snap / Flatpak paths (Linux only — not macOS, not Windows)\n if (IS_LINUX) {\n for (const p of chromeLikePaths(CHROMIUM_SNAP_BASE)) all.push(...readChromeLike(p, 'chromium-snap'));\n for (const p of chromeLikePaths(CHROMIUM_FLATPAK_BASE)) all.push(...readChromeLike(p, 'chromium-flatpak'));\n for (const p of chromeLikePaths(CHROME_FLATPAK_BASE)) all.push(...readChromeLike(p, 'chrome-flatpak'));\n for (const p of chromeLikePaths(BRAVE_FLATPAK_BASE)) all.push(...readChromeLike(p, 'brave-flatpak'));\n for (const p of chromeLikePaths(EDGE_FLATPAK_BASE)) all.push(...readChromeLike(p, 'edge-flatpak'));\n }\n\n // Firefox: standard + snap + flatpak\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxBookmarks(dir));\n }\n\n // Deduplicate by hostname\n const seen = new Set<string>();\n return all.filter(h => {\n if (seen.has(h.hostname)) return false;\n seen.add(h.hostname);\n return true;\n });\n}\n\nexport async function scanAllHistory(): Promise<HistoryHost[]> {\n const all: HistoryHost[] = [];\n\n // Standard browser paths\n for (const p of chromeLikeHistoryPaths(CHROME_BASE)) all.push(...await readChromiumHistory(p, 'chrome'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_BASE)) all.push(...await readChromiumHistory(p, 'chromium'));\n for (const p of chromeLikeHistoryPaths(EDGE_BASE)) all.push(...await readChromiumHistory(p, 'edge'));\n for (const p of chromeLikeHistoryPaths(BRAVE_BASE)) all.push(...await readChromiumHistory(p, 'brave'));\n for (const p of chromeLikeHistoryPaths(VIVALDI_BASE)) all.push(...await readChromiumHistory(p, 'vivaldi'));\n for (const p of chromeLikeHistoryPaths(OPERA_BASE)) all.push(...await readChromiumHistory(p, 'opera'));\n\n // Snap / Flatpak paths (Linux only — not macOS, not Windows)\n if (IS_LINUX) {\n for (const p of chromeLikeHistoryPaths(CHROMIUM_SNAP_BASE)) all.push(...await readChromiumHistory(p, 'chromium-snap'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'chromium-flatpak'));\n for (const p of chromeLikeHistoryPaths(CHROME_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'chrome-flatpak'));\n for (const p of chromeLikeHistoryPaths(BRAVE_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'brave-flatpak'));\n for (const p of chromeLikeHistoryPaths(EDGE_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'edge-flatpak'));\n }\n\n // Firefox: standard + snap + flatpak\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxHistory(dir));\n }\n\n // Deduplicate by hostname, summing visit counts\n const byHost = new Map<string, HistoryHost>();\n for (const h of all) {\n const existing = byHost.get(h.hostname);\n if (existing) {\n existing.visitCount += h.visitCount;\n } else {\n byHost.set(h.hostname, { ...h });\n }\n }\n\n // Sort by visit count descending\n return [...byHost.values()].sort((a, b) => b.visitCount - a.visitCount);\n}\n","/**\n * Cross-platform utilities for Linux, macOS, and Windows.\n * Centralizes all OS-specific logic so scanning tools work everywhere.\n */\n\nimport { homedir, hostname as osHostname, userInfo } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { checkReadOnly } from './allowlist.js';\nimport { logWarn } from './logger.js';\n\n// ── Platform detection ───────────────────────────────────────────────────────\n\nexport type Platform = 'linux' | 'darwin' | 'win32';\n\nexport const PLATFORM: Platform = process.platform as Platform;\nexport const IS_WIN = PLATFORM === 'win32';\nexport const IS_MAC = PLATFORM === 'darwin';\nexport const IS_LINUX = PLATFORM === 'linux';\nexport const HOME = homedir();\n\n// ── Source attribution primitives (read-only; never leave the machine in 2.9) ──\n\n/** OS hostname, with a stable fallback so attribution is never empty. */\nexport function hostname(): string {\n try {\n const h = osHostname();\n return h && h.trim() ? h.trim() : 'unknown-host';\n } catch {\n return 'unknown-host';\n }\n}\n\n/** Current OS username, falling back to USER/USERNAME env, then a sentinel. */\nexport function osUser(): string {\n try {\n const u = userInfo().username;\n if (u && u.trim()) return u.trim();\n } catch {\n // fall through to env\n }\n return process.env.USER ?? process.env.USERNAME ?? 'unknown-user';\n}\n\n/** Process-lifetime memo for {@link machineId} (mirrors the `_shell` cache). */\nlet _machineId: string | undefined;\n\n/**\n * Stable, privacy-respecting per-install identifier: a random UUID v4 cached at\n * `~/.cartography/machine-id` (mode 0600). No hardware fingerprinting, no command\n * execution — allowlist-safe. If the file cannot be read or written (read-only\n * home, missing HOME in CI), it degrades to a process-stable ephemeral UUID with a\n * warning (optional-deps-degrade house style) rather than throwing. The id never\n * leaves the machine in 2.9; it is opaque and non-identifying.\n */\nexport function machineId(): string {\n if (_machineId) return _machineId;\n const dir = join(HOME, '.cartography');\n const file = join(dir, 'machine-id');\n try {\n if (existsSync(file)) {\n const v = readFileSync(file, 'utf8').trim();\n if (v) return (_machineId = v);\n }\n mkdirSync(dir, { recursive: true });\n const id = randomUUID();\n writeFileSync(file, id, { mode: 0o600 });\n return (_machineId = id);\n } catch (err) {\n logWarn(`machineId not persisted (using ephemeral id): ${err instanceof Error ? err.message : String(err)}`);\n return (_machineId = randomUUID());\n }\n}\n\n// ── Shell selection ──────────────────────────────────────────────────────────\n\n/**\n * Returns the correct shell for execSync on each platform.\n * - Windows: PowerShell (pwsh if available, otherwise powershell.exe)\n * - macOS/Linux: /bin/sh\n */\nexport function platformShell(): string {\n if (!IS_WIN) return '/bin/sh';\n // Prefer pwsh (PowerShell 7+) over powershell.exe (5.1)\n try {\n execSync('pwsh -Version', { stdio: 'pipe', timeout: 3000 });\n return 'pwsh';\n } catch {\n return 'powershell.exe';\n }\n}\n\n/** Cached shell value (computed once) */\nlet _shell: string | undefined;\nexport function getShell(): string {\n if (!_shell) _shell = platformShell();\n return _shell;\n}\n\n// ── Cross-platform command runner ────────────────────────────────────────────\n\nexport interface RunOptions {\n timeout?: number;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Run a shell command, returning stdout as string. Returns '' on error.\n * Automatically uses the correct shell for the platform.\n */\n/** Safe environment variables — excludes secrets from child processes */\nconst SAFE_ENV_KEYS = [\n 'PATH', 'HOME', 'USER', 'LANG', 'LC_ALL', 'TERM', 'SHELL',\n 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA', 'PROGRAMFILES',\n 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_RUNTIME_DIR',\n 'AWS_DEFAULT_REGION', 'AWS_PROFILE', 'AWS_CONFIG_FILE',\n 'KUBECONFIG', 'GOOGLE_APPLICATION_CREDENTIALS',\n 'AZURE_CONFIG_DIR',\n];\n\nexport function safeEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const key of SAFE_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key];\n }\n return env;\n}\n\nexport function run(cmd: string, opts: RunOptions = {}): string {\n // Defense in depth: never spawn a command that is not on the read-only allowlist,\n // regardless of where it originated (scanner template, agent, or MCP tool).\n const policy = checkReadOnly(cmd, { shell: IS_WIN ? 'powershell' : 'posix' });\n if (!policy.allowed) {\n logWarn(`Blocked non-read-only command: ${policy.reason}`);\n return '';\n }\n try {\n return execSync(cmd, {\n stdio: 'pipe',\n timeout: opts.timeout ?? 10_000,\n shell: getShell(),\n env: opts.env ?? safeEnv(),\n }).toString().trim();\n } catch {\n return '';\n }\n}\n\n// ── Command existence check (cross-platform `which`) ─────────────────────────\n\n/**\n * Check if a command exists. Returns its path or '' if not found.\n * - Unix: `which <cmd>`\n * - Windows: `Get-Command <cmd>` via PowerShell\n */\nexport function commandExists(cmd: string): string {\n if (IS_WIN) {\n const r = run(`Get-Command ${cmd} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source`, { timeout: 5000 });\n return r;\n }\n return run(`which ${cmd} 2>/dev/null`);\n}\n\n// ── Null device ──────────────────────────────────────────────────────────────\n\nexport const NULL_DEV = IS_WIN ? 'NUL' : '/dev/null';\n\n// ── App data directories ─────────────────────────────────────────────────────\n\n/** Returns the platform-specific user app data directory */\nexport function appDataDir(): string {\n if (IS_WIN) return process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n if (IS_MAC) return join(HOME, 'Library', 'Application Support');\n return process.env.XDG_CONFIG_HOME ?? join(HOME, '.config');\n}\n\n/** Returns the platform-specific user data directory (broader than config) */\nexport function userDataDir(): string {\n if (IS_WIN) return process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n if (IS_MAC) return join(HOME, 'Library', 'Application Support');\n return process.env.XDG_DATA_HOME ?? join(HOME, '.local', 'share');\n}\n\n// ── Browser profile base paths (Chromium-based) ──────────────────────────────\n\nexport interface BrowserPaths {\n chrome: string;\n chromium: string;\n edge: string;\n brave: string;\n vivaldi: string;\n opera: string;\n}\n\nexport function browserBasePaths(): BrowserPaths {\n if (IS_WIN) {\n const local = process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n return {\n chrome: join(local, 'Google', 'Chrome', 'User Data'),\n chromium: join(local, 'Chromium', 'User Data'),\n edge: join(local, 'Microsoft', 'Edge', 'User Data'),\n brave: join(local, 'BraveSoftware', 'Brave-Browser', 'User Data'),\n vivaldi: join(local, 'Vivaldi', 'User Data'),\n opera: join(userDataDir(), 'Opera Software', 'Opera Stable'),\n };\n }\n if (IS_MAC) {\n const lib = join(HOME, 'Library', 'Application Support');\n return {\n chrome: join(lib, 'Google', 'Chrome'),\n chromium: join(lib, 'Chromium'),\n edge: join(lib, 'Microsoft Edge'),\n brave: join(lib, 'BraveSoftware', 'Brave-Browser'),\n vivaldi: join(lib, 'Vivaldi'),\n opera: join(lib, 'com.operasoftware.Opera'),\n };\n }\n // Linux\n return {\n chrome: join(HOME, '.config', 'google-chrome'),\n chromium: join(HOME, '.config', 'chromium'),\n edge: join(HOME, '.config', 'microsoft-edge'),\n brave: join(HOME, '.config', 'BraveSoftware', 'Brave-Browser'),\n vivaldi: join(HOME, '.config', 'vivaldi'),\n opera: join(HOME, '.config', 'opera'),\n };\n}\n\n/** Firefox profile parent directories per platform */\nexport function firefoxBaseDirs(): string[] {\n if (IS_WIN) {\n const roaming = process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n return [join(roaming, 'Mozilla', 'Firefox', 'Profiles')];\n }\n if (IS_MAC) {\n return [join(HOME, 'Library', 'Application Support', 'Firefox', 'Profiles')];\n }\n // Linux: standard + snap + flatpak\n return [\n join(HOME, '.mozilla', 'firefox'),\n join(HOME, 'snap', 'firefox', 'common', '.mozilla', 'firefox'),\n join(HOME, '.var', 'app', 'org.mozilla.firefox', '.mozilla', 'firefox'),\n ];\n}\n\n// ── Database scan directories ────────────────────────────────────────────────\n\n/** Returns directories to search for SQLite/DB files per platform */\nexport function dbScanDirs(): string[] {\n const dirs: string[] = [];\n if (IS_WIN) {\n const local = process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n const roaming = process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n dirs.push(local, roaming);\n const pd = join(HOME, 'AppData', 'Local', 'Programs');\n if (existsSync(pd)) dirs.push(pd);\n } else if (IS_MAC) {\n dirs.push(join(HOME, 'Library', 'Application Support'));\n if (existsSync('/var/lib')) dirs.push('/var/lib');\n } else {\n const configDir = join(HOME, '.config');\n const dataDir = join(HOME, '.local', 'share');\n if (existsSync(configDir)) dirs.push(configDir);\n if (existsSync(dataDir)) dirs.push(dataDir);\n if (existsSync('/var/lib')) dirs.push('/var/lib');\n }\n return dirs.filter(d => existsSync(d));\n}\n\n// ── File search (cross-platform find) ────────────────────────────────────────\n\n/**\n * Search for files matching glob patterns in given directories.\n * - Unix: `find` command\n * - Windows: PowerShell `Get-ChildItem`\n */\nexport function findFiles(dirs: string[], patterns: string[], maxDepth: number, limit: number): string {\n if (dirs.length === 0) return '';\n if (IS_WIN) {\n const includes = patterns.map(p => `'${p}'`).join(',');\n const pathList = dirs.map(d => `'${d}'`).join(',');\n return run(\n `Get-ChildItem -Path ${pathList} -Recurse -Depth ${maxDepth} -Include ${includes} -ErrorAction SilentlyContinue | Select-Object -First ${limit} -ExpandProperty FullName`,\n { timeout: 15_000 },\n );\n }\n const nameArgs = patterns.map(p => `-name \"${p}\"`).join(' -o ');\n const findCmds = dirs.map(d => `find \"${d}\" -maxdepth ${maxDepth} \\\\( ${nameArgs} \\\\) 2>/dev/null`).join('; ');\n return run(`{ ${findCmds}; } | head -${limit}`, { timeout: 15_000 });\n}\n\n// ── Network scanning ─────────────────────────────────────────────────────────\n\n/** Get all listening TCP ports and the processes behind them */\nexport function scanListeningPorts(): string {\n if (IS_WIN) {\n // PowerShell: Get-NetTCPConnection for listening ports + owning process\n return run(\n `Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | ` +\n `ForEach-Object { $p = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue; ` +\n `\"$($_.LocalAddress):$($_.LocalPort) PID=$($_.OwningProcess) $($p.ProcessName)\" } | ` +\n `Sort-Object -Unique`,\n { timeout: 15_000 },\n );\n }\n if (IS_MAC) {\n // macOS: lsof is the most reliable way (ss not available)\n return run('sudo lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null || lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null', { timeout: 15_000 });\n }\n // Linux: ss is the standard tool\n return run('ss -tlnp 2>/dev/null', { timeout: 10_000 });\n}\n\n/**\n * Get all established TCP connections (local→remote) read-only, cross-platform.\n * Mirrors {@link scanListeningPorts}; used by the connections scanner (3.2) to infer\n * `connects_to` edges. Every command is in the read-only allowlist (`ss`/`lsof`/\n * `Get-NetTCPConnection`) and `run()` re-validates before executing.\n */\nexport function scanEstablishedConnections(): string {\n if (IS_WIN) {\n return run(\n `Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue | ` +\n `ForEach-Object { \"$($_.LocalAddress):$($_.LocalPort) -> $($_.RemoteAddress):$($_.RemotePort)\" } | ` +\n `Sort-Object -Unique`,\n { timeout: 15_000 },\n );\n }\n if (IS_MAC) {\n return run('lsof -nP -iTCP -sTCP:ESTABLISHED 2>/dev/null', { timeout: 15_000 });\n }\n return run('ss -tnp state established 2>/dev/null', { timeout: 10_000 });\n}\n\n/** Get running processes (cross-platform) */\nexport function scanProcesses(): string {\n if (IS_WIN) {\n return run(\n `Get-Process | Select-Object -Property Id, ProcessName, Path | Format-Table -AutoSize | Out-String -Width 200`,\n { timeout: 15_000 },\n );\n }\n return run('ps aux 2>/dev/null', { timeout: 10_000 });\n}\n\n// ── Windows-specific: installed programs ─────────────────────────────────────\n\n/** Scan Windows registry for installed programs */\nexport function scanWindowsPrograms(): string {\n if (!IS_WIN) return '';\n return run(\n `$paths = @(` +\n `'HKLM:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*',` +\n `'HKLM:\\\\Software\\\\Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*',` +\n `'HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*'` +\n `); Get-ItemProperty $paths -ErrorAction SilentlyContinue | ` +\n `Where-Object { $_.DisplayName } | ` +\n `Select-Object -Property DisplayName, Publisher, DisplayVersion | ` +\n `Sort-Object DisplayName | ` +\n `Format-Table -AutoSize | Out-String -Width 300`,\n { timeout: 20_000 },\n );\n}\n\n/** Scan Windows services for database engines */\nexport function scanWindowsDbServices(): string {\n if (!IS_WIN) return '';\n return run(\n `Get-Service | Where-Object { ` +\n `$_.Name -match 'postgres|mysql|mariadb|mongo|redis|MSSQL|elastic|clickhouse|cassandra' ` +\n `} | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize`,\n { timeout: 10_000 },\n );\n}\n\n// ── file:// URL helper ───────────────────────────────────────────────────────\n\n/** Generate a correct file:// URL for the current platform */\nexport function fileUrl(absPath: string): string {\n if (IS_WIN) {\n // Windows: file:///C:/Users/... (forward slashes, triple slash)\n const normalized = absPath.replace(/\\\\/g, '/');\n return `file:///${normalized}`;\n }\n return `file://${absPath}`;\n}\n","/**\n * Read-only command policy — a strict allowlist.\n *\n * Unlike a denylist (which is inherently leaky — novel destructive commands slip\n * through), this module permits only commands that are known to be read-only and\n * rejects everything else. It is the authoritative safety boundary for every\n * command the package spawns, independent of which agent or LLM is driving.\n *\n * The check is shell-aware: it splits a command line into segments on the control\n * operators `|`, `&&`, `||`, `;` (respecting single/double quotes), then validates\n * the leading executable of each segment plus its sub-command/arguments.\n */\n\n/** Plain read-only executables — no sub-command restriction needed. */\nconst READONLY_BINARIES = new Set<string>([\n // shell & text utilities\n 'echo', 'printf', 'true', 'false', 'test', 'cat', 'head', 'tail', 'grep', 'egrep',\n 'fgrep', 'awk', 'sed', 'cut', 'sort', 'uniq', 'wc', 'tr', 'xargs', 'tee',\n 'ls', 'find', 'which', 'command', 'type', 'basename', 'dirname', 'realpath',\n 'readlink', 'stat', 'file', 'printenv', 'date', 'hostname', 'uname',\n 'whoami', 'id', 'pwd', 'expr', 'seq', 'tac', 'rev', 'column', 'paste',\n // network & process inspection (read-only)\n 'ss', 'netstat', 'lsof', 'ps', 'ip', 'ifconfig', 'arp', 'dig', 'nslookup', 'host',\n // database clients (read-only usage is enforced separately for risky verbs)\n 'psql', 'mysql', 'mysqladmin', 'mongosh', 'redis-cli', 'sqlite3', 'pg_lsclusters',\n 'clickhouse-client',\n // macOS\n 'mdfind',\n]);\n\n/** `tee` is read-only only when writing to the bit bucket; otherwise it writes files. */\nconst CONDITIONAL_BINARIES = new Set<string>(['tee']);\n\n/** Package managers: list/query only — reject install/remove/upgrade-style verbs and flags. */\nconst PKG_MANAGERS = new Set<string>(['dpkg', 'rpm', 'snap', 'flatpak', 'brew', 'winget', 'choco', 'scoop', 'apt-cache']);\nconst MUTATING_PKG = /^(install|uninstall|reinstall|remove|purge|erase|upgrade|update|add|delete|pin|enable|disable|-i|--install|-r|--remove|-P|--purge|-e|--erase|-U|--upgrade|-F|--freshen)$/i;\n\n/** Executables that run another command supplied as an argument — must be validated recursively. */\nconst COMMAND_RUNNERS = new Set<string>(['xargs', 'env', 'nice', 'nohup', 'timeout', 'time', 'stdbuf', 'watch', 'sudo']);\n\n/** Mutating PowerShell cmdlets and Windows commands — rejected in PowerShell mode. */\nconst DANGEROUS_PS = /\\b(Remove-Item|Remove-ItemProperty|Move-Item|Copy-Item|Rename-Item|New-Item|New-Service|Set-Content|Add-Content|Clear-Content|Out-File|Set-ItemProperty|Set-Service|Stop-Process|Stop-Service|Start-Service|Restart-Service|Stop-Computer|Restart-Computer|Format-Volume|Clear-Disk|Remove-\\w+|Uninstall-\\w+|Install-\\w+|Set-\\w+|New-\\w+|Start-\\w+|Stop-\\w+|Restart-\\w+|Invoke-Expression|iex|Invoke-WebRequest|Invoke-RestMethod|Invoke-Command|Start-Process|Register-\\w+|Unregister-\\w+|Disable-\\w+|Enable-\\w+|Reset-\\w+|del|rmdir|rd)\\b/i;\n\n/** Coarse Unix destructive denylist — defense-in-depth backstop. */\nconst DANGEROUS_POSIX = /\\b(rm|rmdir|mv|dd|mkfs|chmod|chown|chgrp|kill|killall|pkill|reboot|shutdown|poweroff|halt|truncate|shred|fdisk|parted)\\b/i;\n\n/**\n * Multi-verb tools: the first non-flag token after the binary (and, for some, the\n * whole token list) must satisfy a read-only predicate.\n */\nconst SUBCOMMAND_RULES: Record<string, (tokens: string[]) => boolean> = {\n kubectl: (t) => allowFirstVerb(t, ['get', 'describe', 'top', 'logs', 'explain', 'config', 'version', 'cluster-info', 'api-resources', 'api-versions', 'auth']),\n docker: (t) => allowFirstVerb(t, ['ps', 'images', 'inspect', 'version', 'info', 'logs', 'stats', 'top', 'port', 'history', 'diff', 'system', 'context', 'volume', 'network', 'image', 'container']) && !hasMutatingDockerVerb(t),\n podman: (t) => SUBCOMMAND_RULES['docker']!(t),\n helm: (t) => allowFirstVerb(t, ['list', 'ls', 'status', 'get', 'show', 'history', 'version', 'repo', 'search', 'env']),\n systemctl: (t) => allowFirstVerb(t, ['status', 'show', 'list-units', 'list-unit-files', 'list-sockets', 'list-timers', 'list-dependencies', 'is-active', 'is-enabled', 'is-failed', 'cat', 'get-default', 'show-environment']),\n service: (t) => t.some((x) => /^status$/i.test(x)),\n // cloud CLIs: read-only actions only — must contain a read verb, never a mutating one\n aws: (t) => containsAwsReadAction(t) && !hasMutatingCloudVerb(t),\n gcloud: (t) => (hasToken(t, ['list', 'describe']) || isInfoOnly(t)) && !hasMutatingCloudVerb(gcloudActionTokens(t)),\n az: (t) => (hasToken(t, ['list', 'show']) || isInfoOnly(t)) && !hasMutatingCloudVerb(t),\n // version control (read-only verbs only)\n git: (t) => allowFirstVerb(t, ['status', 'log', 'show', 'diff', 'branch', 'remote', 'config', 'rev-parse', 'ls-files', 'ls-remote', 'describe', 'tag', 'shortlog', 'cat-file', 'symbolic-ref']),\n gh: (t) => allowFirstVerb(t, ['repo', 'pr', 'issue', 'release', 'api', 'auth', 'status']) && hasToken(t, ['list', 'view', 'status', 'get']),\n};\n\n/** curl/wget: GET-only, no file writes, no request methods or bodies. */\nconst FETCH_RULES: Record<string, (tokens: string[]) => boolean> = {\n curl: (t) => !t.some((x) => /^-X$/i.test(x) || /^--request$/i.test(x) || /^-[dF]$/.test(x) || /^--data/i.test(x) || /^--form$/i.test(x) || /^-[oO]$/.test(x) || /^--output$/i.test(x) || /^--upload-file$/i.test(x)),\n wget: (t) => !t.some((x) => /^-O$/.test(x) || /^--output-document/i.test(x) || /^--post-data/i.test(x) || /^--method/i.test(x) || /^-i$/.test(x)),\n};\n\n/** Read-only PowerShell verbs (cmdlets are `Verb-Noun`). */\nconst READONLY_PS_VERBS = new Set<string>([\n 'get', 'select', 'where', 'measure', 'sort', 'format', 'out', 'convertto',\n 'convertfrom', 'compare', 'test', 'resolve', 'split', 'join', 'group', 'foreach',\n 'write', 'read', 'show', 'find', 'search', 'tee',\n]);\n\n/** Bare PowerShell helpers / aliases that are read-only. */\nconst READONLY_PS_BARE = new Set<string>(['where', 'select', 'sort', 'foreach', 'ft', 'fl', 'gci', 'gc', 'gm', 'gps', 'gsv', 'echo', 'write-host', 'write-output']);\n\nexport interface PolicyResult {\n allowed: boolean;\n reason?: string;\n}\n\nexport type ShellKind = 'posix' | 'powershell';\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction allowFirstVerb(tokens: string[], verbs: string[]): boolean {\n const verb = tokens.find((t) => !t.startsWith('-'));\n return verb !== undefined && verbs.includes(verb.toLowerCase());\n}\n\nfunction hasToken(tokens: string[], any: string[]): boolean {\n const lower = tokens.map((t) => t.toLowerCase());\n return any.some((a) => lower.includes(a));\n}\n\nfunction isInfoOnly(tokens: string[]): boolean {\n // e.g. `gcloud config list ...`, `az account show`\n return hasToken(tokens, ['config', 'account', 'version', 'info']);\n}\n\nconst MUTATING_CLOUD = /^(create|delete|update|put|set|add|remove|deploy|run|start|stop|restart|reboot|terminate|modify|attach|detach|associate|disassociate|enable|disable|invoke|exec|apply|destroy|scale|patch|register|deregister|import|copy|move|rename|reset|rotate|revoke|grant)([-_].*)?$/i;\n\nfunction hasMutatingCloudVerb(tokens: string[]): boolean {\n return tokens.some((t) => !t.startsWith('-') && MUTATING_CLOUD.test(t));\n}\n\n/**\n * gcloud service *groups* whose name collides with a mutating-verb token but is\n * a read-only noun in `gcloud <group> ... list/describe` (e.g. `gcloud run\n * services list`). We drop the leading service-group noun before the mutating-verb\n * check so a legitimate read is not blocked, while any *trailing* mutating action\n * (`gcloud run deploy …`) is still caught — those commands carry no `list`/`describe`\n * read verb and so fail the gcloud read-verb gate regardless.\n */\nconst GCLOUD_GROUP_NOUNS = new Set<string>(['run', 'import', 'reset', 'scan']);\n\n/** Drop a single leading gcloud service-group noun (the token right after `gcloud`). */\nfunction gcloudActionTokens(tokens: string[]): string[] {\n const firstPositional = tokens.findIndex((t) => !t.startsWith('-'));\n if (firstPositional !== -1 && GCLOUD_GROUP_NOUNS.has(tokens[firstPositional]!.toLowerCase())) {\n return [...tokens.slice(0, firstPositional), ...tokens.slice(firstPositional + 1)];\n }\n return tokens;\n}\n\nfunction containsAwsReadAction(tokens: string[]): boolean {\n // aws <service> <action> — action must be read-only\n return tokens.some((t) => /^(describe|list|get|lookup|search|scan|view|ls)[-_a-z0-9]*$/i.test(t) || t.toLowerCase() === 'ls');\n}\n\nfunction hasMutatingDockerVerb(tokens: string[]): boolean {\n return tokens.some((t) => /^(run|rm|rmi|exec|build|push|pull|start|stop|kill|create|commit|cp|save|load|tag|login|logout|prune|kill|restart|pause|unpause|rename|update|export|import)$/i.test(t));\n}\n\n/** Split a command line on shell control operators, honoring single/double quotes. */\nexport function splitSegments(cmd: string): string[] {\n const segments: string[] = [];\n let buf = '';\n let quote: '\"' | \"'\" | null = null;\n for (let i = 0; i < cmd.length; i++) {\n const c = cmd[i]!;\n const next = cmd[i + 1];\n if (quote) {\n buf += c;\n if (c === quote) quote = null;\n continue;\n }\n if (c === '\"' || c === \"'\") { quote = c; buf += c; continue; }\n // command substitution is rejected wholesale elsewhere; treat `$(` as a break too\n if ((c === '|' && next === '|') || (c === '&' && next === '&')) { segments.push(buf); buf = ''; i++; continue; }\n if (c === '|' || c === ';' || c === '\\n') { segments.push(buf); buf = ''; continue; }\n buf += c;\n }\n segments.push(buf);\n return segments.map((s) => s.trim()).filter(Boolean);\n}\n\n/** Tokenize one segment into words, honoring quotes and stripping them. */\nfunction tokenize(segment: string): string[] {\n const tokens: string[] = [];\n let buf = '';\n let quote: '\"' | \"'\" | null = null;\n let started = false;\n const push = () => { if (started) { tokens.push(buf); buf = ''; started = false; } };\n for (let i = 0; i < segment.length; i++) {\n const c = segment[i]!;\n if (quote) { if (c === quote) quote = null; else buf += c; started = true; continue; }\n if (c === '\"' || c === \"'\") { quote = c; started = true; continue; }\n if (c === ' ' || c === '\\t') { push(); continue; }\n buf += c; started = true;\n }\n push();\n return tokens;\n}\n\nfunction baseName(executable: string): string {\n const noPath = executable.split(/[\\\\/]/).pop() ?? executable;\n return noPath.toLowerCase();\n}\n\n/** `find` is read-only unless it is asked to execute or delete. */\nfunction findIsReadOnly(rest: string[]): boolean {\n return !rest.some((t) => /^-(exec|execdir|ok|okdir|delete|fprintf|fprint|fls)$/i.test(t));\n}\n\n/** Guard `awk`/`sed` programs against shelling out. */\nfunction awkSedIsReadOnly(exe: string, rest: string[]): boolean {\n const program = rest.join(' ');\n if (exe === 'awk') return !/\\bsystem\\s*\\(/.test(program) && !/\\|\\s*[\"']/.test(program) && !/print\\s*>/.test(program);\n // sed: reject the `e` (execute) command and the s///e flag and `w` (write file)\n return !/(^|;|\\{|\\s)e\\b/.test(program) && !/s[^\\s]*\\/[a-z]*e[a-z]*\\b/i.test(program) && !/\\bw\\s+\\S/.test(program);\n}\n\nfunction isWriteRedirect(segment: string): boolean {\n // Allow only redirects to the bit bucket / stderr merge: 2>/dev/null, >/dev/null, 2>&1, *> $null, Out-Null\n // Reject any other `>` or `>>` (file writes).\n const stripped = segment\n .replace(/\\d?>>?\\s*\\/dev\\/null/g, '')\n .replace(/\\d?>\\s*&\\s*\\d/g, '')\n .replace(/\\d?>\\s*\\$null/gi, '');\n return /(^|[^0-9&])>>?/.test(stripped);\n}\n\n// ── Public API ─────────────────────────────────────────────────────────────────\n\n/**\n * Decide whether a command line is read-only and therefore safe to execute.\n * Returns `{ allowed: false, reason }` for anything not explicitly permitted.\n */\nexport function checkReadOnly(command: string, opts: { shell?: ShellKind } = {}): PolicyResult {\n const cmd = command.trim();\n if (!cmd) return { allowed: false, reason: 'empty command' };\n\n // PowerShell uses `$(...)`, `;`-in-blocks and `{}` legitimately, so a POSIX parser\n // would mis-fire. In PowerShell mode we reject file writes and mutating cmdlets instead.\n if (opts.shell === 'powershell') {\n if (isWriteRedirect(cmd)) return { allowed: false, reason: 'file-writing redirect is not allowed' };\n if (DANGEROUS_PS.test(cmd)) return { allowed: false, reason: 'mutating PowerShell cmdlet is not allowed' };\n if (DANGEROUS_POSIX.test(cmd)) return { allowed: false, reason: 'destructive command is not allowed' };\n return { allowed: true };\n }\n\n // Reject command substitution and backticks — they hide arbitrary execution.\n if (/\\$\\(|`/.test(cmd)) return { allowed: false, reason: 'command substitution is not allowed' };\n\n // Reject file-writing redirects (anything other than /dev/null or stderr merge).\n if (isWriteRedirect(cmd)) return { allowed: false, reason: 'file-writing redirect is not allowed' };\n\n for (const segment of splitSegments(cmd)) {\n const r = checkSegment(segment);\n if (!r.allowed) return r;\n }\n\n return { allowed: true };\n}\n\n/** Validate a single pipeline segment's leading executable and its arguments. */\nfunction checkSegment(segment: string): PolicyResult {\n // Drop leading inline env assignments (`FOO=bar cmd`) and shell grouping tokens (`{ } ( )`).\n let tokens = tokenize(segment)\n .filter((t) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(t))\n .filter((t) => t !== '{' && t !== '}' && t !== '(' && t !== ')');\n if (tokens.length === 0) return { allowed: true };\n\n let exe = baseName(tokens[0]!);\n let rest = tokens.slice(1);\n\n // Command runners (xargs, timeout, nice, env, ...): unwrap to the inner command and validate it.\n while (COMMAND_RUNNERS.has(exe)) {\n // skip the runner's own flags and their values, plus xargs replace-string (-I {})\n const inner: string[] = [];\n let i = 0;\n for (; i < rest.length; i++) {\n const t = rest[i]!;\n if (t.startsWith('-')) { if (/^-(I|n|L|P|d|s|E|u|g)$/.test(t)) i++; continue; }\n inner.push(...rest.slice(i));\n break;\n }\n if (inner.length === 0) return { allowed: true }; // runner with no inner command (e.g. `env`, `xargs echo`-less)\n exe = baseName(inner[0]!);\n rest = inner.slice(1);\n }\n\n if (exe === 'find') {\n if (!findIsReadOnly(rest)) return { allowed: false, reason: 'find: -exec/-delete is not allowed' };\n return { allowed: true };\n }\n if (exe === 'awk' || exe === 'sed') {\n if (!awkSedIsReadOnly(exe, rest)) return { allowed: false, reason: `${exe}: program may not shell out or write files` };\n return { allowed: true };\n }\n if (PKG_MANAGERS.has(exe)) {\n if (rest.some((t) => MUTATING_PKG.test(t))) return { allowed: false, reason: `${exe}: only list/query sub-commands are allowed` };\n return { allowed: true };\n }\n if (FETCH_RULES[exe]) {\n if (!FETCH_RULES[exe]!(rest)) return { allowed: false, reason: `${exe}: only read-only GET requests are allowed` };\n return { allowed: true };\n }\n if (SUBCOMMAND_RULES[exe]) {\n if (!SUBCOMMAND_RULES[exe]!(rest)) return { allowed: false, reason: `${exe}: sub-command is not read-only` };\n return { allowed: true };\n }\n if (CONDITIONAL_BINARIES.has(exe)) {\n if (rest.some((t) => !t.startsWith('-') && t !== '/dev/null')) return { allowed: false, reason: 'tee may only write to /dev/null' };\n return { allowed: true };\n }\n if (READONLY_BINARIES.has(exe)) return { allowed: true };\n if (READONLY_PS_BARE.has(exe)) return { allowed: true };\n\n // PowerShell cmdlet fallback: Verb-Noun where the verb must be read-only.\n if (exe.includes('-') && /^[a-z]+-[a-z]/.test(exe)) {\n const verb = exe.split('-')[0]!;\n if (READONLY_PS_VERBS.has(verb)) return { allowed: true };\n return { allowed: false, reason: `PowerShell cmdlet not read-only: ${exe}` };\n }\n\n return { allowed: false, reason: `command not on read-only allowlist: ${exe}` };\n}\n\n/** Convenience boolean form. */\nexport function isReadOnlyCommand(command: string): boolean {\n return checkReadOnly(command).allowed;\n}\n\n/** Throwing form for guard sites that prefer exceptions. */\nexport function assertReadOnly(command: string): void {\n const r = checkReadOnly(command);\n if (!r.allowed) throw new Error(`Blocked by read-only allowlist: ${r.reason}`);\n}\n","/**\n * Structured logging for enterprise observability.\n * Outputs JSON to stderr for compatibility with ELK, Datadog, Splunk, CloudWatch.\n */\n\nexport type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n}\n\nlet verboseMode = false;\n\nexport function setVerbose(v: boolean): void {\n verboseMode = v;\n}\n\nexport function log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n if (level === 'DEBUG' && !verboseMode) return;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n ...(context && Object.keys(context).length > 0 ? { context } : {}),\n };\n process.stderr.write(JSON.stringify(entry) + '\\n');\n}\n\nexport function logDebug(message: string, context?: Record<string, unknown>): void {\n log('DEBUG', message, context);\n}\n\nexport function logInfo(message: string, context?: Record<string, unknown>): void {\n log('INFO', message, context);\n}\n\nexport function logWarn(message: string, context?: Record<string, unknown>): void {\n log('WARN', message, context);\n}\n\nexport function logError(message: string, context?: Record<string, unknown>): void {\n log('ERROR', message, context);\n}\n","/**\n * Shared helpers for the cloud / k8s / database scanners.\n *\n * These keep the provider scanners (`cloud-aws`, `cloud-gcp`, `cloud-azure`,\n * `k8s`, `databases`) small and consistent: JSON parsing that never throws,\n * provider-parameter extraction from the single `ScanContext.hint` channel\n * (validated against the same strict patterns the agent tools use), and the\n * `=== KEY ===` report formatter so the structured + raw-report output shape\n * stays identical to the legacy agent-tool reports.\n */\n\nimport { assertSafeScanArg, type ScanArgKind } from '../tools.js';\n\n/**\n * Parse JSON CLI output. Returns `undefined` for empty output, our sentinel\n * placeholders (`(error or not available)`, `(skipped …)`), or malformed JSON —\n * it never throws, so a scanner degrades to \"no nodes\" instead of crashing the\n * discovery run.\n */\nexport function safeJson<T = unknown>(raw: string): T | undefined {\n const s = raw.trim();\n if (!s || s.startsWith('(')) return undefined;\n try {\n return JSON.parse(s) as T;\n } catch {\n return undefined;\n }\n}\n\n/** Provider parameters parsed out of a {@link ScanContext.hint}. */\nexport interface ScanHintParams {\n namespace?: string;\n region?: string;\n profile?: string;\n project?: string;\n subscription?: string;\n resourceGroup?: string;\n /** Leftover non-`key=value` terms (back-compat with the installed-apps hint use). */\n free: string;\n}\n\n/** Recognised `key=value` hint keys → the strict {@link ScanArgKind} that validates them. */\nconst HINT_ARG_KINDS: Record<string, ScanArgKind> = {\n namespace: 'k8s-namespace',\n region: 'aws-region',\n profile: 'aws-profile',\n project: 'gcp-project',\n subscription: 'azure-subscription',\n 'resource-group': 'azure-resource-group',\n};\n\n/**\n * Parse `key=value` provider parameters from a `ScanContext.hint`, validating\n * each value against its strict pattern via {@link assertSafeScanArg} (throws on\n * an injection payload — even a read-only one the allowlist would otherwise\n * permit). Unknown tokens flow through to {@link ScanHintParams.free} unchanged.\n */\nexport function parseScanHint(hint: string | undefined): ScanHintParams {\n const out: ScanHintParams = { free: '' };\n const free: string[] = [];\n for (const tok of (hint ?? '').split(/[\\s,]+/).filter(Boolean)) {\n const eq = tok.indexOf('=');\n if (eq <= 0) {\n free.push(tok);\n continue;\n }\n const key = tok.slice(0, eq);\n const value = tok.slice(eq + 1);\n const kind = HINT_ARG_KINDS[key];\n if (!kind) {\n free.push(tok);\n continue;\n }\n assertSafeScanArg(kind, value);\n if (key === 'resource-group') out.resourceGroup = value;\n else if (key === 'namespace') out.namespace = value;\n else if (key === 'region') out.region = value;\n else if (key === 'profile') out.profile = value;\n else if (key === 'project') out.project = value;\n else if (key === 'subscription') out.subscription = value;\n }\n out.free = free.join(' ');\n return out;\n}\n\n/** Render `=== KEY ===\\n…` report sections (the legacy agent-tool report shape). */\nexport function buildReport(sections: Array<[string, string]>): string {\n return sections.map(([k, v]) => `=== ${k} ===\\n${v}`).join('\\n\\n');\n}\n","/**\n * AWS infrastructure scanner.\n *\n * Read-only AWS CLI discovery (`describe`/`list` actions, JSON output) mapped to\n * deterministic nodes. Detection is a cheap `commandExists('aws')` check, so an\n * absent CLI skips the scanner without throwing. Every command stays within the\n * read-only allowlist's `aws` gate and uses `--output json` for stable parsing.\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport { commandExists } from '../platform.js';\nimport { createScanRunner, redactValue } from '../tools.js';\nimport { safeJson, parseScanHint, buildReport } from './cloud-util.js';\n\ninterface Ec2Instance {\n InstanceId?: string;\n InstanceType?: string;\n State?: { Name?: string };\n PrivateIpAddress?: string;\n}\ninterface Ec2Result {\n Reservations?: Array<{ Instances?: Ec2Instance[] }>;\n}\ninterface RdsInstance {\n DBInstanceIdentifier?: string;\n Engine?: string;\n DBInstanceStatus?: string;\n Endpoint?: { Address?: string; Port?: number };\n}\ninterface RdsResult {\n DBInstances?: RdsInstance[];\n}\ninterface CacheCluster {\n CacheClusterId?: string;\n Engine?: string;\n CacheClusterStatus?: string;\n}\ninterface CacheResult {\n CacheClusters?: CacheCluster[];\n}\ninterface EksResult {\n clusters?: string[];\n}\ninterface LoadBalancer {\n LoadBalancerName?: string;\n DNSName?: string;\n Type?: string;\n State?: { Code?: string };\n}\ninterface ElbResult {\n LoadBalancers?: LoadBalancer[];\n}\n\nexport const cloudAwsScanner: Scanner = {\n id: 'cloud-aws',\n title: 'AWS infrastructure',\n platforms: 'all',\n allowedCommands: ['aws'],\n detect: (ctx) => Boolean((ctx.commandExists ?? commandExists)('aws')),\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const { region, profile } = parseScanHint(ctx.hint);\n const env: NodeJS.ProcessEnv = region ? { ...process.env, AWS_DEFAULT_REGION: region } : process.env;\n const pf = profile ? ` --profile ${profile}` : '';\n const runA = createScanRunner((c) => ctx.run(c, { timeout: 20_000, env }), { threshold: 3 });\n\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n const report: Array<[string, string]> = [];\n\n report.push(['IDENTITY', runA(`aws sts get-caller-identity${pf} --output json`)]);\n\n const ec2Raw = runA(`aws ec2 describe-instances${pf} --output json`);\n report.push(['EC2', ec2Raw]);\n const ec2 = safeJson<Ec2Result>(ec2Raw);\n for (const r of ec2?.Reservations ?? []) {\n for (const i of r.Instances ?? []) {\n const id = String(i.InstanceId ?? '');\n if (!id) continue;\n nodes.push({\n id: `host:aws:${id}`,\n type: 'host',\n name: id,\n discoveredVia: 'aws-ec2',\n confidence: 0.95,\n tags: ['cloud', 'aws', 'ec2'],\n metadata: redactValue({ instanceType: i.InstanceType, state: i.State?.Name, privateIp: i.PrivateIpAddress }) as Record<string, unknown>,\n });\n }\n }\n\n const rdsRaw = runA(`aws rds describe-db-instances${pf} --output json`);\n report.push(['RDS', rdsRaw]);\n const rds = safeJson<RdsResult>(rdsRaw);\n for (const db of rds?.DBInstances ?? []) {\n const id = String(db.DBInstanceIdentifier ?? '');\n if (!id) continue;\n nodes.push({\n id: `database_server:rds:${id}`,\n type: 'database_server',\n name: id,\n discoveredVia: 'aws-rds',\n confidence: 0.95,\n tags: ['cloud', 'aws', 'rds'],\n metadata: redactValue({ engine: db.Engine, status: db.DBInstanceStatus, endpoint: db.Endpoint?.Address, port: db.Endpoint?.Port }) as Record<string, unknown>,\n });\n }\n\n const cacheRaw = runA(`aws elasticache describe-cache-clusters${pf} --output json`);\n report.push(['ELASTICACHE', cacheRaw]);\n const cache = safeJson<CacheResult>(cacheRaw);\n for (const c of cache?.CacheClusters ?? []) {\n const id = String(c.CacheClusterId ?? '');\n if (!id) continue;\n nodes.push({\n id: `cache_server:aws:${id}`,\n type: 'cache_server',\n name: id,\n discoveredVia: 'aws-elasticache',\n confidence: 0.95,\n tags: ['cloud', 'aws', 'elasticache'],\n metadata: redactValue({ engine: c.Engine, status: c.CacheClusterStatus }) as Record<string, unknown>,\n });\n }\n\n const eksRaw = runA(`aws eks list-clusters${pf} --output json`);\n report.push(['EKS', eksRaw]);\n const eks = safeJson<EksResult>(eksRaw);\n for (const name of eks?.clusters ?? []) {\n if (!name) continue;\n nodes.push({\n id: `k8s_cluster:eks:${name}`,\n type: 'k8s_cluster',\n name,\n discoveredVia: 'aws-eks',\n confidence: 0.95,\n tags: ['cloud', 'aws', 'eks', 'kubernetes'],\n metadata: { provider: 'eks' },\n });\n }\n\n const elbRaw = runA(`aws elbv2 describe-load-balancers${pf} --output json`);\n report.push(['ELB_V2', elbRaw]);\n const elb = safeJson<ElbResult>(elbRaw);\n for (const lb of elb?.LoadBalancers ?? []) {\n const dns = String(lb.DNSName ?? '');\n if (!dns) continue;\n nodes.push({\n id: `web_service:aws:${dns}`,\n type: 'web_service',\n name: lb.LoadBalancerName ?? dns,\n discoveredVia: 'aws-elbv2',\n confidence: 0.9,\n tags: ['cloud', 'aws', 'elb'],\n metadata: redactValue({ dnsName: dns, type: lb.Type, state: lb.State?.Code }) as Record<string, unknown>,\n });\n }\n\n return { nodes, edges, report: buildReport(report) };\n },\n};\n","/**\n * Google Cloud Platform scanner.\n *\n * Read-only `gcloud … list` discovery with `--format=json` for stable parsing,\n * mapped to deterministic nodes. Detection is `commandExists('gcloud')`; an\n * absent CLI skips the scanner. Every command stays within the read-only\n * allowlist's `gcloud` gate.\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport { commandExists } from '../platform.js';\nimport { createScanRunner, redactValue } from '../tools.js';\nimport { safeJson, parseScanHint, buildReport } from './cloud-util.js';\n\ninterface ComputeInstance {\n name?: string;\n machineType?: string;\n status?: string;\n zone?: string;\n}\ninterface SqlInstance {\n name?: string;\n databaseVersion?: string;\n state?: string;\n region?: string;\n}\ninterface GkeCluster {\n name?: string;\n status?: string;\n location?: string;\n}\ninterface RedisInstance {\n name?: string;\n tier?: string;\n state?: string;\n}\ninterface RunService {\n metadata?: { name?: string };\n status?: { url?: string };\n}\ninterface PubsubTopic {\n name?: string;\n}\n\n/** gcloud `machineType`/`zone`/`name` are often full resource URLs — keep the last path segment. */\nfunction lastSegment(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const parts = value.split('/');\n return parts[parts.length - 1] || value;\n}\n\nexport const cloudGcpScanner: Scanner = {\n id: 'cloud-gcp',\n title: 'Google Cloud Platform infrastructure',\n platforms: 'all',\n allowedCommands: ['gcloud'],\n detect: (ctx) => Boolean((ctx.commandExists ?? commandExists)('gcloud')),\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const { project } = parseScanHint(ctx.hint);\n const pf = project ? ` --project ${project}` : '';\n const runG = createScanRunner((c) => ctx.run(c, { timeout: 20_000 }), { threshold: 3 });\n\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n const report: Array<[string, string]> = [];\n\n report.push(['IDENTITY', runG(`gcloud config list account --format=json`)]);\n\n const computeRaw = runG(`gcloud compute instances list${pf} --format=json`);\n report.push(['COMPUTE_INSTANCES', computeRaw]);\n for (const i of safeJson<ComputeInstance[]>(computeRaw) ?? []) {\n const name = String(i.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `host:gcp:${name}`,\n type: 'host',\n name,\n discoveredVia: 'gcp-compute',\n confidence: 0.95,\n tags: ['cloud', 'gcp', 'compute'],\n metadata: { machineType: lastSegment(i.machineType), status: i.status, zone: lastSegment(i.zone) },\n });\n }\n\n const sqlRaw = runG(`gcloud sql instances list${pf} --format=json`);\n report.push(['SQL_INSTANCES', sqlRaw]);\n for (const s of safeJson<SqlInstance[]>(sqlRaw) ?? []) {\n const name = String(s.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `database_server:gcp-sql:${name}`,\n type: 'database_server',\n name,\n discoveredVia: 'gcp-sql',\n confidence: 0.95,\n tags: ['cloud', 'gcp', 'cloudsql'],\n metadata: { engine: s.databaseVersion, state: s.state, region: s.region },\n });\n }\n\n const gkeRaw = runG(`gcloud container clusters list${pf} --format=json`);\n report.push(['GKE_CLUSTERS', gkeRaw]);\n for (const c of safeJson<GkeCluster[]>(gkeRaw) ?? []) {\n const name = String(c.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `k8s_cluster:gke:${name}`,\n type: 'k8s_cluster',\n name,\n discoveredVia: 'gcp-gke',\n confidence: 0.95,\n tags: ['cloud', 'gcp', 'gke', 'kubernetes'],\n metadata: { status: c.status, location: c.location },\n });\n }\n\n const redisRaw = runG(`gcloud redis instances list --regions=-${pf} --format=json`);\n report.push(['REDIS', redisRaw]);\n for (const r of safeJson<RedisInstance[]>(redisRaw) ?? []) {\n const name = lastSegment(String(r.name ?? ''));\n if (!name) continue;\n nodes.push({\n id: `cache_server:gcp:${name}`,\n type: 'cache_server',\n name,\n discoveredVia: 'gcp-redis',\n confidence: 0.95,\n tags: ['cloud', 'gcp', 'memorystore'],\n metadata: { tier: r.tier, state: r.state },\n });\n }\n\n const runRaw = runG(`gcloud run services list --platform managed${pf} --format=json`);\n report.push(['CLOUD_RUN', runRaw]);\n for (const svc of safeJson<RunService[]>(runRaw) ?? []) {\n const name = String(svc.metadata?.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `web_service:gcp-run:${name}`,\n type: 'web_service',\n name,\n discoveredVia: 'gcp-run',\n confidence: 0.9,\n tags: ['cloud', 'gcp', 'cloudrun'],\n metadata: redactValue({ url: svc.status?.url }) as Record<string, unknown>,\n });\n }\n\n const pubsubRaw = runG(`gcloud pubsub topics list${pf} --format=json`);\n report.push(['PUBSUB', pubsubRaw]);\n for (const t of safeJson<PubsubTopic[]>(pubsubRaw) ?? []) {\n const name = lastSegment(String(t.name ?? ''));\n if (!name) continue;\n nodes.push({\n id: `topic:gcp:${name}`,\n type: 'topic',\n name,\n discoveredVia: 'gcp-pubsub',\n confidence: 0.9,\n tags: ['cloud', 'gcp', 'pubsub'],\n metadata: { fullName: t.name },\n });\n }\n\n return { nodes, edges, report: buildReport(report) };\n },\n};\n","/**\n * Azure infrastructure scanner.\n *\n * Read-only `az … list/show` discovery with `--output json` for stable parsing,\n * mapped to deterministic nodes. Detection is `commandExists('az')`; an absent\n * CLI skips the scanner. Subscription / resource-group scoping comes from the\n * hint. Every command stays within the read-only allowlist's `az` gate.\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport { commandExists } from '../platform.js';\nimport { createScanRunner, redactValue } from '../tools.js';\nimport { safeJson, parseScanHint, buildReport } from './cloud-util.js';\n\ninterface AzVm {\n name?: string;\n location?: string;\n hardwareProfile?: { vmSize?: string };\n powerState?: string;\n}\ninterface AzAks {\n name?: string;\n location?: string;\n kubernetesVersion?: string;\n provisioningState?: string;\n}\ninterface AzSqlServer {\n name?: string;\n location?: string;\n version?: string;\n}\ninterface AzPostgres {\n name?: string;\n location?: string;\n version?: string;\n}\ninterface AzRedis {\n name?: string;\n location?: string;\n provisioningState?: string;\n}\ninterface AzWebApp {\n name?: string;\n defaultHostName?: string;\n state?: string;\n}\n\nexport const cloudAzureScanner: Scanner = {\n id: 'cloud-azure',\n title: 'Azure infrastructure',\n platforms: 'all',\n allowedCommands: ['az'],\n detect: (ctx) => Boolean((ctx.commandExists ?? commandExists)('az')),\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const { subscription, resourceGroup } = parseScanHint(ctx.hint);\n const sf = subscription ? ` --subscription ${subscription}` : '';\n const rf = resourceGroup ? ` --resource-group ${resourceGroup}` : '';\n const scope = `${sf}${rf}`;\n const runZ = createScanRunner((c) => ctx.run(c, { timeout: 20_000 }), { threshold: 3 });\n\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n const report: Array<[string, string]> = [];\n\n report.push(['IDENTITY', runZ(`az account show --output json${sf}`)]);\n\n const vmRaw = runZ(`az vm list${scope} --output json`);\n report.push(['VMS', vmRaw]);\n for (const vm of safeJson<AzVm[]>(vmRaw) ?? []) {\n const name = String(vm.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `host:azure:${name}`,\n type: 'host',\n name,\n discoveredVia: 'azure-vm',\n confidence: 0.95,\n tags: ['cloud', 'azure', 'vm'],\n metadata: { vmSize: vm.hardwareProfile?.vmSize, location: vm.location, powerState: vm.powerState },\n });\n }\n\n const aksRaw = runZ(`az aks list${scope} --output json`);\n report.push(['AKS', aksRaw]);\n for (const aks of safeJson<AzAks[]>(aksRaw) ?? []) {\n const name = String(aks.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `k8s_cluster:aks:${name}`,\n type: 'k8s_cluster',\n name,\n discoveredVia: 'azure-aks',\n confidence: 0.95,\n tags: ['cloud', 'azure', 'aks', 'kubernetes'],\n metadata: { location: aks.location, version: aks.kubernetesVersion, state: aks.provisioningState },\n });\n }\n\n const sqlRaw = runZ(`az sql server list${scope} --output json`);\n report.push(['SQL_SERVERS', sqlRaw]);\n for (const s of safeJson<AzSqlServer[]>(sqlRaw) ?? []) {\n const name = String(s.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `database_server:azure-sql:${name}`,\n type: 'database_server',\n name,\n discoveredVia: 'azure-sql',\n confidence: 0.95,\n tags: ['cloud', 'azure', 'sql'],\n metadata: { engine: 'sqlserver', location: s.location, version: s.version },\n });\n }\n\n const pgRaw = runZ(`az postgres server list${scope} --output json`);\n report.push(['POSTGRES', pgRaw]);\n for (const p of safeJson<AzPostgres[]>(pgRaw) ?? []) {\n const name = String(p.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `database_server:azure-postgres:${name}`,\n type: 'database_server',\n name,\n discoveredVia: 'azure-postgres',\n confidence: 0.95,\n tags: ['cloud', 'azure', 'postgres'],\n metadata: { engine: 'postgresql', location: p.location, version: p.version },\n });\n }\n\n const redisRaw = runZ(`az redis list${scope} --output json`);\n report.push(['REDIS', redisRaw]);\n for (const r of safeJson<AzRedis[]>(redisRaw) ?? []) {\n const name = String(r.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `cache_server:azure:${name}`,\n type: 'cache_server',\n name,\n discoveredVia: 'azure-redis',\n confidence: 0.95,\n tags: ['cloud', 'azure', 'redis'],\n metadata: { location: r.location, state: r.provisioningState },\n });\n }\n\n const webRaw = runZ(`az webapp list${scope} --output json`);\n report.push(['WEBAPPS', webRaw]);\n for (const w of safeJson<AzWebApp[]>(webRaw) ?? []) {\n const name = String(w.name ?? '');\n if (!name) continue;\n nodes.push({\n id: `web_service:azure:${name}`,\n type: 'web_service',\n name,\n discoveredVia: 'azure-webapp',\n confidence: 0.9,\n tags: ['cloud', 'azure', 'webapp'],\n metadata: redactValue({ hostName: w.defaultHostName, state: w.state }) as Record<string, unknown>,\n });\n }\n\n return { nodes, edges, report: buildReport(report) };\n },\n};\n","/**\n * Kubernetes scanner.\n *\n * Read-only `kubectl get … -o json` discovery mapped to deterministic nodes and\n * edges. Detection is `commandExists('kubectl')`; an absent CLI skips the\n * scanner. The cluster node anchors `contains` edges to its hosts and pods, and\n * a `connects_to` edge is emitted from a service to a running pod when the pod's\n * labels match the service's selector — only when both endpoints are in the\n * result set (the driver prunes dangling edges anyway). Pod enumeration is\n * bounded to keep `run_discovery` latency predictable on large clusters.\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport { commandExists } from '../platform.js';\nimport { createScanRunner } from '../tools.js';\nimport { safeJson, parseScanHint, buildReport } from './cloud-util.js';\n\nconst MAX_PODS = 200;\n\ninterface KubeListItem {\n metadata?: { name?: string; namespace?: string; labels?: Record<string, string> };\n}\ninterface KubeList<T extends KubeListItem = KubeListItem> {\n items?: T[];\n}\ninterface ServiceItem extends KubeListItem {\n spec?: { selector?: Record<string, string>; type?: string; clusterIP?: string };\n}\ninterface PodItem extends KubeListItem {\n spec?: { nodeName?: string };\n status?: { phase?: string };\n}\n\n/** True when every key/value in `selector` is present in `labels`. */\nfunction selectorMatches(selector: Record<string, string>, labels: Record<string, string>): boolean {\n const keys = Object.keys(selector);\n if (keys.length === 0) return false;\n return keys.every((k) => labels[k] === selector[k]);\n}\n\nexport const k8sScanner: Scanner = {\n id: 'k8s',\n title: 'Kubernetes resources',\n platforms: 'all',\n allowedCommands: ['kubectl'],\n detect: (ctx) => Boolean((ctx.commandExists ?? commandExists)('kubectl')),\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const { namespace } = parseScanHint(ctx.hint);\n const nsFlag = namespace ? `-n ${namespace}` : '--all-namespaces';\n const runK = createScanRunner((c) => ctx.run(c, { timeout: 15_000 }), { threshold: 3 });\n\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n const report: Array<[string, string]> = [];\n const nodeIds = new Set<string>();\n const add = (n: DiscoveryNode): void => {\n if (nodeIds.has(n.id)) return;\n nodeIds.add(n.id);\n nodes.push(n);\n };\n\n const contextRaw = runK('kubectl config current-context').trim();\n report.push(['CONTEXT', contextRaw]);\n // The circuit breaker returns a `(…)` sentinel on empty output — never a cluster name.\n const context = contextRaw.startsWith('(') ? '' : contextRaw;\n const clusterName = context || 'current';\n const clusterId = `k8s_cluster:${clusterName}`;\n add({\n id: clusterId,\n type: 'k8s_cluster',\n name: clusterName,\n discoveredVia: 'kubectl-context',\n confidence: 0.95,\n tags: ['kubernetes', 'cluster'],\n metadata: { context: clusterName },\n });\n\n const nodesRaw = runK('kubectl get nodes -o json');\n report.push(['NODES', nodesRaw]);\n for (const item of safeJson<KubeList>(nodesRaw)?.items ?? []) {\n const name = String(item.metadata?.name ?? '');\n if (!name) continue;\n const id = `host:k8s:${name}`;\n add({\n id,\n type: 'host',\n name,\n discoveredVia: 'kubectl-node',\n confidence: 0.9,\n tags: ['kubernetes', 'node'],\n metadata: { cluster: clusterName },\n });\n edges.push({ sourceId: clusterId, targetId: id, relationship: 'contains', evidence: 'kubectl get nodes', confidence: 0.9 });\n }\n\n const svcRaw = runK(`kubectl get services ${nsFlag} -o json`);\n report.push(['SERVICES', svcRaw]);\n const services = safeJson<KubeList<ServiceItem>>(svcRaw)?.items ?? [];\n for (const svc of services) {\n const name = String(svc.metadata?.name ?? '');\n const ns = String(svc.metadata?.namespace ?? 'default');\n if (!name) continue;\n add({\n id: `web_service:k8s:${ns}/${name}`,\n type: 'web_service',\n name: `${ns}/${name}`,\n discoveredVia: 'kubectl-service',\n confidence: 0.9,\n tags: ['kubernetes', 'service'],\n metadata: { namespace: ns, type: svc.spec?.type, clusterIP: svc.spec?.clusterIP },\n });\n }\n\n const podsRaw = runK(`kubectl get pods ${nsFlag} --field-selector=status.phase=Running -o json`);\n report.push(['PODS_RUNNING', podsRaw]);\n const allPods = safeJson<KubeList<PodItem>>(podsRaw)?.items ?? [];\n const pods = allPods.slice(0, MAX_PODS);\n if (allPods.length > MAX_PODS) {\n report.push(['PODS_OVERFLOW', `${allPods.length} running pods found; first ${MAX_PODS} catalogued`]);\n }\n for (const pod of pods) {\n const name = String(pod.metadata?.name ?? '');\n const ns = String(pod.metadata?.namespace ?? 'default');\n if (!name) continue;\n const podId = `pod:${ns}/${name}`;\n add({\n id: podId,\n type: 'pod',\n name: `${ns}/${name}`,\n discoveredVia: 'kubectl-pod',\n confidence: 0.9,\n tags: ['kubernetes', 'pod'],\n metadata: { namespace: ns, node: pod.spec?.nodeName },\n });\n edges.push({ sourceId: clusterId, targetId: podId, relationship: 'contains', evidence: 'kubectl get pods', confidence: 0.9 });\n\n // service → pod when the pod's labels match a same-namespace service selector.\n const labels = pod.metadata?.labels ?? {};\n for (const svc of services) {\n const svcNs = String(svc.metadata?.namespace ?? 'default');\n if (svcNs !== ns) continue;\n const selector = svc.spec?.selector;\n if (!selector || !selectorMatches(selector, labels)) continue;\n const svcId = `web_service:k8s:${svcNs}/${String(svc.metadata?.name ?? '')}`;\n edges.push({ sourceId: svcId, targetId: podId, relationship: 'connects_to', evidence: 'label selector match', confidence: 0.85 });\n }\n }\n\n return { nodes, edges, report: buildReport(report) };\n },\n};\n","/**\n * Local database scanner.\n *\n * Probes locally-installed DB clients (read-only) and discovers SQLite files in\n * app-data directories, mapping reachable servers to deterministic nodes. Each\n * client is only probed when present (`commandExists`), so an absent CLI never\n * throws and never produces a node. The expensive home-directory / config-file\n * find is opt-in via the `deep` hint token, keeping the default deterministic\n * `run_discovery` path fast. Connection strings are credential-redacted before\n * persistence.\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport {\n IS_WIN, HOME, commandExists, findFiles, dbScanDirs, scanWindowsDbServices,\n} from '../platform.js';\nimport { redactValue } from '../tools.js';\nimport { parseScanHint, buildReport } from './cloud-util.js';\n\n/** Extract a basename from an absolute path (cross-platform). */\nfunction baseName(path: string): string {\n const parts = path.split(/[\\\\/]/);\n return parts[parts.length - 1] || path;\n}\n\nexport const databasesScanner: Scanner = {\n id: 'databases',\n title: 'Local database servers',\n platforms: 'all',\n allowedCommands: ['psql', 'mysql', 'mongosh', 'redis-cli', 'sqlite3', 'pg_lsclusters', 'find', 'head', 'grep', 'awk', 'Get-Service', 'Get-ChildItem', 'Select-Object', 'Where-Object'],\n detect: (ctx) => {\n const exists = ctx.commandExists ?? commandExists;\n return ['psql', 'mysql', 'mongosh', 'redis-cli'].some((c) => Boolean(exists(c)));\n },\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const exists = ctx.commandExists ?? commandExists;\n const run = (cmd: string, opts?: { timeout?: number }) => ctx.run(cmd, { timeout: opts?.timeout ?? 10_000 });\n const deep = parseScanHint(ctx.hint).free.split(/\\s+/).includes('deep');\n\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n const report: Array<[string, string]> = [];\n const seen = new Set<string>();\n const add = (n: DiscoveryNode): void => {\n if (seen.has(n.id)) return;\n seen.add(n.id);\n nodes.push(n);\n };\n\n // ── PostgreSQL ──\n if (exists('psql')) {\n const out = IS_WIN\n ? run('psql -lqt')\n : run('psql -lqt 2>/dev/null | grep -v \"template0\\\\|template1\" | awk \\'{print $1}\\' | grep -v \"^$\\\\|^|\"');\n report.push(['POSTGRES_DATABASES', out || '(psql not running or requires auth)']);\n if (out) {\n const databases = out.split('\\n').map((l) => l.trim()).filter(Boolean);\n add({\n id: 'database_server:postgresql:localhost',\n type: 'database_server',\n name: 'PostgreSQL (localhost)',\n discoveredVia: 'psql',\n confidence: 0.9,\n tags: ['local', 'postgresql'],\n metadata: redactValue({ engine: 'postgresql', host: 'localhost', databases }) as Record<string, unknown>,\n });\n }\n if (!IS_WIN) {\n const clusters = run('pg_lsclusters 2>/dev/null');\n if (clusters) report.push(['POSTGRES_CLUSTERS', clusters]);\n }\n }\n\n // ── MySQL / MariaDB ──\n if (exists('mysql')) {\n const out = IS_WIN\n ? run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\"')\n : run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\" 2>/dev/null');\n report.push(['MYSQL_DATABASES', out || '(mysql not running or requires auth)']);\n if (out) {\n const databases = out.split('\\n').map((l) => l.trim()).filter((l) => l && l !== 'Database');\n add({\n id: 'database_server:mysql:localhost',\n type: 'database_server',\n name: 'MySQL (localhost)',\n discoveredVia: 'mysql',\n confidence: 0.9,\n tags: ['local', 'mysql'],\n metadata: redactValue({ engine: 'mysql', host: 'localhost', databases }) as Record<string, unknown>,\n });\n }\n }\n\n // ── MongoDB ── (JSON output — no arrow function, so it passes the allowlist)\n if (exists('mongosh')) {\n const evalExpr = 'JSON.stringify(db.adminCommand({listDatabases:1}))';\n const out = IS_WIN\n ? run(`mongosh --quiet --eval \"${evalExpr}\"`)\n : run(`mongosh --quiet --eval \"${evalExpr}\" 2>/dev/null`);\n report.push(['MONGODB_DATABASES', out || '(mongosh not available)']);\n if (out) {\n add({\n id: 'database_server:mongodb:localhost',\n type: 'database_server',\n name: 'MongoDB (localhost)',\n discoveredVia: 'mongosh',\n confidence: 0.9,\n tags: ['local', 'mongodb'],\n metadata: { engine: 'mongodb', host: 'localhost' },\n });\n }\n }\n\n // ── Redis ──\n if (exists('redis-cli')) {\n const out = IS_WIN\n ? run('redis-cli info server')\n : run('redis-cli info server 2>/dev/null | head -5');\n report.push(['REDIS_INFO', out || '(redis-cli not available)']);\n if (out) {\n add({\n id: 'cache_server:redis:localhost',\n type: 'cache_server',\n name: 'Redis (localhost)',\n discoveredVia: 'redis-cli',\n confidence: 0.9,\n tags: ['local', 'redis'],\n metadata: { engine: 'redis', host: 'localhost' },\n });\n }\n }\n\n // ── SQLite files in app-data directories ──\n const appDirs = dbScanDirs();\n if (appDirs.length > 0) {\n const out = findFiles(appDirs, ['*.sqlite', '*.sqlite3', '*.db'], 4, 80);\n report.push(['SQLITE_APP_FILES', out || '(none found)']);\n for (const path of out.split('\\n').map((l) => l.trim()).filter(Boolean)) {\n const base = baseName(path);\n add({\n id: `database:sqlite:${base}`,\n type: 'database',\n name: base,\n discoveredVia: 'sqlite-file',\n confidence: 0.7,\n tags: ['local', 'sqlite'],\n metadata: redactValue({ path }) as Record<string, unknown>,\n });\n }\n }\n\n // ── Windows DB services (port-based detection is covered by local-ports) ──\n if (IS_WIN) {\n report.push(['DB_SERVICES', scanWindowsDbServices() || '(no database services found)']);\n }\n\n // ── Opt-in expensive scans (gated by the `deep` hint token) ──\n if (deep) {\n const deepOut = IS_WIN\n ? run(\n `Get-ChildItem -Path '${HOME}' -Recurse -Depth 6 -Include '*.sqlite','*.sqlite3','*.db' -ErrorAction SilentlyContinue | ` +\n `Where-Object { $_.FullName -notmatch 'node_modules|\\\\.git' } | ` +\n `Select-Object -First 100 -ExpandProperty FullName`,\n )\n : run(`find \"${HOME}\" -maxdepth 6 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" 2>/dev/null | head -100`, { timeout: 30_000 });\n report.push(['SQLITE_DEEP_SCAN', deepOut || '(none found)']);\n for (const path of deepOut.split('\\n').map((l) => l.trim()).filter(Boolean)) {\n const base = baseName(path);\n add({\n id: `database:sqlite:${base}`,\n type: 'database',\n name: base,\n discoveredVia: 'sqlite-deep-scan',\n confidence: 0.6,\n tags: ['local', 'sqlite', 'deep'],\n metadata: redactValue({ path }) as Record<string, unknown>,\n });\n }\n const configOut = IS_WIN\n ? run(\n `Get-ChildItem -Path '${HOME}' -Recurse -Depth 4 -Include '.env','.env.local','database.yml','database.json','docker-compose.yml' -ErrorAction SilentlyContinue | ` +\n `Select-Object -First 20 -ExpandProperty FullName`,\n { timeout: 15_000 },\n )\n : run(`find \"${HOME}\" -maxdepth 4 \\\\( -name \".env\" -o -name \".env.local\" -o -name \"database.yml\" -o -name \"database.json\" -o -name \"docker-compose.yml\" \\\\) 2>/dev/null | head -20`, { timeout: 15_000 });\n report.push(['DB_CONFIG_FILES', configOut || '(none found)']);\n }\n\n return { nodes, edges, report: buildReport(report) };\n },\n};\n","/**\n * Compliance scoring (3.4) — schemas + types.\n *\n * Rulesets are **declarative data** (a serializable `RuleCheck` expression tree)\n * interpreted by a single trusted engine — never executable predicate code. `field`\n * and `pattern` are closed enums, so a ruleset can neither reach arbitrary node\n * properties nor inject a ReDoS-prone regex. Every bundled ruleset is\n * `RulesetSchema.parse`d at module load (fail-fast on malformed data).\n */\n\nimport { z } from 'zod';\nimport { NODE_TYPES, NODE_TYPE_GROUPS } from '../types.js';\n\n/** Keys of NODE_TYPE_GROUPS — the semantic scopes a rule may target. */\nexport const NODE_TYPE_GROUP_KEYS = Object.keys(NODE_TYPE_GROUPS) as Array<keyof typeof NODE_TYPE_GROUPS>;\n\n/** Which nodes a rule applies to. Empty scope (no groups/types) = all nodes. */\nexport const RuleScopeSchema = z.object({\n groups: z.array(z.enum(NODE_TYPE_GROUP_KEYS as [string, ...string[]])).optional(),\n types: z.array(z.enum(NODE_TYPES)).optional(),\n});\nexport type RuleScope = z.infer<typeof RuleScopeSchema>;\n\n/** Closed set of node fields a condition may read (no prototype/field injection). */\nexport const FieldPathSchema = z.enum([\n 'type', 'name', 'domain', 'subDomain', 'confidence', 'qualityScore',\n 'owner', 'tags', 'metadataKeys', 'metadataValues',\n]);\nexport type FieldPath = z.infer<typeof FieldPathSchema>;\n\n/** Engine-owned named regexes — a ruleset can never supply a raw pattern. */\nexport const PATTERN_NAMES = ['dsn_with_credentials', 'owner_key', 'public_exposure'] as const;\n\nexport const ConditionSchema = z.object({\n field: FieldPathSchema,\n op: z.enum(['present', 'absent', 'lt', 'lte', 'gt', 'gte', 'eq', 'includes', 'matches']),\n value: z.union([z.string(), z.number()]).optional(),\n pattern: z.enum(PATTERN_NAMES).optional(),\n});\nexport type Condition = z.infer<typeof ConditionSchema>;\n\n/** A serializable check: a leaf Condition or an all/any/not combinator. */\nexport type RuleCheck =\n | Condition\n | { all: RuleCheck[] }\n | { any: RuleCheck[] }\n | { not: RuleCheck };\n\nexport const RuleCheckSchema: z.ZodType<RuleCheck> = z.lazy(() =>\n z.union([\n ConditionSchema,\n z.object({ all: z.array(RuleCheckSchema) }),\n z.object({ any: z.array(RuleCheckSchema) }),\n z.object({ not: RuleCheckSchema }),\n ]),\n);\n\nexport const SEVERITIES = ['critical', 'high', 'medium', 'low'] as const;\nexport const SeveritySchema = z.enum(SEVERITIES);\nexport type Severity = z.infer<typeof SeveritySchema>;\n\n/** Severity → score weight (decision #5). */\nexport const SEVERITY_WEIGHT: Record<Severity, number> = { critical: 4, high: 3, medium: 2, low: 1 };\n\nexport const ComplianceRuleSchema = z.object({\n id: z.string(),\n control: z.string().describe('External control id, e.g. \"CIS-1.4\"'),\n framework: z.enum(['CIS', 'SOC2', 'ISO27001', 'baseline']),\n title: z.string(),\n severity: SeveritySchema,\n rationale: z.string(),\n scope: RuleScopeSchema,\n /**\n * Optional applicability predicate (decision #7): a scoped node is only counted\n * when this is absent or evaluates true — so a rule needing a signal that's absent\n * everywhere becomes `not_applicable` rather than failing on sparse data.\n */\n applicableWhen: RuleCheckSchema.optional(),\n check: RuleCheckSchema,\n});\nexport type ComplianceRule = z.infer<typeof ComplianceRuleSchema>;\n\nexport const RulesetSchema = z.object({\n name: z.string(),\n version: z.string(),\n framework: z.string(),\n description: z.string(),\n rules: z.array(ComplianceRuleSchema).min(1),\n}).superRefine((rs, ctx) => {\n const seen = new Set<string>();\n for (const r of rs.rules) {\n if (seen.has(r.id)) ctx.addIssue({ code: 'custom', message: `duplicate rule id: ${r.id}`, path: ['rules'] });\n seen.add(r.id);\n }\n});\nexport type Ruleset = z.infer<typeof RulesetSchema>;\n\nexport const ControlResultSchema = z.object({\n ruleId: z.string(),\n control: z.string(),\n framework: z.string(),\n severity: SeveritySchema,\n status: z.enum(['pass', 'fail', 'not_applicable']),\n applicableCount: z.number().int(),\n passedCount: z.number().int(),\n failingNodeIds: z.array(z.string()),\n});\nexport type ControlResult = z.infer<typeof ControlResultSchema>;\n\nexport const ComplianceReportSchema = z.object({\n rulesetName: z.string(),\n rulesetVersion: z.string(),\n generatedAt: z.string(),\n score: z.number().min(0).max(100).nullable(),\n status: z.enum(['pass', 'fail', 'not_applicable']),\n totals: z.object({\n rules: z.number(), applicable: z.number(), passed: z.number(),\n failed: z.number(), notApplicable: z.number(),\n }),\n bySeverity: z.record(SeveritySchema, z.object({ passed: z.number(), failed: z.number() })),\n controls: z.array(ControlResultSchema),\n gaps: z.array(z.object({\n ruleId: z.string(), control: z.string(), severity: SeveritySchema,\n title: z.string(), nodeIds: z.array(z.string()),\n })),\n});\nexport type ComplianceReport = z.infer<typeof ComplianceReportSchema>;\n","/**\n * Compliance scoring engine (3.4) — pure, deterministic, DB-free.\n *\n * `scoreTopology({nodes, edges}, ruleset)` mirrors `diffTopology`'s shape: plain\n * arrays in, a structured `ComplianceReport` out. The engine is the only trusted\n * evaluator of the declarative `RuleCheck` DSL — no `eval`, no ruleset-supplied\n * code or regex. Iteration order is stabilised (nodes + rules sorted by id) so the\n * report is byte-stable for a fixed `now`.\n */\n\nimport type { NodeRow, EdgeRow } from '../types.js';\nimport { NODE_TYPE_GROUPS } from '../types.js';\nimport { redactSecrets } from '../tools.js';\nimport {\n RulesetSchema, SEVERITY_WEIGHT, SEVERITIES,\n} from './types.js';\nimport type {\n Ruleset, ComplianceRule, ComplianceReport, ControlResult, Condition, RuleCheck, Severity, FieldPath,\n} from './types.js';\n\nexport interface ComplianceInput { nodes: NodeRow[]; edges: EdgeRow[]; }\n\n/** Engine-owned named regexes (a ruleset references these by name, never supplies one). */\nconst OWNER_KEY_RE = /^(owner|team|maintainer|contact|owned[-_]?by)$/i;\nconst PUBLIC_EXPOSURE_RE = /(^|[^0-9])0\\.0\\.0\\.0(\\/0)?|public|internet|exposed/i;\n\n/** Read a closed-enum field off a node as a comparable value. */\nfunction readField(node: NodeRow, field: FieldPath): unknown {\n switch (field) {\n case 'type': return node.type;\n case 'name': return node.name;\n case 'domain': return node.domain;\n case 'subDomain': return node.subDomain;\n case 'confidence': return node.confidence;\n case 'qualityScore': return node.qualityScore;\n case 'owner': return node.owner;\n case 'tags': return node.tags;\n case 'metadataKeys': return Object.keys(node.metadata ?? {});\n case 'metadataValues': return Object.values(node.metadata ?? {}).map((v) => (typeof v === 'string' ? v : JSON.stringify(v)));\n }\n}\n\n/** True if `value` \"has content\": non-empty array/string, defined number/other. */\nfunction isPresent(value: unknown): boolean {\n if (value === undefined || value === null) return false;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'string') return value.length > 0;\n return true;\n}\n\nfunction matchesPattern(value: unknown, pattern: NonNullable<Condition['pattern']>): boolean {\n const test = (s: string): boolean => {\n switch (pattern) {\n case 'dsn_with_credentials': return redactSecrets(s) !== s; // a credential was present\n case 'owner_key': return OWNER_KEY_RE.test(s);\n case 'public_exposure': return PUBLIC_EXPOSURE_RE.test(s);\n }\n };\n if (Array.isArray(value)) return value.some((v) => typeof v === 'string' && test(v));\n return typeof value === 'string' && test(value);\n}\n\nexport function evaluateCondition(node: NodeRow, cond: Condition): boolean {\n const v = readField(node, cond.field);\n switch (cond.op) {\n case 'present': return isPresent(v);\n case 'absent': return !isPresent(v);\n case 'lt': return typeof v === 'number' && typeof cond.value === 'number' && v < cond.value;\n case 'lte': return typeof v === 'number' && typeof cond.value === 'number' && v <= cond.value;\n case 'gt': return typeof v === 'number' && typeof cond.value === 'number' && v > cond.value;\n case 'gte': return typeof v === 'number' && typeof cond.value === 'number' && v >= cond.value;\n case 'eq': return v === cond.value;\n case 'includes': return Array.isArray(v) && cond.value !== undefined && v.includes(cond.value as never);\n case 'matches': return cond.pattern !== undefined && matchesPattern(v, cond.pattern);\n }\n}\n\nexport function evaluateCheck(node: NodeRow, check: RuleCheck): boolean {\n if ('all' in check) return check.all.every((c) => evaluateCheck(node, c));\n if ('any' in check) return check.any.some((c) => evaluateCheck(node, c));\n if ('not' in check) return !evaluateCheck(node, check.not);\n return evaluateCondition(node, check);\n}\n\n/** Expand a rule's scope to the set of node types it targets (empty = all types). */\nfunction scopedTypes(rule: ComplianceRule): Set<string> | null {\n const { groups, types } = rule.scope;\n if ((!groups || groups.length === 0) && (!types || types.length === 0)) return null; // all\n const out = new Set<string>();\n for (const g of groups ?? []) for (const t of NODE_TYPE_GROUPS[g as keyof typeof NODE_TYPE_GROUPS]) out.add(t);\n for (const t of types ?? []) out.add(t);\n return out;\n}\n\n/** Nodes a rule applies to: scope-matched AND (applicableWhen absent or true). */\nfunction applicableNodes(nodes: NodeRow[], rule: ComplianceRule): NodeRow[] {\n const types = scopedTypes(rule);\n return nodes.filter((n) =>\n (types === null || types.has(n.type)) &&\n (rule.applicableWhen === undefined || evaluateCheck(n, rule.applicableWhen)),\n );\n}\n\nexport function evaluateRule(input: ComplianceInput, rule: ComplianceRule): ControlResult {\n const applicable = applicableNodes(input.nodes, rule);\n const base = { ruleId: rule.id, control: rule.control, framework: rule.framework, severity: rule.severity };\n if (applicable.length === 0) {\n return { ...base, status: 'not_applicable', applicableCount: 0, passedCount: 0, failingNodeIds: [] };\n }\n const failingNodeIds = applicable.filter((n) => !evaluateCheck(n, rule.check)).map((n) => n.id).sort();\n return {\n ...base,\n status: failingNodeIds.length === 0 ? 'pass' : 'fail',\n applicableCount: applicable.length,\n passedCount: applicable.length - failingNodeIds.length,\n failingNodeIds,\n };\n}\n\n/**\n * Score a topology against a ruleset. `score = round(100 × Σweight(passed applicable) /\n * Σweight(applicable))`, weighted by severity, with not-applicable controls excluded\n * from the denominator. `score = null` / `status = 'not_applicable'` when nothing applies.\n */\nexport function scoreTopology(input: ComplianceInput, ruleset: Ruleset, opts?: { now?: string }): ComplianceReport {\n const nodes = [...input.nodes].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n const rules = [...ruleset.rules].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n const controls = rules.map((r) => evaluateRule({ nodes, edges: input.edges }, r));\n\n const bySeverity = Object.fromEntries(SEVERITIES.map((s) => [s, { passed: 0, failed: 0 }])) as ComplianceReport['bySeverity'];\n let applicable = 0, passed = 0, failed = 0, notApplicable = 0;\n let weightTotal = 0, weightPassed = 0;\n const gaps: ComplianceReport['gaps'] = [];\n\n for (const c of controls) {\n const w = SEVERITY_WEIGHT[c.severity as Severity];\n if (c.status === 'not_applicable') { notApplicable++; continue; }\n applicable++;\n weightTotal += w;\n if (c.status === 'pass') { passed++; weightPassed += w; bySeverity[c.severity].passed++; }\n else {\n failed++; bySeverity[c.severity].failed++;\n const rule = rules.find((r) => r.id === c.ruleId)!;\n gaps.push({ ruleId: c.ruleId, control: c.control, severity: c.severity, title: rule.title, nodeIds: c.failingNodeIds });\n }\n }\n\n const score = applicable === 0 ? null : Math.round((100 * weightPassed) / weightTotal);\n const status: ComplianceReport['status'] = applicable === 0 ? 'not_applicable' : failed === 0 ? 'pass' : 'fail';\n\n return {\n rulesetName: ruleset.name,\n rulesetVersion: ruleset.version,\n generatedAt: opts?.now ?? new Date().toISOString(),\n score,\n status,\n totals: { rules: rules.length, applicable, passed, failed, notApplicable },\n bySeverity,\n controls,\n gaps,\n };\n}\n\n/** Validate raw ruleset data (used by the registry and any future import path). */\nexport function loadRuleset(raw: unknown): Ruleset {\n return RulesetSchema.parse(raw);\n}\n","/**\n * Topology diffing — pure, deterministic comparison of two discovery snapshots.\n *\n * This module knows nothing about the database; it operates on plain\n * node/edge arrays so it can be unit-tested in isolation and reused by the\n * CLI, the MCP server, and the exporter. Drift is detected on a stable\n * projection of node fields (see DRIFT_FIELDS); `confidence` is reported but\n * never on its own marks a node as changed.\n */\n\nimport type { NodeRow, EdgeRow, NodeChange, DriftField } from './types.js';\nimport { DRIFT_FIELDS } from './types.js';\n\n/** Logical identity of an edge within a session: source + target + relationship. */\nconst edgeKey = (e: EdgeRow): string => `${e.sourceId}\u0000${e.targetId}\u0000${e.relationship}`;\n\n/** Deterministic JSON serialization with recursively sorted object keys. */\nexport function stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;\n const keys = Object.keys(value as Record<string, unknown>).sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify((value as Record<string, unknown>)[k])}`).join(',')}}`;\n}\n\n/** Return the subset of DRIFT_FIELDS that differ between two nodes (order-independent). */\nfunction driftedFields(a: NodeRow, b: NodeRow): DriftField[] {\n const changed: DriftField[] = [];\n for (const f of DRIFT_FIELDS) {\n if (f === 'tags') {\n if ([...a.tags].sort().join('\u0000') !== [...b.tags].sort().join('\u0000')) changed.push(f);\n } else if (f === 'metadata' || f === 'cost') {\n // `cost` is an object (3.3); a reference compare via `!==` would always mis-fire.\n if (stableStringify(a[f]) !== stableStringify(b[f])) changed.push(f);\n } else if (a[f] !== b[f]) {\n changed.push(f);\n }\n }\n return changed;\n}\n\nexport interface TopologyInput {\n nodes: NodeRow[];\n edges: EdgeRow[];\n}\n\nexport interface TopologyDelta {\n nodes: { added: NodeRow[]; removed: NodeRow[]; changed: NodeChange[]; unchanged: number };\n edges: { added: EdgeRow[]; removed: EdgeRow[]; unchanged: number };\n summary: {\n nodesAdded: number; nodesRemoved: number; nodesChanged: number;\n edgesAdded: number; edgesRemoved: number;\n };\n}\n\n/**\n * Compute the delta from `base` to `current`. Nodes are keyed by `id`, edges by\n * (source, target, relationship). Pure: same inputs always yield the same output.\n */\nexport function diffTopology(base: TopologyInput, current: TopologyInput): TopologyDelta {\n const baseNodes = new Map(base.nodes.map((n) => [n.id, n]));\n const curNodes = new Map(current.nodes.map((n) => [n.id, n]));\n\n const added: NodeRow[] = [];\n const removed: NodeRow[] = [];\n const changed: NodeChange[] = [];\n let unchangedNodes = 0;\n\n for (const [id, after] of curNodes) {\n const before = baseNodes.get(id);\n if (!before) {\n added.push(after);\n continue;\n }\n const fields = driftedFields(before, after);\n if (fields.length > 0) {\n changed.push({ id, before, after, changedFields: fields, confidenceDelta: after.confidence - before.confidence });\n } else {\n unchangedNodes++;\n }\n }\n for (const [id, before] of baseNodes) {\n if (!curNodes.has(id)) removed.push(before);\n }\n\n const baseEdges = new Map(base.edges.map((e) => [edgeKey(e), e]));\n const curEdges = new Map(current.edges.map((e) => [edgeKey(e), e]));\n const edgesAdded: EdgeRow[] = [];\n const edgesRemoved: EdgeRow[] = [];\n let unchangedEdges = 0;\n for (const [k, e] of curEdges) {\n if (baseEdges.has(k)) unchangedEdges++;\n else edgesAdded.push(e);\n }\n for (const [k, e] of baseEdges) {\n if (!curEdges.has(k)) edgesRemoved.push(e);\n }\n\n return {\n nodes: { added, removed, changed, unchanged: unchangedNodes },\n edges: { added: edgesAdded, removed: edgesRemoved, unchanged: unchangedEdges },\n summary: {\n nodesAdded: added.length,\n nodesRemoved: removed.length,\n nodesChanged: changed.length,\n edgesAdded: edgesAdded.length,\n edgesRemoved: edgesRemoved.length,\n },\n };\n}\n","/**\n * Anomaly detection (3.6) — pure, deterministic flagging of standing structural risk.\n *\n * Knows nothing about the database; operates on plain `NodeRow[]` + an in-memory\n * degree map so it can be unit-tested in isolation and reused by the CLI, the MCP\n * server, and the scheduled-scan alert path (3.1). Same topology always yields the\n * same anomalies with identical `reason` strings (mirrors `deriveSessionName`). No\n * LLM, no I/O, no dependency. `reason` interpolates only the structured `nodeId` and\n * numeric scores — never raw node name/metadata (prompt-injection safe).\n */\n\nimport type { NodeRow, Anomaly, AnomalyThresholds, AnomalySeverity } from './types.js';\nimport { NODE_TYPE_GROUPS, DEFAULT_ANOMALY_THRESHOLDS } from './types.js';\n\nexport { DEFAULT_ANOMALY_THRESHOLDS };\n\n/** Node types in a managed family; anything else (incl. 'unknown') is shadow-IT-eligible. */\nconst MANAGED_TYPES: ReadonlySet<string> = new Set(Object.values(NODE_TYPE_GROUPS).flat());\n\n/** True when a node has no business domain ('(none)' is the summary's sentinel). */\nfunction hasNoDomain(n: NodeRow): boolean {\n return n.domain == null || n.domain === '' || n.domain === '(none)';\n}\n\n/** Flag zero/weak-degree nodes. degree 0 → high; 1..orphanWeakDegree → low. */\nexport function detectOrphans(\n nodes: NodeRow[],\n degree: ReadonlyMap<string, number>,\n thresholds: AnomalyThresholds = DEFAULT_ANOMALY_THRESHOLDS,\n): Anomaly[] {\n const out: Anomaly[] = [];\n for (const n of nodes) {\n const d = degree.get(n.id) ?? 0;\n if (d === 0) {\n out.push({ nodeId: n.id, kind: 'orphan', severity: 'high', reason: 'zero-degree node (no edges)' });\n } else if (d <= thresholds.orphanWeakDegree) {\n out.push({ nodeId: n.id, kind: 'orphan', severity: 'low', reason: `weakly-connected node (degree ${d})` });\n }\n }\n return out;\n}\n\n/** Flag unmanaged-type or undomained low-confidence/quality nodes. */\nexport function detectShadowIt(\n nodes: NodeRow[],\n thresholds: AnomalyThresholds = DEFAULT_ANOMALY_THRESHOLDS,\n): Anomaly[] {\n const out: Anomaly[] = [];\n for (const n of nodes) {\n if (!MANAGED_TYPES.has(n.type)) {\n out.push({ nodeId: n.id, kind: 'shadow-it', severity: 'medium', reason: `unmanaged node type \"${n.type}\"` });\n continue;\n }\n if (hasNoDomain(n)) {\n const lowConf = n.confidence < thresholds.shadowConfidence;\n const lowQual = n.qualityScore != null && n.qualityScore < thresholds.shadowQuality;\n if (lowConf || lowQual) {\n const sev: AnomalySeverity = lowConf && lowQual ? 'high' : 'medium';\n const parts: string[] = [];\n if (lowConf) parts.push(`low confidence ${n.confidence.toFixed(2)}`);\n if (lowQual) parts.push(`low quality ${n.qualityScore}`);\n out.push({ nodeId: n.id, kind: 'shadow-it', severity: sev, reason: `${parts.join(' and ')} with no business domain` });\n }\n }\n }\n return out;\n}\n\n/** Aggregate + stable sort (nodeId, then kind). Returns [] for an empty topology. */\nexport function detectAnomalies(\n nodes: NodeRow[],\n degree: ReadonlyMap<string, number>,\n thresholds: AnomalyThresholds = DEFAULT_ANOMALY_THRESHOLDS,\n): Anomaly[] {\n return [...detectOrphans(nodes, degree, thresholds), ...detectShadowIt(nodes, thresholds)]\n .sort((a, b) => a.nodeId.localeCompare(b.nodeId) || a.kind.localeCompare(b.kind));\n}\n\n/** Anomalies present in `current` but absent in `base`, keyed by (nodeId, kind). Pure & order-stable. */\nexport function newAnomalies(base: Anomaly[], current: Anomaly[]): Anomaly[] {\n const seen = new Set(base.map((a) => `${a.nodeId}|${a.kind}`));\n return current.filter((a) => !seen.has(`${a.nodeId}|${a.kind}`));\n}\n","/**\n * `SqliteStoreBackend` — the default (and currently only) {@link StoreBackend}\n * implementation for the central collector (2.12).\n *\n * It is a thin adapter over `CartographyDB`: the schema, migrations, and merge SQL\n * all live in `src/db.ts` (the single owner of the catalog), so this class only\n * forwards the org-scoped central operations. Constructing it adds no new state and\n * no new schema — the zero-config local SQLite path is byte-for-byte unchanged.\n *\n * A graph/Postgres backend (4.3) implements the same interface without changing the\n * ingest orchestration that consumes it.\n */\n\nimport type { CartographyDB, OrgSummary } from '../db.js';\nimport type { DiscoveryNode, DiscoveryEdge, Contributor } from '../types.js';\nimport type { StoreBackend, NodeIdentity } from './backend.js';\n\nexport class SqliteStoreBackend implements StoreBackend {\n constructor(private readonly db: CartographyDB) {}\n\n upsertNode(org: string, node: DiscoveryNode, identity: NodeIdentity, contributor: Contributor): 'created' | 'merged' {\n return this.db.upsertCentralNode(org, node, identity, contributor);\n }\n\n insertEdge(org: string, edge: DiscoveryEdge): void {\n this.db.insertCentralEdge(org, edge);\n }\n\n getSummary(org: string): OrgSummary {\n return this.db.getOrgSummary(org);\n }\n\n getContributors(globalId: string): Contributor[] {\n return this.db.getContributorsByGlobalId(globalId);\n }\n\n /**\n * No-op: the wrapped `CartographyDB` is owned by the caller (it is shared with the\n * read-side MCP server in server-mode), so the backend never closes it. The caller\n * closes the `CartographyDB` directly.\n */\n close(): void {\n /* connection lifecycle is owned by the caller */\n }\n}\n","/**\n * Global-identity merge core for the central collector (2.12) — pure, no I/O.\n *\n * The merge *keys* (`normalizeId`, `contentHash`, `keyMetaOf`, `globalId`) already\n * exist in `src/db.ts` from the 2.9 identity work; this module re-exports them so\n * the central collector has one import surface, and adds {@link computeIdentity} —\n * the small adapter that turns one incoming `DiscoveryNode` into the precomputed\n * `(globalId, contentHash)` pair the {@link StoreBackend} merge consumes.\n *\n * The merge *resolution* (primary by `(org, globalId)`, secondary by\n * `(org, contentHash)`, contributor union, max-confidence) lives in the store\n * (`CartographyDB.upsertCentralNode`) because it needs the existing row — keeping\n * the SQL next to the schema. This module is the deterministic key derivation that\n * both the client (push) and the server (ingest) must agree on.\n */\n\nimport { normalizeId, contentHash, keyMetaOf, globalId } from '../db.js';\nimport type { DiscoveryNode } from '../types.js';\nimport type { NodeIdentity } from '../store/backend.js';\n\nexport { normalizeId, contentHash, keyMetaOf, globalId };\n\n/**\n * Derive the precomputed merge identity for one incoming node under `org`:\n * - `globalId` = `{org}:{normalizeId(node.id)}` (primary key; the org-scope ensures\n * two organizations never collapse onto one logical node).\n * - `contentHash` = sha256 over `type + normalized name + sorted key-meta` (secondary\n * key; catches `id` drift between machines for the same logical resource).\n *\n * Pure and deterministic: the same node + org always yields the same identity, on\n * any machine. This is the contract the client (2.11 push) and the server (ingest)\n * share so a content hash computed on the client matches the one the server recomputes.\n */\nexport function computeIdentity(org: string, node: DiscoveryNode): NodeIdentity {\n return {\n globalId: globalId(org, node.id),\n contentHash: contentHash(node.type, node.name, keyMetaOf(node.metadata ?? {})),\n };\n}\n","/**\n * Org-keyed, admin-reversible pseudonymization (2.10 `anonymized` sharing level).\n *\n * Identifying fragments — private IPs, file paths, `user@host` pairs, and bare\n * hostnames — are replaced by a deterministic token\n * `anon:{kind}:{base32(first 12 bytes of HMAC-SHA256(hmacKey, plaintext))}`\n * so the same input + org key always yields the same token (a central merge then\n * collapses the same host across machines). The token is one-way for anyone\n * without the org key (HMAC preimage resistance + a 32-byte secret); an admin\n * holding the key can invert it via an AES-256-GCM reversal map.\n *\n * Topology shape is preserved: {@link pseudonymize} mirrors `redactValue`\n * (`src/tools.ts`) — it walks structure, rewriting only leaf strings; it never\n * adds, removes, or reorders keys/array elements.\n *\n * Zero new dependencies — all crypto is `node:crypto`.\n */\n\nimport { createHmac, createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { hmacKey, reversalKey } from './orgkey.js';\nimport { logError } from './logger.js';\nimport type { CartographyDB } from './db.js';\n\n/** The kinds of identifying fragment we tokenize. The prefix keeps namespaces legible + non-colliding. */\nexport type FragmentKind = 'host' | 'user' | 'path' | 'ip';\n\n/**\n * RFC-1918 private IPv4 ranges: 10/8, 172.16/12, 192.168/16. Only private IPs are\n * pseudonymized — public IPs are not identifying of an employee's machine and are\n * left intact (so topology against public infra still reads).\n */\nexport const PRIVATE_IP =\n /\\b(?:10(?:\\.\\d{1,3}){3}|192\\.168(?:\\.\\d{1,3}){2}|172\\.(?:1[6-9]|2\\d|3[01])(?:\\.\\d{1,3}){2})\\b/g;\n\n/** A hostname/domain label run, e.g. `db-01.internal.acme.lan` (≥2 dot-separated labels). */\nconst HOSTNAME = /\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}\\b/gi;\n\n/** An absolute POSIX path (`/home/alice/...`) or Windows path (`C:\\Users\\alice\\...`). */\nconst POSIX_PATH = /(?:^|(?<=\\s|=|:|\"|'|\\())(\\/[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)+)/g;\nconst WIN_PATH = /\\b[A-Za-z]:\\\\[A-Za-z0-9._\\\\-]+/g;\n\n/** RFC-4648 base32 (no padding) of the first 12 bytes of a digest → 20 lowercase chars. */\nconst B32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';\nfunction base32(buf: Buffer): string {\n let bits = 0;\n let value = 0;\n let out = '';\n for (const byte of buf) {\n value = (value << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n out += B32_ALPHABET[(value >>> (bits - 5)) & 31];\n bits -= 5;\n }\n }\n if (bits > 0) out += B32_ALPHABET[(value << (5 - bits)) & 31];\n return out;\n}\n\n/** GCM ciphertext layout: base64( iv[12] ‖ tag[16] ‖ ciphertext ). */\nfunction encryptPlaintext(plaintext: string, key: Buffer): string {\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n const tag = cipher.getAuthTag();\n return Buffer.concat([iv, tag, ct]).toString('base64');\n}\n\nfunction decryptPlaintext(encoded: string, key: Buffer): string | undefined {\n try {\n const raw = Buffer.from(encoded, 'base64');\n const iv = raw.subarray(0, 12);\n const tag = raw.subarray(12, 28);\n const ct = raw.subarray(28);\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8');\n } catch {\n // GCM auth failure (tampered / wrong key) — fail closed, never throw raw.\n return undefined;\n }\n}\n\n/**\n * Deterministic token for one identifying fragment. When `db` is supplied, the\n * plaintext is AES-256-GCM-encrypted under `reversalKey(orgKey)` and persisted so\n * an admin can later invert the token. Idempotent: the token is deterministic, so\n * re-encrypting the same fragment `INSERT OR REPLACE`s the same row.\n */\nexport function pseudonymizeFragment(plaintext: string, kind: FragmentKind, orgKey: Buffer, db?: CartographyDB): string {\n const digest = createHmac('sha256', hmacKey(orgKey)).update(plaintext).digest();\n const token = `anon:${kind}:${base32(digest.subarray(0, 12))}`;\n if (db) db.saveReversal(token, encryptPlaintext(plaintext, reversalKey(orgKey)));\n return token;\n}\n\n/**\n * Pseudonymize the identifying fragments inside a single string — private IPs,\n * file paths, `user@host` pairs, and bare hostnames — leaving structure-irrelevant\n * text intact. Order matters: paths and IPs are matched before hostnames so an IP\n * or a path segment is never mis-tokenized as a host.\n */\nexport function pseudonymizeString(s: string, orgKey: Buffer, db?: CartographyDB): string {\n let out = s;\n // 1. Private IPs (most specific; also subsumed by paths/hosts if left late).\n out = out.replace(PRIVATE_IP, (m) => pseudonymizeFragment(m, 'ip', orgKey, db));\n // 2. Windows + POSIX absolute paths.\n out = out.replace(WIN_PATH, (m) => pseudonymizeFragment(m, 'path', orgKey, db));\n out = out.replace(POSIX_PATH, (m) => pseudonymizeFragment(m, 'path', orgKey, db));\n // 3. user@host — tokenize the user and the host independently so both invert.\n out = out.replace(/\\b([a-z0-9._-]+)@((?:[a-z0-9-]+\\.)+[a-z]{2,})\\b/gi, (_m, user: string, host: string) =>\n `${pseudonymizeFragment(user, 'user', orgKey, db)}@${pseudonymizeFragment(host, 'host', orgKey, db)}`,\n );\n // 4. Remaining bare hostnames/domains.\n out = out.replace(HOSTNAME, (m) => pseudonymizeFragment(m, 'host', orgKey, db));\n return out;\n}\n\n/**\n * Recursive, structure-preserving walker — same shape as `redactValue`\n * (`src/tools.ts`): strings → {@link pseudonymizeString}, arrays → map,\n * objects → per-value recurse, primitives → unchanged. Object keys are left\n * verbatim (they are schema, not data), so topology shape is invariant.\n */\nexport function pseudonymize(value: unknown, orgKey: Buffer, db?: CartographyDB): unknown {\n if (typeof value === 'string') return pseudonymizeString(value, orgKey, db);\n if (Array.isArray(value)) return value.map((v) => pseudonymize(v, orgKey, db));\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = pseudonymize(v, orgKey, db);\n return out;\n }\n return value;\n}\n\n/**\n * Admin reversal: decrypt the original plaintext behind a pseudonym token, or\n * `undefined` when the token is unknown or the ciphertext fails GCM authentication\n * (tampered or produced under a different / rotated org key).\n */\nexport function reversePseudonym(token: string, orgKey: Buffer, db: CartographyDB): string | undefined {\n const encoded = db.getReversal(token);\n if (encoded == null) return undefined;\n const plaintext = decryptPlaintext(encoded, reversalKey(orgKey));\n if (plaintext === undefined) {\n logError('reversePseudonym: ciphertext failed authentication (tampered or wrong/rotated org key)');\n }\n return plaintext;\n}\n","/**\n * Org-key lifecycle for the 2.10 anonymization layer.\n *\n * The org key is the single secret that makes pseudonyms deterministic across\n * machines (so a central merge collapses the same host to one token) and\n * admin-reversible (only the key holder can invert a pseudonym). It lives on disk\n * at `~/.cartography/org-key` (mode 0600), **never** in the SQLite catalog, never\n * in any log line, and never in an export. The returned key is HKDF-namespaced by\n * `organization`, so two organizations sharing one machine derive non-colliding\n * keys (and therefore non-colliding tokens + isolated reversal maps).\n *\n * Zero new dependencies: HMAC/AES/HKDF/randomBytes all come from `node:crypto`.\n */\n\nimport { randomBytes, hkdfSync } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { logInfo, logWarn } from './logger.js';\n\nexport interface OrgKeyOptions {\n /** Organization namespace; absent ⇒ the `'default'` namespace. */\n organization?: string;\n /** Override the on-disk key path (tests / non-default data dirs). */\n keyPath?: string;\n}\n\n/** Default org-key file path: `~/.cartography/org-key`. */\nexport function orgKeyPath(home: string = homedir()): string {\n return join(home, '.cartography', 'org-key');\n}\n\n/** Bytes of the org-key file: 32 random bytes stored hex. */\nconst KEY_BYTES = 32;\n\n/** Read the raw file secret, or lazily create it (32 random bytes, hex, mode 0600). */\nfunction loadFileSecret(path: string): Buffer {\n if (existsSync(path)) {\n // Warn (never throw) if the key file is group/world-readable — it must be 0600.\n try {\n const mode = statSync(path).mode & 0o777;\n if (mode & 0o077) {\n logWarn('org-key file is not 0600 — restrict it: chmod 600 ' + path);\n }\n } catch {\n /* stat failure is non-fatal; fall through to the read */\n }\n const hex = readFileSync(path, 'utf8').trim();\n const buf = Buffer.from(hex, 'hex');\n if (buf.length === KEY_BYTES) return buf;\n // Corrupt/short key material: regenerate rather than derive a weak key.\n logWarn('org-key file was malformed — regenerating a fresh org key');\n }\n mkdirSync(dirname(path), { recursive: true });\n const secret = randomBytes(KEY_BYTES);\n writeFileSync(path, secret.toString('hex'), { mode: 0o600 });\n logInfo('created org key at ~/.cartography/org-key (mode 0600) — distribute to admins out-of-band');\n return secret;\n}\n\n/**\n * Load (or lazily create) the org key, HKDF-namespaced by `organization`. The\n * on-disk secret is a single random value; per-org keys are derived from it so\n * the same file serves multiple orgs without collision. Pure of side effects\n * beyond the lazy file creation; never logs the key material.\n */\nexport function loadOrgKey(opts: OrgKeyOptions = {}): Buffer {\n const path = opts.keyPath ?? orgKeyPath();\n const secret = loadFileSecret(path);\n const info = opts.organization && opts.organization.trim() ? opts.organization.trim() : 'default';\n return Buffer.from(hkdfSync('sha256', secret, Buffer.alloc(0), info, KEY_BYTES));\n}\n\n/**\n * Rotate the org key: overwrite the on-disk secret with fresh random bytes and\n * return the new derived key. Reversal entries written under the old key become\n * unrecoverable by design — callers are warned at the stderr level.\n */\nexport function rotateOrgKey(opts: OrgKeyOptions = {}): Buffer {\n const path = opts.keyPath ?? orgKeyPath();\n mkdirSync(dirname(path), { recursive: true });\n const secret = randomBytes(KEY_BYTES);\n writeFileSync(path, secret.toString('hex'), { mode: 0o600 });\n logWarn('org key rotated — pseudonym_reversal entries created under the previous key are now UNRECOVERABLE');\n const info = opts.organization && opts.organization.trim() ? opts.organization.trim() : 'default';\n return Buffer.from(hkdfSync('sha256', secret, Buffer.alloc(0), info, KEY_BYTES));\n}\n\n/** Subkey for HMAC pseudonym tokens (distinct domain from the reversal cipher key). */\nexport function hmacKey(orgKey: Buffer): Buffer {\n return Buffer.from(hkdfSync('sha256', orgKey, Buffer.from('cartography-hmac-v1'), 'hmac', 32));\n}\n\n/** Subkey for AES-256-GCM reversal-map encryption (distinct domain from the HMAC key). */\nexport function reversalKey(orgKey: Buffer): Buffer {\n return Buffer.from(hkdfSync('sha256', orgKey, Buffer.from('cartography-reversal-v1'), 'reversal', 32));\n}\n","/**\n * Server-side anonymization re-validation for the central collector (2.12).\n *\n * **Don't trust the client.** The client (2.10) is supposed to pseudonymize\n * identifying fragments before pushing at the `anonymized` sharing level, but the\n * collector independently re-checks every incoming payload and rejects or scrubs any\n * un-anonymized identifying fragment it finds. This is defense-in-depth: a buggy,\n * outdated, or malicious client cannot leak raw identifiers into the org-wide store.\n *\n * The walker mirrors `redactValue`/`pseudonymize` (it rewrites only leaf strings;\n * structure is invariant). What it flags at the `anonymized` level:\n * - **private-ip** — RFC-1918 / loopback IPv4 (`src/anonymize.ts:PRIVATE_IP` + 127/8).\n * - **absolute-path**— POSIX (`/home/alice/...`) or Windows (`C:\\Users\\alice\\...`) paths.\n * - **username** — the user segment of `/home/<u>`, `/Users/<u>`, `C:\\Users\\<u>`.\n * - **hostname** — multi-label FQDNs (`db-01.internal.acme.lan`), AND the known\n * 2.10 residual: **bare single-label internal hostnames** (e.g. `db-prod-01`) that\n * the client's multi-label-only HOSTNAME regex never tokenizes. A token already in\n * `anon:{kind}:{base32}` form is recognized as anonymized and never flagged.\n *\n * At the `none` / `full` levels nothing is claimed to be anonymized, so re-validation\n * is a no-op (returns no violations): the employee consented to share at that level.\n */\n\nimport { PRIVATE_IP } from '../anonymize.js';\nimport type { DiscoveryNode } from '../types.js';\n\n/** The anonymization level claimed by an ingest envelope (mirrors `SharingLevel`). */\nexport type AnonymizationLevel = 'none' | 'anonymized' | 'full';\n\n/** A detected un-anonymized identifying fragment, with its location for logging. */\nexport interface AnonViolation {\n /** JSON path to the offending leaf, e.g. `metadata.host`. */\n path: string;\n kind: 'hostname' | 'username' | 'absolute-path' | 'private-ip';\n}\n\n/** Loopback IPv4 (127.0.0.0/8) — identifying of a local machine; client never tokenizes it. */\nconst LOOPBACK_IP = /\\b127(?:\\.\\d{1,3}){3}\\b/g;\n\n/** Multi-label FQDN/domain (≥2 dot-separated labels) — the shape 2.10 tokenizes. */\nconst FQDN = /\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}\\b/gi;\n\n/** POSIX absolute path and the user segment of a home dir. */\nconst POSIX_PATH = /(?:^|(?<=\\s|=|:|\"|'|\\())(\\/[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)+)/g;\nconst WIN_PATH = /\\b[A-Za-z]:\\\\[A-Za-z0-9._\\\\-]+/g;\nconst HOME_USER = /(?:\\/home\\/|\\/Users\\/|[A-Za-z]:\\\\Users\\\\)([A-Za-z0-9._-]+)/g;\n\n/**\n * A bare single-label internal hostname — the known 2.10 residual. We only flag a\n * single label when it *looks* like an internal host: it contains a hyphen or a\n * digit run (e.g. `db-01`, `web2`, `prod-db`) so we do not false-positive ordinary\n * English words used as a `name`. An already-pseudonymized `anon:...` token has a\n * colon and is excluded by construction. Matched only when the whole string (after\n * trimming) is one such label — embedded prose is left alone to avoid over-scrubbing.\n */\nconst BARE_INTERNAL_HOST = /^[a-z0-9]+(?:-[a-z0-9]+)+$|^[a-z]+\\d+$|^\\d+[a-z]+$/i;\n\n/** A pseudonym token produced by 2.10 (`anon:host:abc…`) — already anonymized. */\nconst ANON_TOKEN = /^anon:(?:host|user|path|ip):[a-z2-7]+$/;\n\n/** Detect identifying fragments in a single leaf string at the `anonymized` level. */\nfunction violationsInString(s: string, path: string): AnonViolation[] {\n const out: AnonViolation[] = [];\n const trimmed = s.trim();\n if (trimmed === '' || ANON_TOKEN.test(trimmed)) return out;\n\n if (PRIVATE_IP.test(s) || LOOPBACK_IP.test(s)) out.push({ path, kind: 'private-ip' });\n PRIVATE_IP.lastIndex = 0; LOOPBACK_IP.lastIndex = 0;\n\n if (HOME_USER.test(s)) out.push({ path, kind: 'username' });\n HOME_USER.lastIndex = 0;\n\n if (WIN_PATH.test(s) || POSIX_PATH.test(s)) out.push({ path, kind: 'absolute-path' });\n WIN_PATH.lastIndex = 0; POSIX_PATH.lastIndex = 0;\n\n if (FQDN.test(s)) out.push({ path, kind: 'hostname' });\n FQDN.lastIndex = 0;\n\n // The residual: a bare single-label internal hostname not already caught above.\n if (out.length === 0 && BARE_INTERNAL_HOST.test(trimmed)) out.push({ path, kind: 'hostname' });\n\n return out;\n}\n\n/**\n * Recursively collect every anonymization violation in a value at the given level.\n * Pure. At `none`/`full` returns `[]` (no anonymization is claimed). Object keys are\n * schema, not data, so only values are inspected; `path` accumulates the location.\n */\nexport function findAnonViolations(value: unknown, level: AnonymizationLevel, path = ''): AnonViolation[] {\n if (level !== 'anonymized') return [];\n return collect(value, path);\n}\n\nfunction collect(value: unknown, path: string): AnonViolation[] {\n if (typeof value === 'string') return violationsInString(value, path || '(root)');\n if (Array.isArray(value)) return value.flatMap((v, i) => collect(v, `${path}[${i}]`));\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>).flatMap(([k, v]) => collect(v, path ? `${path}.${k}` : k));\n }\n return [];\n}\n\n/** Mask every leaf string that contains a violation to `***` (structure-preserving). */\nfunction scrub(value: unknown, path: string): unknown {\n if (typeof value === 'string') return violationsInString(value, path || '(root)').length > 0 ? '***' : value;\n if (Array.isArray(value)) return value.map((v, i) => scrub(v, `${path}[${i}]`));\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = scrub(v, path ? `${path}.${k}` : k);\n return out;\n }\n return value;\n}\n\n/**\n * Re-validate one node against its claimed anonymization level (2.12).\n *\n * - `mode: 'reject'` (default, strict) — returns the node unchanged plus the\n * violations; the caller drops a node with any violation and never persists it.\n * - `mode: 'strip'` (lenient) — returns a sanitized node (offending leaves masked to\n * `***`) plus the violations (for logging), so the topology shape survives while\n * the identifying fragments do not.\n *\n * Pure: no I/O, no logging (the caller logs). At `none`/`full` it is a pass-through.\n */\nexport function revalidateAnonymized(\n node: DiscoveryNode,\n level: AnonymizationLevel,\n mode: 'reject' | 'strip',\n): { node: DiscoveryNode; violations: AnonViolation[] } {\n const violations = [\n ...findAnonViolations(node.name, level, 'name'),\n ...findAnonViolations(node.id, level, 'id'),\n ...findAnonViolations(node.metadata ?? {}, level, 'metadata'),\n ...findAnonViolations(node.tags ?? [], level, 'tags'),\n ...findAnonViolations(node.domain ?? '', level, 'domain'),\n ...findAnonViolations(node.subDomain ?? '', level, 'subDomain'),\n ];\n if (violations.length === 0 || mode === 'reject') return { node, violations };\n // strip mode: mask the offending leaves while preserving structure.\n const scrubbed: DiscoveryNode = {\n ...node,\n name: scrub(node.name, 'name') as string,\n metadata: scrub(node.metadata ?? {}, 'metadata') as Record<string, unknown>,\n tags: (node.tags ?? []).map((t, i) => scrub(t, `tags[${i}]`) as string),\n ...(node.domain != null ? { domain: scrub(node.domain, 'domain') as string } : {}),\n ...(node.subDomain != null ? { subDomain: scrub(node.subDomain, 'subDomain') as string } : {}),\n };\n return { node: scrubbed, violations };\n}\n","/**\n * Ingest orchestration for the central collector (2.12).\n *\n * Consumes the 2.11 PUSH ENVELOPE verbatim —\n * `{ schemaVersion: 1, org?, items: [{ contentHash, kind: 'node'|'edge', payload }] }`\n * — and merges it into the tenant-partitioned central store. The pipeline is:\n *\n * 1. **Validate** the envelope shape (zod). A malformed envelope is rejected whole;\n * nothing is persisted (the HTTP layer turns this into a 400).\n * 2. **Re-validate anonymization** per node (`revalidateAnonymized`) — the client is\n * not trusted. In `reject` mode a node with any identifying fragment is dropped\n * and counted; in `strip` mode it is scrubbed and persisted. Either way a\n * structured WARN is logged with the violation path + action.\n * 3. **Merge** each node by `(org, globalId)` primary + `(org, contentHash)` secondary\n * with a contributor union (`store.upsertNode`), and insert edges (`store.insertEdge`)\n * only when both endpoints survived re-validation.\n * 4. **Log** one structured INFO with the per-ingest counts.\n *\n * The contributor `at` is stamped server-side (`new Date().toISOString()`) — never\n * trusted from the client. The envelope MAY carry a `contributor`/`anonymizationLevel`\n * extension (2.9/2.10); absent, the collector derives a conservative default and\n * re-validates at the `anonymized` level (the safe assumption).\n */\n\nimport { z } from 'zod';\nimport { NodeSchema, EdgeSchema } from '../types.js';\nimport type { Contributor } from '../types.js';\nimport { logInfo, logWarn } from '../logger.js';\nimport type { StoreBackend } from '../store/backend.js';\nimport { computeIdentity } from './merge.js';\nimport { revalidateAnonymized } from './anonymization.js';\nimport type { AnonymizationLevel } from './anonymization.js';\n\n/** Wire-format version this collector accepts (the 2.11 `PUSH_SCHEMA_VERSION`). */\nexport const INGEST_SCHEMA_VERSION = 1 as const;\n\n/** Hard cap on items per envelope — bounds memory before the per-delta transaction. */\nconst MAX_ITEMS = 50_000;\n\n/** Optional source-attribution extension an envelope may carry (2.9). */\nconst ContributorSchema = z.object({\n machineId: z.string().min(1),\n hostname: z.string().default('unknown'),\n user: z.string().default('unknown'),\n confidence: z.number().min(0).max(1).default(0.5),\n});\n\n/**\n * The ingest envelope — the 2.11 push contract, plus optional `contributor` /\n * `anonymizationLevel` extension fields (read when present; never required). `org`\n * is optional in the wire format; the collector falls back to its `defaultOrg`.\n */\nexport const IngestEnvelopeSchema = z.object({\n schemaVersion: z.literal(INGEST_SCHEMA_VERSION),\n org: z.string().min(1).optional(),\n items: z.array(z.object({\n contentHash: z.string(),\n kind: z.enum(['node', 'edge']),\n payload: z.unknown(),\n })).max(MAX_ITEMS),\n // Extensions (forward-compatible; 2.11 does not yet send these).\n contributor: ContributorSchema.optional(),\n anonymizationLevel: z.enum(['none', 'anonymized', 'full']).optional(),\n});\nexport type IngestEnvelope = z.infer<typeof IngestEnvelopeSchema>;\n\n/** Per-ingest outcome counts (the 200 response body). */\nexport interface IngestResult {\n org: string;\n /** Nodes persisted (created + merged). */\n accepted: number;\n /** Nodes that collapsed onto an existing logical node. */\n merged: number;\n /** Nodes dropped for anonymization violations (reject mode). */\n rejected: number;\n /** Edges persisted. */\n edges: number;\n /** Total anonymization violations detected across all nodes. */\n violations: number;\n}\n\nexport interface IngestOptions {\n /** `reject` (default) drops nodes with violations; `strip` scrubs and keeps them. */\n anonMode?: 'reject' | 'strip';\n /** Tenant used when the envelope omits `org`. */\n defaultOrg?: string;\n}\n\n/**\n * Ingest one validated envelope into the store. Returns the outcome counts.\n * The caller (HTTP handler) wraps this in try/catch; the store's per-node upsert is\n * itself transactional, so a single bad node never half-writes a row.\n */\nexport function ingestEnvelope(store: StoreBackend, envelope: IngestEnvelope, opts: IngestOptions = {}): IngestResult {\n const anonMode = opts.anonMode ?? 'reject';\n const org = envelope.org ?? opts.defaultOrg ?? 'local';\n const level: AnonymizationLevel = envelope.anonymizationLevel ?? 'anonymized';\n const at = new Date().toISOString();\n const contributor: Contributor = {\n machineId: envelope.contributor?.machineId ?? 'unknown',\n hostname: envelope.contributor?.hostname ?? 'unknown',\n user: envelope.contributor?.user ?? 'unknown',\n organization: org,\n at,\n confidence: envelope.contributor?.confidence ?? 0.5,\n };\n\n let accepted = 0;\n let merged = 0;\n let rejected = 0;\n let edges = 0;\n let violations = 0;\n /** Node ids that survived re-validation — an edge persists only if both endpoints did. */\n const acceptedNodeIds = new Set<string>();\n\n for (const item of envelope.items) {\n if (item.kind !== 'node') continue;\n const parsed = NodeSchema.safeParse(item.payload);\n if (!parsed.success) {\n rejected += 1;\n logWarn('ingest: dropped malformed node payload', { org, contentHash: item.contentHash });\n continue;\n }\n const node = parsed.data;\n const check = revalidateAnonymized(node, level, anonMode);\n if (check.violations.length > 0) {\n violations += check.violations.length;\n // The node id cannot be safely scrubbed — masking it to `***` would destroy the\n // merge identity (global_id derives from id). So an id-borne violation forces a\n // reject even in `strip` mode; strip only ever masks the safely-maskable leaves.\n const idViolation = check.violations.some((v) => v.path === 'id');\n if (anonMode === 'reject' || idViolation) {\n rejected += 1;\n logWarn('ingest: rejected node with un-anonymized fragments', {\n org, nodeId: node.id, action: 'reject',\n mode: anonMode, idViolation,\n kinds: check.violations.map((v) => `${v.path}:${v.kind}`),\n });\n continue;\n }\n logWarn('ingest: scrubbed un-anonymized fragments from node', {\n org, nodeId: node.id, action: 'strip',\n kinds: check.violations.map((v) => `${v.path}:${v.kind}`),\n });\n }\n const safe = check.node;\n const identity = computeIdentity(org, safe);\n const outcome = store.upsertNode(org, safe, identity, { ...contributor, confidence: safe.confidence });\n accepted += 1;\n if (outcome === 'merged') merged += 1;\n acceptedNodeIds.add(safe.id);\n }\n\n for (const item of envelope.items) {\n if (item.kind !== 'edge') continue;\n const parsed = EdgeSchema.safeParse(item.payload);\n if (!parsed.success) {\n logWarn('ingest: dropped malformed edge payload', { org, contentHash: item.contentHash });\n continue;\n }\n const edge = parsed.data;\n // Persist an edge only when both endpoints survived this envelope's node ingest.\n // An edge-only envelope (no nodes) trusts its endpoints exist from a prior ingest.\n if (acceptedNodeIds.size > 0 && (!acceptedNodeIds.has(edge.sourceId) || !acceptedNodeIds.has(edge.targetId))) {\n continue;\n }\n store.insertEdge(org, edge);\n edges += 1;\n }\n\n logInfo('ingest', { org, accepted, merged, rejected, edges, violations, level, anonMode });\n return { org, accepted, merged, rejected, edges, violations };\n}\n","/**\n * The central-collector ingest HTTP surface (2.12).\n *\n * {@link createIngestHandler} produces the request handler that the Streamable-HTTP\n * transport mounts at `POST /ingest` when server-mode is on. It is deliberately a\n * pure `(body) => { status, body }` function with no socket of its own, so it can be\n * unit-tested directly without binding a port (the transport owns auth, the\n * non-loopback host allowlist, and the body size cap — see `src/mcp/transports.ts`).\n *\n * Validation is fail-closed: a malformed envelope yields 400 (no writes); an internal\n * error yields 500; only a valid envelope runs `ingestEnvelope` and returns 200 with\n * the {@link IngestResult}. The bearer token is enforced by the transport before this\n * handler ever runs, so the handler never sees (and never logs) the token.\n */\n\nimport type { StoreBackend } from '../store/backend.js';\nimport { IngestEnvelopeSchema, ingestEnvelope } from './ingest.js';\nimport type { IngestResult, IngestOptions } from './ingest.js';\nimport { logWarn } from '../logger.js';\n\n/** A transport-agnostic HTTP-ish response: a status code and a JSON-serializable body. */\nexport interface IngestResponse {\n status: number;\n body: unknown;\n}\n\nexport type IngestHandler = (body: unknown) => IngestResponse;\n\n/**\n * Build the `/ingest` handler over a {@link StoreBackend}. The handler validates the\n * 2.11 push envelope, runs ingest (re-validating anonymization first), and maps the\n * outcome to an HTTP status:\n * - 400 — envelope failed `IngestEnvelopeSchema` (no writes).\n * - 500 — ingest threw (the store's per-node transaction rolls that node back).\n * - 200 — {@link IngestResult}.\n */\nexport function createIngestHandler(store: StoreBackend, opts: IngestOptions = {}): IngestHandler {\n return (body: unknown): IngestResponse => {\n const parsed = IngestEnvelopeSchema.safeParse(body);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);\n logWarn('ingest: rejected invalid envelope', { issues });\n return { status: 400, body: { error: 'invalid envelope', issues } };\n }\n try {\n const result: IngestResult = ingestEnvelope(store, parsed.data, opts);\n return { status: 200, body: result };\n } catch (err) {\n logWarn('ingest: failed', { error: err instanceof Error ? err.message : String(err) });\n return { status: 500, body: { error: 'ingest failed' } };\n }\n };\n}\n","import type { Scanner, ScanResult } from './types.js';\nimport type { DiscoveryNode } from '../types.js';\nimport { scanAllBookmarks } from '../bookmarks.js';\n\n/**\n * Hostname substrings that indicate a personal site — never catalogued, and the\n * hard floor for the 2.10 sharing policy (a personal host is forced to `'none'`\n * regardless of policy). Single-sourced here and shared via {@link isPersonalHost}\n * so the never-share list never forks.\n */\nexport const PERSONAL = [\n 'facebook.', 'instagram.', 'twitter.', 'x.com', 'tiktok.', 'reddit.', 'youtube.', 'netflix.',\n 'spotify.', 'twitch.', 'pinterest.', 'snapchat.', 'whatsapp.', 'amazon.', 'ebay.', 'aliexpress.',\n 'cnn.', 'bbc.', 'nytimes.', 'espn.', 'booking.', 'airbnb.', 'tripadvisor.', 'wikipedia.',\n];\n\n/** True when `host` matches a PERSONAL substring (case-insensitive). */\nexport function isPersonalHost(host: string): boolean {\n const h = host.toLowerCase();\n return PERSONAL.some((p) => h.includes(p));\n}\n\n/** Well-known business/SaaS hostnames → catalogued as saas_tool. */\nconst BUSINESS = [\n 'github.', 'gitlab.', 'bitbucket.', 'atlassian.', 'jira.', 'confluence.', 'notion.', 'linear.',\n 'slack.', 'zoom.', 'figma.', 'miro.', 'vercel.', 'netlify.', 'heroku.', 'datadog', 'sentry.',\n 'grafana.', 'pagerduty.', 'aws.amazon.', 'console.cloud.google', 'portal.azure', 'cloudflare.',\n 'hubspot.', 'salesforce.', 'stripe.', 'twilio.', 'sendgrid.', 'mailchimp.', 'segment.', 'mixpanel.',\n 'amplitude.', 'looker.', 'tableau.', 'snowflake.', 'databricks.', 'mongodb.', 'redis.', 'elastic.',\n 'openai.', 'anthropic.', 'huggingface.', 'docker.', 'npmjs.', 'pypi.', 'circleci.', 'travis-ci.',\n 'jenkins.', 'terraform.', 'hashicorp.', 'okta.', 'auth0.', '1password.', 'asana.', 'trello.', 'monday.',\n];\n\nfunction classify(hostname: string): { type: DiscoveryNode['type']; confidence: number } | null {\n const h = hostname.toLowerCase();\n if (isPersonalHost(h)) return null;\n if (BUSINESS.some((b) => h.includes(b))) return { type: 'saas_tool', confidence: 0.7 };\n // Internal/custom hosts (IPs or *.company.tld with non-standard ports) → web_service\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(h) || /\\.(internal|local|corp|lan)\\b/.test(h)) {\n return { type: 'web_service', confidence: 0.6 };\n }\n return null; // unknown public host — leave for LLM-driven classification\n}\n\nexport const bookmarksScanner: Scanner = {\n id: 'bookmarks',\n title: 'Browser bookmarks',\n platforms: 'all',\n detect: () => true,\n async scan(ctx): Promise<ScanResult> {\n const hosts = await (ctx.scanBookmarks ?? scanAllBookmarks)();\n const seen = new Set<string>();\n const nodes: DiscoveryNode[] = [];\n for (const host of hosts) {\n const klass = classify(host.hostname);\n if (!klass) continue;\n const id = `${klass.type}:${host.hostname}`;\n if (seen.has(id)) continue;\n seen.add(id);\n nodes.push({\n id, type: klass.type, name: host.hostname, discoveredVia: 'bookmark',\n confidence: klass.confidence, tags: ['bookmark'],\n metadata: { protocol: host.protocol, ...(host.port ? { port: host.port } : {}) },\n });\n }\n return { nodes, edges: [] };\n },\n};\n","/**\n * Sharing classifier + pre-send preview (2.10).\n *\n * Resolves the effective sharing level for each node (a PERSONAL-host hard floor\n * over the persisted policy), applies that level (`none` drops, `anonymized`\n * pseudonymizes, `full` keeps raw), and builds {@link previewShare} — the exact,\n * topology-shape-preserving payload that *would* leave the machine. This is the\n * privacy gate 2.11/2.12 build on; nothing here performs any network I/O.\n */\n\nimport type { DiscoveryNode, NodeRow, EdgeRow, SharingLevel, SharingPolicy } from './types.js';\nimport type { CartographyDB } from './db.js';\nimport { pseudonymizeString, pseudonymize } from './anonymize.js';\nimport { isPersonalHost } from './scanners/bookmarks.js';\n\n/** Count of `*`/`**` wildcards in a pattern (fewer ⇒ more specific). */\nfunction wildcardCount(pattern: string): number {\n return (pattern.match(/\\*/g) ?? []).length;\n}\n\n/**\n * Glob match over a node id. `*` matches any run of non-`:` chars within a\n * segment; `**` matches any chars including `:`. Anchored full match.\n */\nfunction globMatch(pattern: string, id: string): boolean {\n // Build a regex: ** → .*, * → [^:]*, everything else escaped literally.\n let re = '^';\n for (let i = 0; i < pattern.length; i++) {\n const c = pattern[i];\n if (c === '*') {\n if (pattern[i + 1] === '*') { re += '.*'; i++; }\n else re += '[^:]*';\n } else {\n re += c.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n }\n re += '$';\n return new RegExp(re).test(id);\n}\n\n/**\n * Resolve the policy-level for a node id: the most-specific matching override\n * wins (fewest wildcards, then longest pattern); ties and no-match fall through\n * to `defaultLevel`. The `'*'`/`'**'` rows are the global default and always lose\n * to any narrower match. Pure and deterministic.\n */\nexport function resolveSharingLevel(nodeId: string, policy: SharingPolicy): SharingLevel {\n const matches = policy.overrides\n .filter((o) => o.pattern !== '*' && o.pattern !== '**' && globMatch(o.pattern, nodeId))\n .sort((a, b) =>\n wildcardCount(a.pattern) - wildcardCount(b.pattern) ||\n b.pattern.length - a.pattern.length,\n );\n return matches.length ? matches[0].level : policy.defaultLevel;\n}\n\n/** Hostnames embedded in a node (id, name, and host/url metadata) for the PERSONAL floor. */\nfunction nodeHosts(node: DiscoveryNode): string[] {\n const out: string[] = [node.id, node.name];\n const meta = node.metadata ?? {};\n for (const k of ['host', 'url', 'domain'] as const) {\n const v = (meta as Record<string, unknown>)[k];\n if (typeof v === 'string') out.push(v);\n }\n if (node.domain) out.push(node.domain);\n return out;\n}\n\n/**\n * The effective level for a node: a PERSONAL-host hard floor (single-sourced from\n * the bookmarks never-share list) forces `'none'` regardless of policy; otherwise\n * the policy's resolved level for the node id applies.\n */\nexport function resolveEffectiveLevel(node: DiscoveryNode, policy: SharingPolicy): SharingLevel {\n if (nodeHosts(node).some((h) => isPersonalHost(h))) return 'none';\n return resolveSharingLevel(node.id, policy);\n}\n\n/**\n * Apply a sharing level to one node:\n * - `none` → `null` (the node is dropped, never leaves).\n * - `anonymized` → a clone with `id`/`name` pseudonymized and `metadata` walked\n * (structure-preserving); identifying fragments tokenized.\n * - `full` → a structural clone, verbatim.\n *\n * The same deterministic `pseudonymizeString` is applied to the id here and by\n * {@link previewShare} when remapping edges, so endpoints always resolve.\n */\nexport function applySharingLevel(\n node: DiscoveryNode,\n level: SharingLevel,\n orgKey: Buffer,\n db?: CartographyDB,\n): DiscoveryNode | null {\n if (level === 'none') return null;\n if (level === 'full') return { ...node, metadata: { ...(node.metadata ?? {}) }, tags: [...(node.tags ?? [])] };\n return {\n ...node,\n id: pseudonymizeString(node.id, orgKey, db),\n name: pseudonymizeString(node.name, orgKey, db),\n metadata: pseudonymize(node.metadata ?? {}, orgKey, db) as Record<string, unknown>,\n tags: (node.tags ?? []).map((t) => pseudonymizeString(t, orgKey, db)),\n };\n}\n\nexport interface SharePreviewEntry {\n /** The original (un-anonymized) node, for the operator's side-by-side disclosure. */\n node: DiscoveryNode;\n level: SharingLevel;\n /** What would actually leave: the transformed node, or `null` when dropped. */\n payload: DiscoveryNode | null;\n}\n\nexport interface SharePreview {\n nodes: SharePreviewEntry[];\n /** Edges that would leave, with endpoints remapped to surviving (transformed) ids. */\n edges: { sourceId: string; targetId: string; relationship: string }[];\n /** Original ids of nodes dropped at level `none`. */\n droppedNodeIds: string[];\n}\n\n/**\n * Build the exact payload that would leave for a session: resolve each node's\n * effective level, apply it, and remap edge endpoints through the same id\n * transform. An edge survives only when both endpoints survive — so when no node\n * is dropped the node count, edge count, and relationship multiset are identical\n * before/after; when some are dropped the result is a well-defined subgraph.\n *\n * `opts.persistReversal` (default false) controls whether anonymized fragments are\n * written to the reversal map — pure preview need not persist; an actual\n * pre-send transform should, so an admin can later invert.\n */\nexport function previewShare(\n db: CartographyDB,\n sessionId: string,\n orgKey: Buffer,\n policy: SharingPolicy,\n opts: { persistReversal?: boolean } = {},\n): SharePreview {\n const persist = opts.persistReversal ? db : undefined;\n const nodes: NodeRow[] = db.getNodes(sessionId);\n const edges: EdgeRow[] = db.getEdges(sessionId);\n\n const entries: SharePreviewEntry[] = [];\n const idMap = new Map<string, string | null>(); // original id → new id (null = dropped)\n const droppedNodeIds: string[] = [];\n\n for (const node of nodes) {\n const level = resolveEffectiveLevel(node, policy);\n const payload = applySharingLevel(node, level, orgKey, persist);\n entries.push({ node, level, payload });\n if (payload === null) {\n idMap.set(node.id, null);\n droppedNodeIds.push(node.id);\n } else {\n idMap.set(node.id, payload.id);\n }\n }\n\n const outEdges: { sourceId: string; targetId: string; relationship: string }[] = [];\n for (const e of edges) {\n const src = idMap.get(e.sourceId);\n const tgt = idMap.get(e.targetId);\n // Drop an edge if either endpoint is dropped or unknown (not in this session's nodes).\n if (src == null || tgt == null) continue;\n outEdges.push({ sourceId: src, targetId: tgt, relationship: e.relationship });\n }\n\n return { nodes: entries, edges: outEdges, droppedNodeIds };\n}\n\n/**\n * The seam 2.11 will use to suppress `ask_user` re-prompts: true when the node id\n * is already covered by the persisted policy — either a matching override or a\n * non-`none` default — so a matched item is never re-prompted.\n */\nexport function isRemembered(policy: SharingPolicy, nodeId: string): boolean {\n const matched = policy.overrides.some((o) => o.pattern !== '*' && o.pattern !== '**' && globMatch(o.pattern, nodeId));\n return matched || policy.defaultLevel !== 'none';\n}\n","/**\n * The Cartography MCP server — the package's primary, LLM-agnostic interface.\n *\n * It exposes the discovered infrastructure topology as Model Context Protocol\n * **Resources** (read-only context, progressive disclosure), a small set of query\n * **Tools** (parameterized lookups), and reusable **Prompts**. Any MCP host —\n * Claude Code, Cursor, Cline, Windsurf, the Vercel AI SDK, LangGraph — can drive\n * it; the package never needs to know which model is in use.\n */\n\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { CartographyDB, deriveSessionName, normalizeTenant } from '../db.js';\nimport type { GraphSummary, OrgSummary } from '../db.js';\nimport { defaultConfig, NODE_TYPES, NODE_TYPE_GROUPS } from '../types.js';\nimport type { NodeRow } from '../types.js';\nimport type { TopologyDelta } from '../diff.js';\nimport { classifyDrift, filterBySeverity } from '../drift.js';\nimport { redactValue } from '../tools.js';\nimport { getRuleset, listRulesets } from '../compliance/rulesets/registry.js';\nimport { parseNlQuery, executeNlQuery } from '../nlq/index.js';\n\nconst SERVER_NAME = 'cartography';\nconst SERVER_VERSION = '2.2.0';\n\nconst SERVICE_TYPES = NODE_TYPE_GROUPS.web;\nconst DATA_TYPES = NODE_TYPE_GROUPS.data;\n\n/** A pluggable search backend; defaults to lexical search, can be upgraded to semantic. */\nexport type SearchFn = (\n db: CartographyDB,\n sessionId: string,\n query: string,\n opts: { types?: readonly string[]; limit: number },\n) => Promise<Array<{ node: NodeRow; score?: number }>>;\n\n/** A pluggable discovery backend invoked by the `run_discovery` tool. */\nexport type DiscoveryFn = (\n db: CartographyDB,\n sessionId: string,\n opts: { hint?: string; mode?: 'replace' | 'update' },\n) => Promise<{ nodes: number; edges: number; delta?: TopologyDelta }>;\n\nexport interface CreateMcpServerOptions {\n /** Database instance. If omitted, one is opened at `config.dbPath`. */\n db?: CartographyDB;\n /** Path to the SQLite catalog (used when `db` is not provided). */\n dbPath?: string;\n /** Session to serve: a session id, or `'latest'` (default) for the newest discovery. */\n session?: string | 'latest';\n /**\n * Tenant/organization whose topology this server serves. Defaults to DEFAULT_TENANT\n * (`'local'`). Session resolution is scoped to this tenant, so a server bound to one\n * tenant never surfaces another tenant's sessions/nodes/edges. This is a data-scoping\n * partition, not an authorization boundary (RBAC is Phase 4).\n */\n tenant?: string;\n /** Semantic/lexical search backend. Defaults to lexical `searchNodes`. */\n search?: SearchFn;\n /** Discovery backend for `run_discovery`/`refresh`. Optional. */\n discovery?: DiscoveryFn;\n /**\n * Central-collector org (2.12). When set, `get_summary` returns the org-wide,\n * cross-machine merged summary (`db.getOrgSummary(org)`) instead of the single-\n * session view. Used by server-mode; unset preserves the local single-session\n * behaviour exactly. The org is normalized to a tenant.\n */\n org?: string;\n}\n\nconst lexicalSearch: SearchFn = async (db, sessionId, query, opts) =>\n db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));\n\n/** Compact projection of a node for tool results (token-economical). */\nfunction compactNode(n: NodeRow): Record<string, unknown> {\n return {\n id: n.id,\n type: n.type,\n name: n.name,\n confidence: n.confidence,\n ...(n.domain ? { domain: n.domain } : {}),\n ...(n.tags.length ? { tags: n.tags } : {}),\n };\n}\n\nfunction json(data: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };\n}\n\n/** True for the org-wide (central-collector) summary shape. */\nfunction isOrgSummary(s: GraphSummary | OrgSummary): s is OrgSummary {\n return 'org' in s;\n}\n\nfunction summaryText(s: GraphSummary | OrgSummary): string {\n const header = isOrgSummary(s)\n ? `# Organization topology — org ${s.org} (${s.contributors} contributor${s.contributors === 1 ? '' : 's'})`\n : `# Infrastructure topology — session ${s.sessionId}`;\n const lines = [\n header,\n ``,\n `Totals: ${s.totals.nodes} nodes, ${s.totals.edges} edges`,\n ``,\n `Nodes by type:`,\n ...Object.entries(s.nodesByType).sort((a, b) => b[1] - a[1]).map(([t, c]) => ` - ${t}: ${c}`),\n ``,\n `Nodes by domain:`,\n ...Object.entries(s.nodesByDomain).sort((a, b) => b[1] - a[1]).map(([d, c]) => ` - ${d}: ${c}`),\n ``,\n `Edges by relationship:`,\n ...Object.entries(s.edgesByRelationship).sort((a, b) => b[1] - a[1]).map(([r, c]) => ` - ${r}: ${c}`),\n ``,\n `Most connected:`,\n ...s.topConnected.map((n) => ` - ${n.id} (${n.type}) — degree ${n.degree}`),\n // 3.6 anomalies — single-session GraphSummary only.\n ...(!isOrgSummary(s)\n ? [``, `Anomalies (${s.anomalies.length}):`,\n ...(s.anomalies.length === 0\n ? [' - none']\n : s.anomalies.slice(0, 20).map((a) => ` - [${a.severity}] ${a.kind}: ${a.nodeId} — ${a.reason}`))]\n : []),\n // 3.3 cost section — only for the single-session GraphSummary, and only when costs exist.\n ...(!isOrgSummary(s) && s.costByDomain.length\n ? [``, `Cost by domain:`, ...s.costByDomain.map((c) => ` - ${c.domain} [${c.currency}/${c.period}]: ${c.total} (${c.nodes} nodes)`)]\n : []),\n ...(!isOrgSummary(s) && s.costCoverage.withCost\n ? [``, `Cost coverage: ${s.costCoverage.withCost}/${s.costCoverage.total} nodes attributed`]\n : []),\n ``,\n `Read cartography://nodes/{id} or cartography://dependencies/{id} for detail.`,\n ];\n return lines.join('\\n');\n}\n\n/**\n * Build a fully-configured Cartography MCP server. Call `.connect(transport)` to run it.\n */\nexport function createMcpServer(opts: CreateMcpServerOptions = {}): McpServer {\n const db = opts.db ?? new CartographyDB(opts.dbPath ?? defaultConfig().dbPath);\n const search = opts.search ?? lexicalSearch;\n const tenant = normalizeTenant(opts.tenant);\n // Central-collector org (2.12): when set, get_summary serves the org-wide merged\n // view; otherwise the single-session view is preserved unchanged.\n const org = opts.org !== undefined ? normalizeTenant(opts.org) : undefined;\n\n /**\n * Resolve the served session id at call time (so late discoveries are picked up),\n * scoped to the served `tenant`. An explicit session id must belong to the served\n * tenant or it resolves to undefined — so a client bound to tenant A can never read\n * a session owned by tenant B, even by naming its id.\n */\n const resolveSession = (): string | undefined => {\n if (opts.session && opts.session !== 'latest') {\n const s = db.getSession(opts.session);\n return s && s.tenant === tenant ? s.id : undefined;\n }\n return db.getLatestSession('discover', tenant)?.id ?? db.getLatestSession(undefined, tenant)?.id;\n };\n\n const server = new McpServer(\n { name: SERVER_NAME, version: SERVER_VERSION },\n {\n capabilities: { resources: { subscribe: true, listChanged: true }, tools: {}, prompts: {}, logging: {} },\n instructions:\n 'Cartography exposes a discovered infrastructure/SaaS topology. Start by reading ' +\n 'cartography://graph/summary for a low-token overview, then drill into specific nodes ' +\n 'via cartography://nodes/{id} or query with the query_infrastructure / get_dependencies tools.',\n },\n );\n\n // ── Resources (read-only context, progressive disclosure) ──────────────────\n\n server.registerResource(\n 'graph-summary',\n 'cartography://graph/summary',\n { title: 'Topology summary', description: 'Low-token aggregate index of the whole landscape — read this first.', mimeType: 'text/markdown' },\n (uri) => {\n if (org !== undefined) {\n return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: summaryText(db.getOrgSummary(org)) }] };\n }\n const sid = resolveSession();\n if (!sid) return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: 'No discovery session found. Run discovery first.' }] };\n return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: summaryText(db.getGraphSummary(sid)) }] };\n },\n );\n\n server.registerResource(\n 'cost-summary',\n 'cartography://cost/summary',\n { title: 'Cost summary', description: 'Cost rolled up by domain and owner (currency/period-bucketed).', mimeType: 'application/json' },\n (uri) => {\n const sid = resolveSession();\n if (!sid) return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ error: 'No discovery session found.' }) }] };\n const s = db.getGraphSummary(sid);\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ costByDomain: s.costByDomain, costByOwner: s.costByOwner, costCoverage: s.costCoverage }, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'nodes-index',\n 'cartography://nodes',\n { title: 'Node index', description: 'Lightweight list of all nodes (id, type, name only).', mimeType: 'application/json' },\n (uri) => {\n const sid = resolveSession();\n const nodes = sid ? db.getNodes(sid) : [];\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ count: nodes.length, nodes: nodes.map((n) => ({ id: n.id, type: n.type, name: n.name })) }, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'node-detail',\n new ResourceTemplate('cartography://nodes/{id}', { list: undefined }),\n { title: 'Node detail', description: 'Full node record plus its incident edges.', mimeType: 'application/json' },\n (uri, variables) => {\n const sid = resolveSession();\n const id = decodeURIComponent(String(variables['id']));\n const node = sid ? db.getNode(sid, id) : undefined;\n if (!node) return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ error: `node not found: ${id}` }) }] };\n const edges = db.getEdges(sid!).filter((e) => e.sourceId === id || e.targetId === id);\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ node, edges }, null, 2) }] };\n },\n );\n\n const typedListResource = (name: string, uri: string, title: string, types: readonly string[]) =>\n server.registerResource(name, uri, { title, description: `Nodes of type: ${types.join(', ')}.`, mimeType: 'application/json' }, (u) => {\n const sid = resolveSession();\n const nodes = sid ? db.getNodesByType(sid, types) : [];\n return { contents: [{ uri: u.href, mimeType: 'application/json', text: JSON.stringify({ count: nodes.length, nodes: nodes.map(compactNode) }, null, 2) }] };\n });\n\n typedListResource('services', 'cartography://services', 'Services', SERVICE_TYPES);\n typedListResource('databases', 'cartography://databases', 'Data stores', DATA_TYPES);\n\n server.registerResource(\n 'dependencies',\n new ResourceTemplate('cartography://dependencies/{id}', { list: undefined }),\n { title: 'Dependencies', description: 'Transitive downstream dependencies of a node.', mimeType: 'application/json' },\n (uri, variables) => {\n const sid = resolveSession();\n const id = decodeURIComponent(String(variables['id']));\n if (!sid) return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ error: 'no session' }) }] };\n const r = db.getDependencies(sid, id, { direction: 'downstream', maxDepth: 8 });\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({\n root: id,\n count: r.nodes.length,\n nodes: r.nodes.map((n) => ({ ...compactNode(n), depth: n.depth })),\n edges: r.edges.map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship, confidence: e.confidence, evidence: e.evidence })),\n }, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'sessions',\n 'cartography://sessions',\n { title: 'Discovery sessions', description: 'All discovery sessions in the catalog.', mimeType: 'application/json' },\n (uri) => ({ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(db.getSessions(tenant), null, 2) }] }),\n );\n\n // ── Tools (model-controlled queries) ───────────────────────────────────────\n\n /** Annotations shared by every read-only query tool (untrusted hints, never a security boundary). */\n const readOnly = { readOnlyHint: true, openWorldHint: false } as const;\n\n server.registerTool(\n 'get_summary',\n { title: 'Get topology summary', description: 'Low-token overview of the whole landscape (counts, types, domains, most-connected, anomalies).', inputSchema: {}, annotations: readOnly },\n () => {\n if (org !== undefined) return json(db.getOrgSummary(org));\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n return json(db.getGraphSummary(sid));\n },\n );\n\n server.registerTool(\n 'get_cost_summary',\n { title: 'Get cost summary', description: 'FinOps rollup: cost by domain and owner, currency/period-bucketed (3.3).', inputSchema: {}, annotations: readOnly },\n () => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const s = db.getGraphSummary(sid);\n return json({ costByDomain: s.costByDomain, costByOwner: s.costByOwner, costCoverage: s.costCoverage });\n },\n );\n\n server.registerTool(\n 'query_infrastructure',\n {\n title: 'Query infrastructure',\n description: 'Search the topology by name/id/domain (optionally filtered by node type). Returns compact node records.',\n inputSchema: {\n query: z.string().describe('Free-text query, e.g. \"postgres\", \"auth\", \"github\"'),\n types: z.array(z.enum(NODE_TYPES)).optional().describe('Restrict to these node types'),\n limit: z.number().int().min(1).max(200).default(25).optional(),\n },\n annotations: readOnly,\n },\n async (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const results = await search(db, sid, args.query, { types: args.types, limit: args.limit ?? 25 });\n return json({ count: results.length, results: results.map((r) => ({ ...compactNode(r.node), ...(r.score !== undefined ? { score: r.score } : {}) })) });\n },\n );\n\n server.registerTool(\n 'search_topology',\n {\n title: 'Search topology (semantic)',\n description: 'Find nodes related to a concept by meaning (semantic search when available, lexical otherwise).',\n inputSchema: { query: z.string(), limit: z.number().int().min(1).max(100).default(10).optional() },\n annotations: readOnly,\n },\n async (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const results = await search(db, sid, args.query, { limit: args.limit ?? 10 });\n return json({ count: results.length, results: results.map((r) => ({ ...compactNode(r.node), ...(r.score !== undefined ? { score: r.score } : {}) })) });\n },\n );\n\n server.registerTool(\n 'list_services',\n {\n title: 'List services',\n description: 'List discovered services or data stores.',\n inputSchema: { kind: z.enum(['services', 'databases', 'all']).default('all').optional() },\n annotations: readOnly,\n },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const kind = args.kind ?? 'all';\n const types = kind === 'services' ? SERVICE_TYPES : kind === 'databases' ? DATA_TYPES : [...SERVICE_TYPES, ...DATA_TYPES];\n return json(db.getNodesByType(sid, types).map(compactNode));\n },\n );\n\n server.registerTool(\n 'get_node',\n { title: 'Get node', description: 'Fetch a single node with its incident edges.', inputSchema: { id: z.string() }, annotations: readOnly },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const node = db.getNode(sid, args.id);\n if (!node) return json({ error: `node not found: ${args.id}` });\n const edges = db.getEdges(sid).filter((e) => e.sourceId === args.id || e.targetId === args.id);\n return json({ node, edges });\n },\n );\n\n server.registerTool(\n 'get_dependencies',\n {\n title: 'Get dependencies',\n description: 'Traverse the dependency graph from a node (downstream/upstream/both) with a depth limit.',\n inputSchema: {\n id: z.string(),\n direction: z.enum(['downstream', 'upstream', 'both']).default('downstream').optional(),\n maxDepth: z.number().int().min(1).max(64).default(8).optional(),\n minConfidence: z.number().min(0).max(1).optional().describe('Drop edges below this confidence (0..1).'),\n },\n annotations: readOnly,\n },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const r = db.getDependencies(sid, args.id, { direction: args.direction ?? 'downstream', maxDepth: args.maxDepth ?? 8 });\n const minConfidence = args.minConfidence ?? 0;\n return json({\n root: r.root ? compactNode(r.root) : null,\n direction: r.direction,\n count: r.nodes.length,\n nodes: r.nodes.map((n) => ({ ...compactNode(n), depth: n.depth })),\n edges: r.edges\n .filter((e) => e.confidence >= minConfidence)\n .map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship, confidence: e.confidence, evidence: e.evidence })),\n });\n },\n );\n\n server.registerTool(\n 'query_natural_language',\n {\n title: 'Query in natural language',\n description:\n 'Answer a plain-English topology question (e.g. \"services that depend on the payments DB\"). ' +\n 'Deterministically parses the question into a structured intent, then anchors via search and ' +\n 'traverses dependencies, applying any node-type filter to the results. Echoes the parsed ' +\n 'intent for explainability. Read-only, LLM-free.',\n inputSchema: {\n query: z.string().min(1).max(1000).describe('Natural-language question about the topology'),\n maxDepth: z.number().int().min(1).max(64).default(8).optional(),\n },\n annotations: readOnly,\n },\n async (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const intent = parseNlQuery(args.query);\n const r = await executeNlQuery(db, sid, search, intent, { maxDepth: args.maxDepth ?? 8 });\n return json({\n intent: {\n query: r.intent.query, subjectQuery: r.intent.subjectQuery,\n relation: r.intent.relation, direction: r.intent.direction ?? null,\n typeFilter: r.intent.typeFilter ?? null, degraded: r.intent.degraded,\n },\n anchors: r.anchors.map((a) => ({ ...compactNode(a.node), ...(a.score !== undefined ? { score: a.score } : {}) })),\n count: r.nodes.length,\n nodes: r.nodes.map((n) => ({ ...compactNode(n), ...(n.depth !== undefined ? { depth: n.depth } : {}) })),\n paths: r.paths.map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship })),\n });\n },\n );\n\n server.registerTool(\n 'diff_topology',\n {\n title: 'Diff topology (drift detection)',\n description:\n 'Compare two discovery sessions and report added/removed/changed nodes and added/removed edges, ' +\n 'plus newly-appearing structural anomalies (3.6). ' +\n 'Defaults to the two most recent sessions (base = second-most-recent, current = most-recent).',\n inputSchema: {\n base: z.string().optional().describe('Baseline session id (default: second-most-recent session)'),\n current: z.string().optional().describe('Current session id (default: most-recent session)'),\n },\n annotations: readOnly,\n },\n (args) => {\n // getSessions(tenant) is ordered newest-first (rowid DESC): [0] = latest, [1] = previous.\n const sessions = db.getSessions(tenant);\n const currentId = args.current ?? sessions[0]?.id;\n const baseId = args.base ?? sessions[1]?.id;\n if (!baseId || !currentId) return json({ error: 'Need at least two discovery sessions to diff.' });\n if (baseId === currentId) return json({ error: 'Base and current session are the same.' });\n const d = db.diffSessions(baseId, currentId);\n return json({\n base: d.base,\n current: d.current,\n summary: d.summary,\n nodes: {\n added: d.nodes.added.map(compactNode),\n removed: d.nodes.removed.map(compactNode),\n changed: d.nodes.changed.map((c) => ({ ...compactNode(c.after), changedFields: c.changedFields })),\n },\n edges: {\n added: d.edges.added.map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship })),\n removed: d.edges.removed.map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship })),\n },\n anomalies: {\n added: d.anomalies.added,\n baseCount: d.anomalies.base.length,\n currentCount: d.anomalies.current.length,\n },\n });\n },\n );\n\n server.registerTool(\n 'classify_drift',\n {\n title: 'Classify drift (severity-ranked drift detection)',\n description:\n 'Compare two discovery sessions and return a severity-classified drift alert ' +\n '(info|warning|critical per item plus an overall severity). Defaults to the two most recent. ' +\n 'Read-only: never dispatches to sinks.',\n inputSchema: {\n base: z.string().optional().describe('Baseline session id (default: second-most-recent)'),\n current: z.string().optional().describe('Current session id (default: most-recent)'),\n minSeverity: z.enum(['info', 'warning', 'critical']).optional().describe('Drop items below this severity'),\n },\n annotations: readOnly,\n },\n (args) => {\n // getSessions(tenant) is newest-first (rowid DESC): [0] = latest, [1] = previous.\n const sessions = db.getSessions(tenant);\n const currentId = args.current ?? sessions[0]?.id;\n const baseId = args.base ?? sessions[1]?.id;\n if (!baseId || !currentId) return json({ error: 'Need at least two discovery sessions to diff.' });\n if (baseId === currentId) return json({ error: 'Base and current session are the same.' });\n let alert = classifyDrift(db.diffSessions(baseId, currentId));\n if (args.minSeverity) alert = filterBySeverity(alert, args.minSeverity);\n return json(redactValue(alert));\n },\n );\n\n server.registerTool(\n 'score_compliance',\n {\n title: 'Score compliance',\n description:\n 'Grade the served session against a compliance ruleset (baseline/cis/soc2/iso27001 starter ' +\n 'sets) and list gaps with the node ids that caused them. Read-only; never throws.',\n inputSchema: {\n ruleset: z.string().default('baseline').optional().describe('Ruleset name (default: baseline)'),\n session: z.string().optional().describe('Session id (default: the served session)'),\n },\n annotations: readOnly,\n },\n (args) => {\n const sid = args.session ?? resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const name = args.ruleset ?? 'baseline';\n const rs = getRuleset(name);\n if (!rs) return json({ error: `Unknown ruleset: ${name}`, available: listRulesets().map((r) => r.name) });\n return json(db.scoreSession(sid, rs));\n },\n );\n\n server.registerTool(\n 'get_activity_events',\n {\n title: 'Get activity events (audit trail)',\n description: 'Recent executed tool calls and their result sizes for the current session.',\n inputSchema: { limit: z.number().int().min(1).max(500).default(50).optional() },\n annotations: readOnly,\n },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const events = db.getEvents(sid).slice(-(args.limit ?? 50));\n return json({ count: events.length, events });\n },\n );\n\n if (opts.discovery) {\n const discovery = opts.discovery;\n server.registerTool(\n 'run_discovery',\n {\n title: 'Run discovery',\n description: 'Scan the local system (read-only) and update the catalog. Returns counts of nodes/edges found. Pass `update: true` to rescan the served session in place and return the delta (2.1 incremental discovery).',\n inputSchema: {\n hint: z.string().optional().describe('Optional focus, e.g. tool names to look for'),\n update: z.boolean().optional().describe('Rescan the served session in place and return the delta instead of creating a new session'),\n },\n // Scans read-only but writes results to the local catalog, so not a read-only tool; never destructive.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args) => {\n let sid = resolveSession();\n if (args.update) {\n // Incremental rescan needs an existing served session; never silently create one.\n if (!sid) return json({ error: 'No session to update; run discovery first.' });\n } else if (!sid) {\n sid = db.createSession('discover', defaultConfig(), tenant);\n }\n const result = await discovery(db, sid, { hint: args.hint, mode: args.update ? 'update' : 'replace' });\n // Give the session a deterministic, human-friendly name once it has content.\n // The `!sess.name` guard means an in-place rescan never clobbers a name.\n const sess = db.getSession(sid);\n if (sess && !sess.name) db.setSessionName(sid, deriveSessionName(db.getGraphSummary(sid), sess.startedAt));\n server.server.sendResourceUpdated({ uri: 'cartography://graph/summary' }).catch((err: unknown) => {\n process.stderr.write(`[cartography-mcp] resource update notification failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n });\n server.server.sendResourceListChanged?.();\n return json({\n session: sid, nodes: result.nodes, edges: result.edges,\n ...(result.delta ? { summary: result.delta.summary } : {}),\n });\n },\n );\n }\n\n // ── Prompts (user-controlled templates) ────────────────────────────────────\n\n server.registerPrompt(\n 'audit-attack-surface',\n { title: 'Audit attack surface', description: 'Review the discovered topology for externally-reachable services and risky dependencies.' },\n () => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n 'Read cartography://graph/summary and cartography://services. Identify externally-reachable ' +\n 'services, data stores with broad inbound dependencies, and any node with low confidence that ' +\n 'warrants verification. Use get_dependencies to assess blast radius. Summarize the attack surface ' +\n 'and concrete hardening recommendations.' } }],\n }),\n );\n\n server.registerPrompt(\n 'map-service-dependencies',\n {\n title: 'Map service dependencies',\n description: 'Produce a dependency map for a given service.',\n argsSchema: { service: z.string().describe('Service node id or name') },\n },\n (args) => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n `Use query_infrastructure to locate \"${args.service}\", then get_dependencies (direction=both) to ` +\n `map everything it depends on and everything that depends on it. Present the result as a clear ` +\n `dependency tree and call out single points of failure.` } }],\n }),\n );\n\n server.registerPrompt(\n 'compare-environments',\n { title: 'Compare environments', description: 'Summarize infrastructure drift between two discovery snapshots.' },\n () => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n 'Call diff_topology to compare the two most recent discovery sessions. Summarize what was added, ' +\n 'removed, and changed. Flag newly externally-reachable services and removed dependencies that could ' +\n 'indicate an outage or decommission. Recommend what an operator should verify.' } }],\n }),\n );\n\n server.registerPrompt(\n 'onboard-to-system',\n { title: 'Onboard to system', description: 'Explain the system landscape to a new engineer.' },\n () => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n 'Read cartography://graph/summary, then cartography://services and cartography://databases. ' +\n 'Write a concise onboarding briefing for a new engineer: what the major systems are, how they ' +\n 'connect, which data stores are central, and where to look first.' } }],\n }),\n );\n\n server.registerPrompt(\n 'find-single-points-of-failure',\n { title: 'Find single points of failure', description: 'Rank chokepoints whose loss has the largest blast radius.' },\n () => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n 'Call get_summary and read topConnected (the most-connected nodes). For each, call get_dependencies ' +\n '(direction=both) to measure how many services depend on it. Identify single points of failure — nodes ' +\n 'whose loss would disconnect or degrade the largest blast radius — rank them by impact, and recommend ' +\n 'redundancy or mitigation for each.' } }],\n }),\n );\n\n server.registerPrompt(\n 'generate-runbook',\n {\n title: 'Generate operations runbook',\n description: 'Produce an operations/onboarding runbook from the topology.',\n argsSchema: { service: z.string().optional().describe('Optional service id/name to scope the runbook') },\n },\n (args) => ({\n messages: [{\n role: 'user', content: { type: 'text', text: args.service\n ? `Use query_infrastructure to locate \"${args.service}\", then get_dependencies (direction=both). Write an ` +\n `operations runbook for it: purpose, upstream/downstream dependencies, startup/shutdown order, health ` +\n `checks, common failure modes, and escalation steps.`\n : 'Read cartography://graph/summary, then call get_summary and list_services. Write a system-wide operations ' +\n 'runbook: major components, how they connect, critical data stores, startup/shutdown order, health checks, ' +\n 'and where an on-call engineer should look first.' } }],\n }),\n );\n\n return server;\n}\n","import type { DriftSink } from './types.js';\nimport type { DriftAlert } from '../types.js';\nimport { redactValue } from '../tools.js';\nimport { logInfo } from '../logger.js';\n\n/**\n * Default sink: writes one severity-tagged, credential-redacted JSON line to\n * **stdout** (the data channel) and a one-line diagnostic to **stderr**, keeping\n * the stdout/stderr discipline the rest of the CLI follows. Fully local — no\n * network egress.\n */\nexport class StdoutSink implements DriftSink {\n readonly name = 'stdout';\n\n async emit(alert: DriftAlert): Promise<void> {\n process.stdout.write(JSON.stringify(redactValue(alert)) + '\\n');\n logInfo('drift alert emitted', { sink: this.name, severity: alert.severity, items: alert.items.length });\n }\n}\n","import type { DriftSink } from './types.js';\nimport type { DriftAlert } from '../types.js';\nimport { redactValue, stripSensitive } from '../tools.js';\nimport { logWarn, logError } from '../logger.js';\n\nexport interface WebhookSinkOptions {\n url: string;\n token?: string;\n timeoutMs?: number;\n}\n\n/** Loopback hosts for which a plaintext `http:` webhook is permitted (local dev). */\nconst LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n\n/**\n * True if `url` is safe to POST to: `https:`, or `http:` only to a loopback host,\n * or any scheme when the documented `CARTOGRAPHY_ALLOW_INSECURE_SYNC=1` escape\n * hatch (test-only) is set. An unparseable URL is treated as insecure. Mirrors the\n * 2.11 `pushDeltas` guard so the drift feature never silently exfiltrates over the\n * wire in plaintext.\n */\nexport function isSecureWebhookUrl(url: string, env: NodeJS.ProcessEnv = process.env): boolean {\n if (env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1') return true;\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return false;\n }\n if (parsed.protocol === 'https:') return true;\n if (parsed.protocol === 'http:' && LOOPBACK_HOSTS.has(parsed.hostname)) return true;\n return false;\n}\n\n/**\n * Outbound sink: POSTs the alert as JSON to an operator-configured endpoint. The\n * first and only outbound network surface of the drift feature — off by default\n * (constructed only when a `webhook` sink is explicitly configured). Hardened:\n * - the body is **always** `redactValue(alert)`, so no `user:password@` secret\n * leaves the process;\n * - only `stripSensitive(url)` (host:port) is ever logged — never the full URL,\n * the bearer token, or the body;\n * - the target must be `https:` (SSRF / plaintext-exfil guard) — a plaintext\n * `http:` URL is refused unless the host is loopback or the documented\n * `CARTOGRAPHY_ALLOW_INSECURE_SYNC=1` escape hatch is set (mirrors `pushDeltas`);\n * - degrades to a logged no-op when `fetch` is unavailable or the URL is empty;\n * - never throws (the runner owns retry/abort policy).\n */\nexport class WebhookSink implements DriftSink {\n readonly name = 'webhook';\n\n constructor(private readonly opts: WebhookSinkOptions) {}\n\n async emit(alert: DriftAlert): Promise<void> {\n if (typeof fetch !== 'function') {\n logWarn('webhook sink unavailable: global fetch missing', { sink: this.name });\n return;\n }\n const { url, token, timeoutMs } = this.opts;\n if (!url) {\n logWarn('webhook sink unavailable: no url configured', { sink: this.name });\n return;\n }\n if (!isSecureWebhookUrl(url)) {\n logWarn('webhook sink refused: insecure scheme (use https:// or a loopback host)', {\n sink: this.name,\n host: stripSensitive(url),\n });\n return;\n }\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...(token ? { authorization: `Bearer ${token}` } : {}),\n },\n body: JSON.stringify(redactValue(alert)),\n signal: AbortSignal.timeout(timeoutMs ?? 10_000),\n });\n if (!res.ok) {\n logError('webhook sink failed', { sink: this.name, host: stripSensitive(url), status: res.status });\n }\n } catch (err) {\n logError('webhook sink failed', {\n sink: this.name,\n host: stripSensitive(url),\n reason: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n","import type { DriftConfig } from '../types.js';\nimport type { DriftSink } from './types.js';\nimport { StdoutSink } from './stdout.js';\nimport { WebhookSink } from './webhook.js';\n\n/**\n * Construct sinks from config. Absent/empty config → `[new StdoutSink()]` (the\n * local default). A webhook sink's token falls back to `CARTOGRAPHY_DRIFT_TOKEN`\n * when not given explicitly (mirroring `CARTOGRAPHY_HTTP_TOKEN`). A webhook entry\n * without a url is skipped defensively (the schema already rejects it).\n */\nexport function buildSinks(drift?: DriftConfig): DriftSink[] {\n const configs = drift?.sinks && drift.sinks.length > 0 ? drift.sinks : [{ type: 'stdout' as const }];\n const sinks: DriftSink[] = [];\n for (const s of configs) {\n if (s.type === 'webhook') {\n if (!s.url) continue;\n sinks.push(new WebhookSink({\n url: s.url,\n token: s.token ?? process.env.CARTOGRAPHY_DRIFT_TOKEN,\n timeoutMs: s.timeoutMs,\n }));\n } else {\n sinks.push(new StdoutSink());\n }\n }\n return sinks.length > 0 ? sinks : [new StdoutSink()];\n}\n\nexport { StdoutSink } from './stdout.js';\nexport { WebhookSink, isSecureWebhookUrl } from './webhook.js';\nexport type { DriftSink } from './types.js';\nexport type { WebhookSinkOptions } from './webhook.js';\n","/**\n * Drift classification + dispatch (3.1).\n *\n * Layered on top of the pure topology delta (`diffTopology` → `db.diffSessions`):\n * `classifyDrift` turns a `TopologyDiff` into a severity-ranked `DriftAlert`\n * (deterministic, DB-agnostic, exhaustively unit-testable), and `runDrift` is the\n * one-shot runner — the seam scheduled discovery (2.5) calls after a scan — that\n * resolves the latest two sessions, classifies, threshold-filters, fans out to the\n * configured sinks (one sink's failure never aborts the others), and writes an\n * audit event. No new tables, no migration; read-only except the audit row.\n */\n\nimport type {\n TopologyDiff, DriftAlert, DriftAlertItem, Severity, NodeChange, CartographyConfig,\n} from './types.js';\nimport { SEVERITIES } from './types.js';\nimport type { CartographyDB } from './db.js';\nimport { stableStringify } from './diff.js';\nimport { redactValue } from './tools.js';\nimport { buildSinks } from './sinks/index.js';\nimport { logInfo, logWarn, logError } from './logger.js';\nimport { SECURITY_METADATA_KEYS } from './types.js';\n\n/** Return the highest severity among items; `info` for an empty list. */\nexport function maxSeverity(items: DriftAlertItem[]): Severity {\n let rank = 0;\n for (const it of items) {\n const r = SEVERITIES.indexOf(it.severity);\n if (r > rank) rank = r;\n }\n return SEVERITIES[rank];\n}\n\nconst SECURITY_KEY_SET = new Set<string>(SECURITY_METADATA_KEYS);\n\n/**\n * Names of the metadata keys whose value changed and which warrant escalation to\n * `critical`. Empty unless the node-changed touched `metadata` and a\n * security-relevant key (case-insensitive) actually differs before→after.\n */\nexport function securityRelevantChange(change: NodeChange): string[] {\n if (!change.changedFields.includes('metadata')) return [];\n const before = change.before.metadata ?? {};\n const after = change.after.metadata ?? {};\n const keys = new Set<string>([...Object.keys(before), ...Object.keys(after)]);\n const triggered: string[] = [];\n for (const k of keys) {\n if (!SECURITY_KEY_SET.has(k.toLowerCase())) continue;\n if (stableStringify((before as Record<string, unknown>)[k]) !== stableStringify((after as Record<string, unknown>)[k])) {\n triggered.push(k);\n }\n }\n return triggered.sort();\n}\n\n/** Logical reference for an edge item: `source -rel-> target`. */\nconst edgeRef = (sourceId: string, rel: string, targetId: string): string =>\n `${sourceId} -${rel}-> ${targetId}`;\n\n/**\n * Classify a topology diff into a severity-tagged alert. Pure & deterministic\n * (only `generatedAt` depends on `now`). Rules:\n * - node-removed, edge-removed → warning (lost capability / connectivity)\n * - node-changed touching a security metadata → critical\n * key\n * - node-changed (other fields) → info\n * - node-added, edge-added → info\n * Overall severity = maxSeverity(items).\n */\nexport function classifyDrift(diff: TopologyDiff, now: Date = new Date()): DriftAlert {\n const items: DriftAlertItem[] = [];\n\n for (const n of diff.nodes.added) {\n items.push({ kind: 'node-added', ref: n.id, label: n.name, nodeType: n.type, severity: 'info' });\n }\n for (const n of diff.nodes.removed) {\n items.push({ kind: 'node-removed', ref: n.id, label: n.name, nodeType: n.type, severity: 'warning' });\n }\n for (const c of diff.nodes.changed) {\n const securityFields = securityRelevantChange(c);\n const severity: Severity = securityFields.length > 0 ? 'critical' : 'info';\n items.push({\n kind: 'node-changed',\n ref: c.id,\n label: c.after.name,\n nodeType: c.after.type,\n severity,\n changedFields: c.changedFields,\n ...(securityFields.length > 0 ? { securityFields } : {}),\n });\n }\n for (const e of diff.edges.added) {\n items.push({\n kind: 'edge-added',\n ref: edgeRef(e.sourceId, e.relationship, e.targetId),\n label: `${e.sourceId} → ${e.targetId}`,\n severity: 'info',\n });\n }\n for (const e of diff.edges.removed) {\n items.push({\n kind: 'edge-removed',\n ref: edgeRef(e.sourceId, e.relationship, e.targetId),\n label: `${e.sourceId} → ${e.targetId}`,\n severity: 'warning',\n });\n }\n\n return {\n base: diff.base,\n current: diff.current,\n summary: diff.summary,\n severity: maxSeverity(items),\n items,\n generatedAt: now.toISOString(),\n };\n}\n\n/** Drop items strictly below `min`; recompute the overall severity over what's kept. */\nexport function filterBySeverity(alert: DriftAlert, min: Severity): DriftAlert {\n const minRank = SEVERITIES.indexOf(min);\n const items = alert.items.filter((it) => SEVERITIES.indexOf(it.severity) >= minRank);\n return { ...alert, items, severity: maxSeverity(items) };\n}\n\nexport interface RunDriftOptions { base?: string; current?: string; minSeverity?: Severity; }\n\n/**\n * Resolve base/current (newest-first `getSessions`, like the CLI/MCP surfaces),\n * classify, filter below `minSeverity`, dispatch to all configured sinks\n * (`Promise.allSettled` — one sink failure never aborts the others), and record an\n * audit event. Returns the alert, or `null` when fewer than two sessions exist\n * (graceful no-op). The seam scheduled discovery (2.5) calls post-scan.\n */\nexport async function runDrift(\n db: CartographyDB, config: CartographyConfig, opts: RunDriftOptions = {},\n): Promise<DriftAlert | null> {\n // getSessions() is newest-first (rowid DESC): [0] = latest, [1] = previous.\n const sessions = db.getSessions();\n const currentId = opts.current ?? sessions[0]?.id;\n const baseId = opts.base ?? sessions[1]?.id;\n if (!baseId || !currentId) {\n logInfo('drift: fewer than two sessions, skipping');\n return null;\n }\n if (baseId === currentId) {\n logWarn('drift: base and current session are the same, skipping', { session: currentId });\n return null;\n }\n\n const diff = db.diffSessions(baseId, currentId);\n let alert = classifyDrift(diff);\n alert = filterBySeverity(alert, opts.minSeverity ?? config.drift?.minSeverity ?? 'info');\n\n const sinks = buildSinks(config.drift);\n const results = await Promise.allSettled(sinks.map((s) => s.emit(alert)));\n results.forEach((r, i) => {\n if (r.status === 'rejected') {\n logError('drift sink rejected', {\n sink: sinks[i]?.name,\n reason: r.reason instanceof Error ? r.reason.message : String(r.reason),\n });\n }\n });\n\n // Audit (no migration): reuses existing activity_events columns.\n db.insertEvent(currentId, {\n eventType: 'drift_alert_dispatched',\n process: 'drift',\n pid: process.pid,\n command: JSON.stringify(redactValue({\n base: baseId,\n current: currentId,\n severity: alert.severity,\n items: alert.items.length,\n sinks: sinks.map((s) => s.name),\n })),\n });\n\n return alert;\n}\n","/**\n * Baseline compliance ruleset (3.4) — the acceptance target.\n *\n * Scored against signals available today (node type/group, domain, confidence,\n * owner, metadata DSNs). Validated by `RulesetSchema.parse` at load (fail-fast).\n */\n\nimport { RulesetSchema } from '../types.js';\nimport type { Ruleset } from '../types.js';\n\nexport const baseline: Ruleset = RulesetSchema.parse({\n name: 'baseline',\n version: '1.0.0',\n framework: 'baseline',\n description: 'Deterministic baseline hygiene controls scored against signals available today.',\n rules: [\n {\n id: 'BASE-1', control: 'BASE-1', framework: 'baseline', severity: 'medium',\n title: 'Asset has an owner', rationale: 'Every asset should have a clear owning team/person for accountability.',\n scope: {},\n check: { any: [\n { field: 'owner', op: 'present' },\n { field: 'domain', op: 'present' },\n { field: 'tags', op: 'matches', pattern: 'owner_key' },\n { field: 'metadataKeys', op: 'matches', pattern: 'owner_key' },\n ] },\n },\n {\n id: 'BASE-2', control: 'BASE-2', framework: 'baseline', severity: 'low',\n title: 'Service/data asset is assigned a business domain', rationale: 'Domain assignment enables blast-radius and ownership analysis.',\n scope: { groups: ['data', 'web'] },\n check: { field: 'domain', op: 'present' },\n },\n {\n id: 'BASE-3', control: 'BASE-3', framework: 'baseline', severity: 'high',\n title: 'Critical asset is discovered with adequate confidence', rationale: 'Low-confidence critical assets indicate incomplete or unreliable discovery.',\n scope: { groups: ['data', 'infra'] },\n check: { field: 'confidence', op: 'gte', value: 0.5 },\n },\n {\n id: 'BASE-4', control: 'BASE-4', framework: 'baseline', severity: 'critical',\n title: 'No embedded credentials / plaintext DSN in metadata', rationale: 'A connection string with embedded credentials is a direct secret-exposure risk.',\n scope: {},\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'dsn_with_credentials' } },\n },\n {\n id: 'BASE-5', control: 'BASE-5', framework: 'baseline', severity: 'medium',\n title: 'Data store carries an acceptable quality score', rationale: 'Where a quality score exists, a low score flags an under-governed data store.',\n scope: { groups: ['data'] },\n applicableWhen: { field: 'qualityScore', op: 'present' },\n check: { field: 'qualityScore', op: 'gte', value: 50 },\n },\n ],\n});\n","/**\n * CIS starter subset (3.4) — NOT a certified CIS Benchmark. A small, illustrative\n * subset that resolves the `cis` ruleset name; full coverage is plugin/community work.\n */\n\nimport { RulesetSchema } from '../types.js';\nimport type { Ruleset } from '../types.js';\n\nexport const cis: Ruleset = RulesetSchema.parse({\n name: 'cis',\n version: '0.1.0',\n framework: 'CIS',\n description: 'CIS starter subset — illustrative controls, not a certified benchmark.',\n rules: [\n {\n id: 'CIS-1.1', control: 'CIS-1.1', framework: 'CIS', severity: 'critical',\n title: 'No plaintext credentials in service configuration', rationale: 'CIS hardening forbids embedded secrets in config/metadata.',\n scope: {},\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'dsn_with_credentials' } },\n },\n {\n id: 'CIS-2.1', control: 'CIS-2.1', framework: 'CIS', severity: 'high',\n title: 'Data store is not publicly exposed', rationale: 'Data stores should not bind to public/0.0.0.0 addresses.',\n scope: { groups: ['data'] },\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'public_exposure' } },\n },\n {\n id: 'CIS-3.1', control: 'CIS-3.1', framework: 'CIS', severity: 'medium',\n title: 'Asset inventory has an accountable owner', rationale: 'CIS asset management requires an assigned owner.',\n scope: {},\n check: { any: [{ field: 'owner', op: 'present' }, { field: 'metadataKeys', op: 'matches', pattern: 'owner_key' }] },\n },\n ],\n});\n","/**\n * SOC 2 starter subset (3.4) — NOT a certified SOC 2 control set. Illustrative\n * controls that resolve the `soc2` ruleset name; full coverage is community work.\n */\n\nimport { RulesetSchema } from '../types.js';\nimport type { Ruleset } from '../types.js';\n\nexport const soc2: Ruleset = RulesetSchema.parse({\n name: 'soc2',\n version: '0.1.0',\n framework: 'SOC2',\n description: 'SOC 2 starter subset — illustrative controls, not a certified control set.',\n rules: [\n {\n id: 'CC6.1', control: 'CC6.1', framework: 'SOC2', severity: 'critical',\n title: 'Logical access — no embedded credentials', rationale: 'CC6.1 logical access controls preclude plaintext secrets in config.',\n scope: {},\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'dsn_with_credentials' } },\n },\n {\n id: 'CC6.6', control: 'CC6.6', framework: 'SOC2', severity: 'high',\n title: 'Boundary protection — data stores not public', rationale: 'CC6.6 requires protection of system boundaries from external access.',\n scope: { groups: ['data'] },\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'public_exposure' } },\n },\n {\n id: 'CC1.2', control: 'CC1.2', framework: 'SOC2', severity: 'medium',\n title: 'Accountability — asset has an owner', rationale: 'CC1.2 establishes accountability; assets need an assigned owner.',\n scope: {},\n check: { any: [{ field: 'owner', op: 'present' }, { field: 'domain', op: 'present' }] },\n },\n ],\n});\n","/**\n * ISO/IEC 27001 starter subset (3.4) — NOT a certified Annex A control set.\n * Illustrative controls that resolve the `iso27001` ruleset name.\n */\n\nimport { RulesetSchema } from '../types.js';\nimport type { Ruleset } from '../types.js';\n\nexport const iso27001: Ruleset = RulesetSchema.parse({\n name: 'iso27001',\n version: '0.1.0',\n framework: 'ISO27001',\n description: 'ISO/IEC 27001 Annex A starter subset — illustrative, not certified.',\n rules: [\n {\n id: 'A.8.1', control: 'A.8.1', framework: 'ISO27001', severity: 'medium',\n title: 'Inventory of assets — ownership assigned', rationale: 'A.8.1 requires an inventory of assets each with an identified owner.',\n scope: {},\n check: { any: [{ field: 'owner', op: 'present' }, { field: 'metadataKeys', op: 'matches', pattern: 'owner_key' }] },\n },\n {\n id: 'A.8.12', control: 'A.8.12', framework: 'ISO27001', severity: 'critical',\n title: 'Data leakage prevention — no embedded secrets', rationale: 'A.8.12 mandates measures against data leakage such as exposed credentials.',\n scope: {},\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'dsn_with_credentials' } },\n },\n {\n id: 'A.8.20', control: 'A.8.20', framework: 'ISO27001', severity: 'high',\n title: 'Network security — data stores not publicly exposed', rationale: 'A.8.20 requires networks to be secured; data stores should not be public.',\n scope: { groups: ['data'] },\n check: { not: { field: 'metadataValues', op: 'matches', pattern: 'public_exposure' } },\n },\n ],\n});\n","/**\n * Bundled ruleset registry (3.4). All rulesets are plain data, validated at load.\n */\n\nimport type { Ruleset } from '../types.js';\nimport { baseline } from './baseline.js';\nimport { cis } from './cis.js';\nimport { soc2 } from './soc2.js';\nimport { iso27001 } from './iso27001.js';\n\nconst RULESETS: Record<string, Ruleset> = { baseline, cis, soc2, iso27001 };\n\n/** Resolve a bundled ruleset by name, or `undefined` (callers degrade, never throw). */\nexport function getRuleset(name: string): Ruleset | undefined {\n return RULESETS[name];\n}\n\n/** List the bundled rulesets (name/version/framework/rule count) for help + degrade messages. */\nexport function listRulesets(): Array<{ name: string; version: string; framework: string; ruleCount: number }> {\n return Object.values(RULESETS).map((r) => ({ name: r.name, version: r.version, framework: r.framework, ruleCount: r.rules.length }));\n}\n\nexport { baseline, cis, soc2, iso27001 };\n","/**\n * Natural-language topology query resolver (3.5) — deterministic, LLM-free.\n *\n * Turns a plain-English question (\"services that depend on the payments DB\") into a\n * structured intent, then composes the existing primitives — the injected `SearchFn`\n * (semantic↔lexical degradation inherited for free) + `db.getDependencies` + a\n * post-traversal type filter — and echoes the parsed intent for explainability.\n * No LLM, no new exec path, read-only. The NL string is sanitized + length-clamped\n * (ReDoS-safe: only linear, anchored patterns).\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { NodeRow, EdgeRow, NodeType } from '../types.js';\nimport { NODE_TYPE_GROUPS } from '../types.js';\nimport type { SearchFn } from '../mcp/server.js';\nimport { sanitizeUntrusted } from '../sanitize.js';\nimport { logDebug } from '../logger.js';\n\n/** The relationship the operator asked about, in resolver-native terms. */\nexport type NlRelation = 'depends-on' | 'depended-on-by' | 'connected-to' | 'list';\n\n/** Maps a parsed relation to the `db.getDependencies` direction. Single source of truth. */\nexport const RELATION_TO_DIRECTION = {\n 'depends-on': 'downstream',\n 'depended-on-by': 'upstream',\n 'connected-to': 'both',\n 'list': undefined,\n} as const satisfies Record<NlRelation, 'downstream' | 'upstream' | 'both' | undefined>;\n\n/** Structured intent parsed from a natural-language question. */\nexport interface NlIntent {\n readonly query: string;\n readonly subjectQuery: string;\n readonly relation: NlRelation;\n readonly direction?: 'downstream' | 'upstream' | 'both';\n /** Concrete node types the *result* set is restricted to (post-traversal), if any. */\n readonly typeFilter?: readonly NodeType[];\n /** True when no relation pattern matched and we degraded to a search-only list. */\n readonly degraded: boolean;\n}\n\nexport interface NlQueryResult {\n readonly intent: NlIntent;\n readonly anchors: Array<{ node: NodeRow; score?: number }>;\n readonly nodes: Array<NodeRow & { depth?: number }>;\n readonly paths: EdgeRow[];\n}\n\nexport interface NlQueryOptions {\n readonly maxDepth?: number;\n readonly anchorLimit?: number;\n}\n\nconst MAX_QUERY_LEN = 1000;\n\n/** Plural/group words → concrete node-type sets (applied to the *result* set). */\nconst GROUP_WORDS: ReadonlyArray<readonly [RegExp, readonly NodeType[]]> = [\n [/\\bservices?\\b/i, NODE_TYPE_GROUPS.web],\n [/\\b(?:databases?|datastores?|data ?stores?)\\b/i, NODE_TYPE_GROUPS.data],\n [/\\b(?:queues?|topics?|brokers?|messaging)\\b/i, NODE_TYPE_GROUPS.messaging],\n [/\\b(?:hosts?|containers?|pods?|clusters?|infra(?:structure)?)\\b/i, NODE_TYPE_GROUPS.infra],\n [/\\bsaas\\b/i, NODE_TYPE_GROUPS.saas],\n];\n\n// Relation patterns (bounded, anchored, no nested quantifiers → ReDoS-free). Each\n// captures a named `subject` group; UPSTREAM also has an implicit leading clause.\nconst UPSTREAM: RegExp[] = [\n /^(?:.*?)\\b(?:that|which)\\s+depends?\\s+(?:on|upon)\\s+(?<subject>.+)$/i,\n /^(?:.*?)\\bdepending\\s+(?:on|upon)\\s+(?<subject>.+)$/i,\n /^(?:.*?)\\b(?:that|which)\\s+relies?\\s+on\\s+(?<subject>.+)$/i,\n /^\\s*what\\s+depends?\\s+(?:on|upon)\\s+(?<subject>.+)$/i,\n];\nconst DOWNSTREAM: RegExp[] = [\n /\\bwhat\\s+(?:does|do)\\s+(?<subject>.+?)\\s+depend\\s+(?:on|upon)\\b/i,\n /^(?<subject>.+?)['’]s\\s+dependencies\\b/i,\n /\\bdependencies\\s+of\\s+(?<subject>.+)$/i,\n /^(?<subject>.+?)\\s+dependencies\\b/i,\n /^(?<subject>.+?)\\s+depends?\\s+(?:on|upon)\\s+.*$/i,\n];\nconst CONNECTED: RegExp[] = [\n /\\bwhat\\s+is\\s+connected\\s+to\\s+(?<subject>.+)$/i,\n /\\b(?:connected|related|linked)\\s+to\\s+(?<subject>.+)$/i,\n /\\btalks?\\s+to\\s+(?<subject>.+)$/i,\n];\n\nfunction firstMatch(query: string, pats: RegExp[]): string | null {\n for (const re of pats) {\n const m = query.match(re);\n if (m?.groups?.subject) return m.groups.subject;\n }\n return null;\n}\n\nfunction cleanSubject(s: string): string {\n return s.replace(/[?.!,]+\\s*$/g, '').replace(/^\\s*(?:the|a|an|my|our|all)\\s+/i, '').trim();\n}\n\nfunction detectTypeFilter(text: string): readonly NodeType[] | undefined {\n const set = new Set<NodeType>();\n for (const [re, types] of GROUP_WORDS) if (re.test(text)) for (const t of types) set.add(t);\n return set.size > 0 ? [...set] : undefined;\n}\n\n/** Parse a natural-language question into a structured {@link NlIntent}. Pure & deterministic. */\nexport function parseNlQuery(raw: string): NlIntent {\n const query = sanitizeUntrusted(raw).slice(0, MAX_QUERY_LEN).trim();\n\n let relation: NlRelation = 'list';\n let subj = firstMatch(query, UPSTREAM);\n if (subj !== null) relation = 'depended-on-by';\n else if ((subj = firstMatch(query, DOWNSTREAM)) !== null) relation = 'depends-on';\n else if ((subj = firstMatch(query, CONNECTED)) !== null) relation = 'connected-to';\n\n const degraded = subj === null;\n const subjectQuery = cleanSubject(subj ?? query);\n // Type filter from the non-subject portion (relational) or the whole query (list),\n // so \"DB\" inside the subject never pollutes a \"services …\" result filter.\n const scanText = subj === null ? query : query.replace(subj, ' ');\n const typeFilter = detectTypeFilter(scanText);\n\n // optional LLM assist seam: a future AgentProvider could re-parse `query` here when relation === 'list'.\n return { query, subjectQuery, relation, direction: RELATION_TO_DIRECTION[relation], typeFilter, degraded };\n}\n\n/** Execute a parsed intent: anchor via `search`, traverse, then filter results by type. */\nexport async function executeNlQuery(\n db: CartographyDB,\n sessionId: string,\n search: SearchFn,\n intent: NlIntent,\n opts: NlQueryOptions = {},\n): Promise<NlQueryResult> {\n const anchorLimit = opts.anchorLimit ?? 5;\n\n // 'list': search-only — apply the type filter to the anchor search (no traversal).\n if (intent.relation === 'list') {\n const anchors = await search(db, sessionId, intent.subjectQuery, { types: intent.typeFilter, limit: anchorLimit });\n logDebug('nlq.execute', { relation: 'list', typeFilter: intent.typeFilter?.length ?? 0, anchors: anchors.length });\n return { intent, anchors, nodes: anchors.map((a) => a.node), paths: [] };\n }\n\n // Relational: anchor on the subject WITHOUT a type filter (the anchor is the subject,\n // not the result type); apply the type filter to the *traversal results*.\n const anchors = await search(db, sessionId, intent.subjectQuery, { limit: anchorLimit });\n if (anchors.length === 0) {\n logDebug('nlq.execute', { relation: intent.relation, anchors: 0, results: 0 });\n return { intent, anchors, nodes: [], paths: [] };\n }\n const root = anchors[0].node; // top hit (search orders by score, then confidence)\n const trav = db.getDependencies(sessionId, root.id, { direction: intent.direction, maxDepth: opts.maxDepth ?? 8 });\n const allow = intent.typeFilter ? new Set<string>(intent.typeFilter) : undefined;\n const nodes = allow ? trav.nodes.filter((n) => allow.has(n.type)) : trav.nodes;\n\n logDebug('nlq.execute', {\n relation: intent.relation, direction: intent.direction,\n typeFilter: intent.typeFilter?.length ?? 0, anchors: anchors.length, results: nodes.length,\n });\n return { intent, anchors, nodes, paths: trav.edges };\n}\n\n/** Convenience: parse + execute in one call. */\nexport async function resolveNlQuery(\n db: CartographyDB, sessionId: string, search: SearchFn, raw: string, opts?: NlQueryOptions,\n): Promise<NlQueryResult> {\n return executeNlQuery(db, sessionId, search, parseNlQuery(raw), opts);\n}\n","/**\n * Transport bindings for the Cartography MCP server.\n *\n * - **stdio**: the local-first default — zero network, every client supports it.\n * - **Streamable HTTP**: a single `/mcp` endpoint for team/remote use, bound to\n * localhost with DNS-rebinding protection. The deprecated SSE transport is not used.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport http from 'node:http';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n/** Connect a server over stdio (resolves when the transport closes). */\nexport async function runStdio(server: McpServer): Promise<void> {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nexport interface HttpOptions {\n port?: number;\n host?: string;\n /** Extra allowed Host headers (defaults to localhost:port variants). */\n allowedHosts?: string[];\n /** Allowed Origin headers (defaults to none → same-origin only). */\n allowedOrigins?: string[];\n /**\n * Shared secret required in the `Authorization: Bearer <token>` header.\n * Mandatory when binding a non-loopback host; optional (and enforced when\n * present) on loopback.\n */\n token?: string;\n /**\n * Central-collector ingest hook (2.12). When set, an authenticated `POST /ingest`\n * **write** route is exposed: it inherits the *same* bearer auth and non-loopback\n * host-allowlist guards as `/mcp` (the route adds no separate hardening path), size-\n * caps the body, parses JSON, and returns the hook's `{ status, body }`. When unset,\n * `/ingest` 404s exactly like any other path — the collector stays dark by default.\n */\n onIngest?: (body: unknown) => { status: number; body: unknown };\n}\n\n/** Max ingest body size (5 MB). A larger POST /ingest is refused with 413 before parsing. */\nconst MAX_INGEST_BYTES = 5 * 1024 * 1024;\n\n/**\n * Read a size-capped request body. On overflow it stops buffering and drains the\n * rest of the stream (rather than resetting the socket), so the caller can still\n * write a clean 413 response that the client receives.\n */\nasync function readCappedBody(req: http.IncomingMessage, cap: number): Promise<{ overflow: boolean; value: unknown }> {\n const chunks: Buffer[] = [];\n let total = 0;\n let overflow = false;\n for await (const chunk of req) {\n if (overflow) continue; // drain remaining data without buffering\n const buf = chunk as Buffer;\n total += buf.length;\n if (total > cap) { overflow = true; chunks.length = 0; continue; }\n chunks.push(buf);\n }\n if (overflow) return { overflow: true, value: undefined };\n if (chunks.length === 0) return { overflow: false, value: undefined };\n try { return { overflow: false, value: JSON.parse(Buffer.concat(chunks).toString('utf8')) }; }\n catch { return { overflow: false, value: undefined }; }\n}\n\n/** Constant-time comparison to avoid leaking the token via timing. */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n/** Extract the bearer token from an Authorization header, if present. */\nfunction bearerToken(header: string | undefined): string | undefined {\n if (!header) return undefined;\n const m = /^Bearer\\s+(.+)$/i.exec(header.trim());\n return m ? m[1] : undefined;\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n if (chunks.length === 0) return undefined;\n try { return JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { return undefined; }\n}\n\n/**\n * Start a Streamable HTTP server. A fresh MCP server instance is created per\n * session via `factory`, so multiple clients can connect concurrently.\n */\n/** Loopback hosts are safe to bind without an explicit Host allowlist. */\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);\n\nexport async function runHttp(factory: () => McpServer, opts: HttpOptions = {}): Promise<http.Server> {\n const host = opts.host ?? '127.0.0.1';\n const port = opts.port ?? 3737;\n\n const isLoopback = LOOPBACK_HOSTS.has(host);\n\n // CVE-2025-66414: a server reachable beyond loopback must declare which Host\n // headers it trusts, or DNS-rebinding protection cannot do its job. Refuse to\n // start an exposed server with the permissive localhost defaults.\n if (!isLoopback && opts.allowedHosts === undefined) {\n throw new Error(\n `Refusing to bind a non-loopback host (${host}) without an explicit allowedHosts allowlist. ` +\n `Pass { allowedHosts: ['your.public.host:port'] } to opt in, or bind 127.0.0.1 for local-only use.`,\n );\n }\n\n // A network-reachable endpoint must be authenticated. Without a token, anyone\n // who can reach the host could invoke the scanning tools.\n if (!isLoopback && !opts.token) {\n throw new Error(\n `Refusing to bind a non-loopback host (${host}) without an auth token. ` +\n `Pass { token } (or --token / CARTOGRAPHY_HTTP_TOKEN) so requests must carry 'Authorization: Bearer <token>'.`,\n );\n }\n\n const allowedHosts = opts.allowedHosts ?? [`${host}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];\n const token = opts.token;\n const transports = new Map<string, StreamableHTTPServerTransport>();\n\n const httpServer = http.createServer(async (req, res) => {\n try {\n const url = req.url ?? '';\n const isIngest = url.startsWith('/ingest') && opts.onIngest !== undefined;\n // Only `/mcp` (read) and, when enabled, `/ingest` (write) are routed; all else 404s\n // — so an unset `onIngest` keeps the collector dark (the write surface never appears).\n if (!url.startsWith('/mcp') && !isIngest) { res.writeHead(404, { 'content-type': 'application/json' }).end('{\"error\":\"not found\"}'); return; }\n\n // Authenticate before touching any session/transport state OR ingest. The ingest\n // route inherits the SAME bearer check + non-loopback guards as /mcp — no separate path.\n if (token) {\n const provided = bearerToken(req.headers['authorization']);\n if (!provided || !timingSafeEqual(provided, token)) {\n res.writeHead(401, { 'content-type': 'application/json', 'www-authenticate': 'Bearer' })\n .end('{\"error\":\"unauthorized\"}');\n return;\n }\n }\n\n // Central-collector ingest (2.12): an authenticated write route, dark unless onIngest is set.\n if (isIngest) {\n // DNS-rebinding protection (CVE-2025-66414): /mcp gets this from the SDK\n // transport's `allowedHosts`, but /ingest returns before the transport runs,\n // so it must validate the Host header itself to inherit the SAME guard.\n const hostHeader = (req.headers['host'] ?? '').toLowerCase();\n if (!allowedHosts.some((h) => h.toLowerCase() === hostHeader)) {\n res.writeHead(403, { 'content-type': 'application/json' }).end('{\"error\":\"host not allowed\"}');\n return;\n }\n const onIngest = opts.onIngest!;\n if (req.method !== 'POST') {\n res.writeHead(405, { 'content-type': 'application/json', 'allow': 'POST' }).end('{\"error\":\"method not allowed\"}');\n return;\n }\n const { overflow, value } = await readCappedBody(req, MAX_INGEST_BYTES);\n if (overflow) { res.writeHead(413, { 'content-type': 'application/json' }).end('{\"error\":\"payload too large\"}'); return; }\n const out = onIngest(value);\n res.writeHead(out.status, { 'content-type': 'application/json' }).end(JSON.stringify(out.body));\n return;\n }\n\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n const existing = sessionId ? transports.get(sessionId) : undefined;\n\n if (existing) {\n const body = req.method === 'POST' ? await readJsonBody(req) : undefined;\n await existing.handleRequest(req, res, body);\n return;\n }\n\n if (req.method !== 'POST') {\n res.writeHead(400, { 'content-type': 'application/json' }).end('{\"error\":\"missing or unknown mcp-session-id\"}');\n return;\n }\n\n // New session: initialize a transport + server instance.\n const body = await readJsonBody(req);\n const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n enableDnsRebindingProtection: true,\n allowedHosts,\n ...(opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {}),\n onsessioninitialized: (id: string) => { transports.set(id, transport); },\n });\n transport.onclose = () => { if (transport.sessionId) transports.delete(transport.sessionId); };\n await factory().connect(transport);\n await transport.handleRequest(req, res, body);\n } catch (err) {\n process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n if (!res.headersSent) res.writeHead(500, { 'content-type': 'application/json' }).end('{\"error\":\"internal error\"}');\n }\n });\n\n await new Promise<void>((resolve) => httpServer.listen(port, host, resolve));\n return httpServer;\n}\n","/** FNV-1a (32-bit) — the shared content hash used by the embeddings and vector store. */\nexport function fnv1a(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n","/**\n * Embedding providers for semantic search.\n *\n * The default provider runs a small sentence-transformer locally via\n * `@huggingface/transformers` (no API key, offline after first download), keeping\n * the package LLM-agnostic. Everything is a lazy import so installs that never use\n * semantic search pay no cost and need no native model.\n */\n\nimport { fnv1a } from './hash.js';\n\n/** Produces fixed-dimension embeddings for a batch of texts. */\nexport interface EmbeddingProvider {\n readonly id: string;\n readonly dimensions: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\n/**\n * Local sentence-transformer embedder (Xenova/all-MiniLM-L6-v2, 384 dims).\n * Returns `undefined` if `@huggingface/transformers` is not installed or the\n * model cannot be loaded, so callers can fall back to lexical search.\n */\nexport async function createLocalEmbedder(\n model = 'Xenova/all-MiniLM-L6-v2',\n): Promise<EmbeddingProvider | undefined> {\n try {\n // Cast the specifier to defeat static module resolution: the type build must\n // not require this optional native dep to be installed. We assert the minimal\n // surface we use instead — the runtime import is unchanged.\n const tf = (await import('@huggingface/transformers' as string)) as {\n pipeline(\n task: string,\n model: string,\n ): Promise<(text: string, opts: { pooling: string; normalize: boolean }) => Promise<{ data: unknown }>>;\n };\n const extractor = await tf.pipeline('feature-extraction', model);\n return {\n id: `local:${model}`,\n dimensions: 384,\n async embed(texts: string[]): Promise<Float32Array[]> {\n const out: Float32Array[] = [];\n for (const text of texts) {\n const tensor = await extractor(text, { pooling: 'mean', normalize: true });\n out.push(Float32Array.from(tensor.data as Iterable<number>));\n }\n return out;\n },\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * A deterministic, dependency-free hashing embedder (bag-of-character-ngrams).\n * Not as good as a transformer, but offline, instant, and useful as a fallback\n * and for tests. Produces L2-normalized vectors.\n */\nexport function createHashEmbedder(dimensions = 256): EmbeddingProvider {\n return {\n id: `hash:${dimensions}`,\n dimensions,\n async embed(texts: string[]): Promise<Float32Array[]> {\n return texts.map((text) => hashEmbed(text, dimensions));\n },\n };\n}\n\nfunction hashEmbed(text: string, dim: number): Float32Array {\n const v = new Float32Array(dim);\n const tokens = text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n for (const tok of tokens) {\n // hash the token and its char trigrams into buckets\n for (const gram of [tok, ...trigrams(tok)]) {\n const h = fnv1a(gram);\n v[h % dim] += 1;\n }\n }\n let norm = 0;\n for (const x of v) norm += x * x;\n norm = Math.sqrt(norm) || 1;\n for (let i = 0; i < dim; i++) v[i] = v[i]! / norm;\n return v;\n}\n\nfunction trigrams(s: string): string[] {\n if (s.length < 3) return [];\n const out: string[] = [];\n for (let i = 0; i <= s.length - 3; i++) out.push(s.slice(i, i + 3));\n return out;\n}\n","/**\n * Vector store backed by `sqlite-vec`. Stores one embedding per node in a `vec0`\n * virtual table living inside the same SQLite catalog, with incremental indexing\n * (content-hashed) so re-runs only embed what changed.\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { NodeRow } from '../types.js';\nimport type { EmbeddingProvider } from './embeddings.js';\nimport { fnv1a } from './hash.js';\n\n/** Text used to represent a node for embedding. */\nexport function nodeText(n: NodeRow): string {\n const desc = typeof n.metadata?.['description'] === 'string' ? (n.metadata['description'] as string) : '';\n const category = typeof n.metadata?.['category'] === 'string' ? (n.metadata['category'] as string) : '';\n return [n.name, n.id.replace(/[:_]/g, ' '), `type ${n.type}`, n.domain ?? '', n.subDomain ?? '', category, n.tags.join(' '), desc]\n .filter(Boolean).join(' — ');\n}\n\nfunction hash(s: string): string {\n return fnv1a(s).toString(16);\n}\n\nfunction toBuffer(v: Float32Array): Buffer {\n return Buffer.from(v.buffer, v.byteOffset, v.byteLength);\n}\n\nexport class VectorStore {\n private loaded = false;\n\n constructor(private db: CartographyDB, private embedder: EmbeddingProvider) {}\n\n /** Load sqlite-vec and ensure the schema exists. Returns false if unavailable. */\n async init(): Promise<boolean> {\n if (this.loaded) return true;\n try {\n const conn = this.db.rawConnection();\n // Cast the specifier so the type build doesn't require this optional native\n // dep to be installed; runtime import is unchanged.\n const sqliteVec = (await import('sqlite-vec' as string)) as { load(db: unknown): void };\n sqliteVec.load(conn);\n conn.exec(`\n CREATE TABLE IF NOT EXISTS vec_index (\n rowid INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n node_id TEXT NOT NULL,\n hash TEXT NOT NULL,\n UNIQUE(session_id, node_id)\n );\n CREATE TABLE IF NOT EXISTS vec_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);\n `);\n // Recreate the vector table if the embedding dimensions changed.\n const dimRow = conn.prepare(\"SELECT value FROM vec_meta WHERE key = 'dims'\").get() as { value: string } | undefined;\n const dims = this.embedder.dimensions;\n if (dimRow && Number(dimRow.value) !== dims) {\n conn.exec('DROP TABLE IF EXISTS vec_nodes; DELETE FROM vec_index;');\n }\n conn.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS vec_nodes USING vec0(embedding float[${dims}])`);\n conn.prepare('INSERT OR REPLACE INTO vec_meta(key, value) VALUES (?, ?)').run('dims', String(dims));\n conn.prepare('INSERT OR REPLACE INTO vec_meta(key, value) VALUES (?, ?)').run('embedder', this.embedder.id);\n this.loaded = true;\n return true;\n } catch {\n return false;\n }\n }\n\n /** Incrementally embed and index any new/changed nodes for a session. */\n async index(sessionId: string): Promise<{ embedded: number; total: number }> {\n if (!(await this.init())) return { embedded: 0, total: 0 };\n const conn = this.db.rawConnection();\n const nodes = this.db.getNodes(sessionId);\n\n const getRow = conn.prepare('SELECT rowid, hash FROM vec_index WHERE session_id = ? AND node_id = ?');\n const insIndex = conn.prepare('INSERT INTO vec_index (session_id, node_id, hash) VALUES (?, ?, ?)');\n const updHash = conn.prepare('UPDATE vec_index SET hash = ? WHERE rowid = ?');\n const delVec = conn.prepare('DELETE FROM vec_nodes WHERE rowid = ?');\n const insVec = conn.prepare('INSERT INTO vec_nodes (rowid, embedding) VALUES (?, ?)');\n\n const pending: Array<{ rowid: bigint; text: string }> = [];\n for (const n of nodes) {\n const text = nodeText(n);\n const h = hash(`${this.embedder.id}:${text}`);\n const existing = getRow.get(sessionId, n.id) as { rowid: number; hash: string } | undefined;\n if (existing) {\n if (existing.hash === h) continue;\n updHash.run(h, existing.rowid);\n delVec.run(BigInt(existing.rowid));\n pending.push({ rowid: BigInt(existing.rowid), text });\n } else {\n const info = insIndex.run(sessionId, n.id, h);\n pending.push({ rowid: BigInt(info.lastInsertRowid as number), text });\n }\n }\n\n if (pending.length > 0) {\n const vectors = await this.embedder.embed(pending.map((p) => p.text));\n const tx = conn.transaction(() => {\n pending.forEach((p, i) => insVec.run(p.rowid, toBuffer(vectors[i]!)));\n });\n tx();\n }\n return { embedded: pending.length, total: nodes.length };\n }\n\n /** k-nearest-neighbour search within a session. Returns node ids + distances. */\n async search(sessionId: string, query: string, k: number): Promise<Array<{ nodeId: string; distance: number }>> {\n if (!(await this.init())) return [];\n await this.index(sessionId);\n const conn = this.db.rawConnection();\n const [qv] = await this.embedder.embed([query]);\n if (!qv) return [];\n\n // Pure-vec KNN scan (most compatible form), then map rowids and filter by\n // session in JS. Over-fetch so cross-session neighbours don't crowd out hits.\n const overfetch = Math.max(k * 5, k);\n const knn = conn.prepare(\n 'SELECT rowid, distance FROM vec_nodes WHERE embedding MATCH ? ORDER BY distance LIMIT ?',\n ).all(toBuffer(qv), overfetch) as Array<{ rowid: number; distance: number }>;\n\n const meta = conn.prepare('SELECT node_id AS nodeId, session_id AS sessionId FROM vec_index WHERE rowid = ?');\n const out: Array<{ nodeId: string; distance: number }> = [];\n for (const row of knn) {\n const m = meta.get(row.rowid) as { nodeId: string; sessionId: string } | undefined;\n if (m && m.sessionId === sessionId) out.push({ nodeId: m.nodeId, distance: row.distance });\n if (out.length >= k) break;\n }\n return out;\n }\n}\n","/**\n * Semantic search backend for the MCP server. Wraps a {@link VectorStore} and\n * degrades gracefully to lexical search when embeddings/sqlite-vec are unavailable\n * or return nothing.\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { NodeRow } from '../types.js';\nimport type { SearchFn } from '../mcp/server.js';\nimport type { EmbeddingProvider } from './embeddings.js';\nimport { createLocalEmbedder } from './embeddings.js';\nimport { VectorStore } from './store.js';\n\nconst lexical = (db: CartographyDB, sessionId: string, query: string, opts: { types?: readonly string[]; limit: number }) =>\n db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));\n\n/** A lexical-only {@link SearchFn} — the fallback when no embedder/vector store is available. */\nconst lexicalSearch = (): SearchFn => async (d, sid, q, opts) => lexical(d, sid, q, opts);\n\n/** Options for {@link createSemanticSearch}. */\nexport interface SemanticSearchOptions {\n /** Logger for mode/degradation diagnostics (stderr). No-op if omitted. */\n log?: (msg: string) => void;\n}\n\n/**\n * Build a {@link SearchFn} that prefers semantic (vector) search and falls back to\n * lexical. Pass an explicit embedder, or let it lazily load the local transformer\n * (returns a lexical-only function if none is available). Pass `opts.log` to record\n * which mode was resolved — semantic readiness or the reason it degraded to lexical.\n */\nexport async function createSemanticSearch(\n db: CartographyDB,\n embedder?: EmbeddingProvider,\n opts: SemanticSearchOptions = {},\n): Promise<SearchFn> {\n const log = opts.log;\n const provider = embedder ?? (await createLocalEmbedder());\n if (!provider) {\n log?.('semantic search: embeddings unavailable (@huggingface/transformers not installed or failed to load) — using lexical search');\n return lexicalSearch();\n }\n const store = new VectorStore(db, provider);\n const ok = await store.init();\n if (!ok) {\n log?.('semantic search: vector store unavailable (sqlite-vec not installed or failed to load) — using lexical search');\n return lexicalSearch();\n }\n log?.('semantic search: ready');\n\n return async (d, sid, query, queryOpts): Promise<Array<{ node: NodeRow; score?: number }>> => {\n const hits = await store.search(sid, query, queryOpts.limit);\n if (hits.length === 0) return lexical(d, sid, query, queryOpts);\n // Materialize only the hit nodes, not the whole session.\n const byId = d.getNodesByIds(sid, hits.map((h) => h.nodeId));\n const results: Array<{ node: NodeRow; score?: number }> = [];\n for (const h of hits) {\n const node = byId.get(h.nodeId);\n if (!node) continue; // vector outlived its node (deleted since last index)\n if (queryOpts.types && queryOpts.types.length > 0 && !queryOpts.types.includes(node.type)) continue;\n // cosine distance → similarity score in [0,1]\n results.push({ node, score: Math.max(0, 1 - h.distance / 2) });\n }\n return results.length > 0 ? results : lexical(d, sid, query, queryOpts);\n };\n}\n\nexport { VectorStore } from './store.js';\nexport { createLocalEmbedder, createHashEmbedder } from './embeddings.js';\nexport type { EmbeddingProvider } from './embeddings.js';\n","/**\n * Public Scanner SPI — the documented extension contract for out-of-tree\n * `@datasynx/scanner-*` packages.\n *\n * A plugin package default-exports `definePlugin({ name, register })`. Its\n * `register(api)` calls `api.registerScanner(scanner)` for each scanner it\n * provides. The core validates, namespaces (`plugin:<pkg>:<id>`), command-gates\n * and freezes each registered scanner — a plugin never imports core internals\n * beyond the published types here. Loading is opt-in (`config.plugins`) and a\n * broken plugin is isolated, never aborting discovery.\n */\n\nimport { z } from 'zod';\nimport type { Scanner } from './types.js';\n\n/** The only surface a plugin's `register()` touches. */\nexport interface ScannerPluginApi {\n /** Register a scanner the host will namespace, command-gate, and freeze. */\n registerScanner(scanner: Scanner): void;\n}\n\n/** The default-exported shape a `@datasynx/scanner-*` package implements. */\nexport interface ScannerPlugin {\n /** Human-readable plugin name (informational; the package name is the identity). */\n name: string;\n /** Register the plugin's scanners through the host-provided API. */\n register(api: ScannerPluginApi): void;\n}\n\n/**\n * Identity helper a plugin author default-exports. It only narrows the type so\n * editors guide authoring; it adds no runtime behaviour.\n */\nexport function definePlugin(plugin: ScannerPlugin): ScannerPlugin {\n return plugin;\n}\n\n/**\n * Zod shape validating a {@link Scanner} produced by an external plugin.\n * `platforms` uses the concrete `Platform` literals so an invalid platform is\n * rejected at registration; `detect`/`scan` are validated as callables only\n * (their async signatures cannot be expressed in zod) — behavioural safety comes\n * from the gated `ctx.run` wrapper plus the per-scanner runtime try/catch.\n */\nexport const ScannerShape = z.object({\n id: z.string().min(1),\n title: z.string().min(1),\n platforms: z.union([\n z.literal('all'),\n z.array(z.enum(['linux', 'darwin', 'win32'])).nonempty(),\n ]),\n allowedCommands: z.array(z.string().min(1)).optional(),\n detect: z.custom<Scanner['detect']>((v) => typeof v === 'function', {\n message: 'detect must be a function',\n }),\n scan: z.custom<Scanner['scan']>((v) => typeof v === 'function', {\n message: 'scan must be a function',\n }),\n});\n\n/**\n * Validate an unknown value as a {@link Scanner}; throws `ZodError` on mismatch.\n * Returns the original value (the parsed result discards the live function\n * references), so the caller keeps the real `detect`/`scan` closures.\n */\nexport function validateScanner(value: unknown): Scanner {\n ScannerShape.parse(value);\n return value as Scanner;\n}\n","/**\n * Scanner plugin contract.\n *\n * A scanner detects whether it applies to the current machine, then produces a\n * read-only {@link ScanResult}: deterministic nodes/edges where the data is\n * structured enough, plus an optional raw report for an LLM to classify further.\n *\n * Modeled on Steampipe plugins / Backstage processors: new sources can be added\n * (in-tree or via `@datasynx/scanner-*` packages) and registered without forking\n * the core. Each scanner declares the commands it needs, feeding the safety layer.\n */\n\nimport type { Platform } from '../platform.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport type { BookmarkHost } from '../bookmarks.js';\nimport { checkReadOnly } from '../allowlist.js';\nimport { validateScanner } from './spi.js';\n\nexport interface ScanContext {\n /** Optional focus hint from the caller (e.g. tool names to look for). */\n hint?: string;\n /** The current platform. */\n platform: Platform;\n /** Allowlist-gated command runner (returns '' on error/blocked). */\n run: (cmd: string, opts?: { timeout?: number; env?: NodeJS.ProcessEnv }) => string;\n /**\n * Injectable seam: resolve a command to its path ('' if absent). Defaults to the\n * real `commandExists` when omitted. Lets scanners run deterministically in tests.\n */\n commandExists?: (cmd: string) => string;\n /** Injectable seam: raw listening-port output. Defaults to `scanListeningPorts`. */\n scanListeningPorts?: () => string;\n /** Injectable seam: raw established-connection output (3.2). Defaults to `scanEstablishedConnections`. */\n scanEstablishedConnections?: () => string;\n /** Injectable seam: cross-platform file search (3.2). Defaults to `findFiles`. */\n findFiles?: (dirs: string[], patterns: string[], maxDepth: number, limit: number) => string;\n /** Injectable seam: browser-bookmark host source. Defaults to `scanAllBookmarks`. */\n scanBookmarks?: () => Promise<BookmarkHost[]>;\n}\n\nexport interface ScanResult {\n /** Deterministically classified nodes. */\n nodes: DiscoveryNode[];\n /** Deterministically classified edges. */\n edges: DiscoveryEdge[];\n /** Optional raw text report for LLM-driven classification. */\n report?: string;\n}\n\nexport interface Scanner {\n /** Stable id, e.g. \"bookmarks\", \"installed-apps\", \"cloud-aws\". */\n id: string;\n /** Human-readable title. */\n title: string;\n /** Platforms this scanner supports, or 'all'. */\n platforms: Platform[] | 'all';\n /** Read-only commands this scanner may run (declared for the safety layer/docs). */\n allowedCommands?: string[];\n /** Cheap check whether the scanner applies here (e.g. a CLI is installed). */\n detect(ctx: ScanContext): boolean | Promise<boolean>;\n /** Perform the read-only scan. */\n scan(ctx: ScanContext): Promise<ScanResult>;\n}\n\n/** A typed registry of scanners with lazy, platform-aware selection. */\nexport class ScannerRegistry {\n private scanners = new Map<string, Scanner>();\n\n register(scanner: Scanner): this {\n if (this.scanners.has(scanner.id)) throw new Error(`scanner already registered: ${scanner.id}`);\n this.scanners.set(scanner.id, scanner);\n return this;\n }\n\n /**\n * Register a {@link Scanner} produced by an external plugin package. Validates\n * the shape (throws `ZodError` on mismatch), namespaces the id to\n * `plugin:<pkg>:<id>` (avoiding collisions with built-ins and across plugins),\n * wraps `scan()` so the scanner's `ctx.run` is gated by its declared\n * `allowedCommands` intersected with the central read-only allowlist\n * ({@link checkReadOnly}), and freezes the wrapper. Reuses the duplicate-id\n * guard in {@link register}.\n *\n * The gated runner delegates the actual execution to the host-supplied\n * `ctx.run` (the platform runner), so the global read-only floor still applies\n * and `allowedCommands` is a *second*, scanner-scoped least-privilege boundary.\n * A command runs only if its leading executable is declared AND the whole\n * command passes `checkReadOnly`; otherwise the runner returns `''` (the\n * documented \"blocked → ''\" contract).\n */\n registerExternal(pkg: string, scanner: Scanner): this {\n const valid = validateScanner(scanner);\n const id = `plugin:${pkg}:${valid.id}`;\n const declared = valid.allowedCommands ?? [];\n const wrapped: Scanner = Object.freeze({\n id,\n title: valid.title,\n platforms: valid.platforms,\n allowedCommands: declared,\n detect: (ctx: ScanContext) => valid.detect(ctx),\n scan: (ctx: ScanContext) =>\n valid.scan({\n ...ctx,\n run: (cmd, opts) => {\n const exe = cmd.trim().split(/\\s+/)[0] ?? '';\n if (!declared.includes(exe)) return '';\n if (!checkReadOnly(cmd).allowed) return '';\n return ctx.run(cmd, opts);\n },\n }),\n });\n return this.register(wrapped);\n }\n\n get(id: string): Scanner | undefined {\n return this.scanners.get(id);\n }\n\n list(): Scanner[] {\n return [...this.scanners.values()];\n }\n\n /** Scanners whose `platforms` include the given platform. */\n forPlatform(platform: Platform): Scanner[] {\n return this.list().filter((s) => s.platforms === 'all' || s.platforms.includes(platform));\n }\n}\n","import type { Scanner, ScanResult } from './types.js';\nimport type { DiscoveryNode } from '../types.js';\nimport { commandExists } from '../platform.js';\n\n/** Known CLIs/tools grouped by category — detected deterministically via `commandExists`. */\nconst KNOWN_TOOLS: Record<string, string[]> = {\n ide: ['code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'nvim', 'vim', 'emacs', 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm'],\n 'dev-tool': ['git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible', 'vagrant', 'packer', 'consul', 'vault', 'nomad'],\n runtime: ['node', 'npm', 'pnpm', 'yarn', 'bun', 'deno', 'python', 'python3', 'pip', 'poetry', 'ruby', 'rails', 'java', 'mvn', 'gradle', 'go', 'cargo', 'rustc', 'php', 'composer', 'dotnet'],\n database: ['psql', 'mysql', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client'],\n cloud: ['aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler', 'supabase'],\n browser: ['google-chrome', 'chromium', 'firefox', 'brave', 'opera'],\n observability: ['prometheus', 'grafana-cli', 'datadog-agent', 'newrelic-agent'],\n};\n\nexport const installedAppsScanner: Scanner = {\n id: 'installed-apps',\n title: 'Installed apps & developer tools',\n platforms: 'all',\n allowedCommands: ['which', 'command', 'Get-Command'],\n detect: () => true,\n async scan(ctx): Promise<ScanResult> {\n const nodes: DiscoveryNode[] = [];\n const hintTerms = (ctx.hint ?? '').toLowerCase().split(/[\\s,]+/).filter(Boolean);\n for (const [category, tools] of Object.entries(KNOWN_TOOLS)) {\n for (const tool of tools) {\n const path = (ctx.commandExists ?? commandExists)(tool);\n if (!path) continue;\n const boosted = hintTerms.some((t) => tool.includes(t));\n nodes.push({\n id: `saas_tool:${tool}`,\n type: 'saas_tool',\n name: tool,\n discoveredVia: 'installed-app',\n confidence: boosted ? 0.95 : 0.9,\n tags: [category],\n metadata: { category, path },\n });\n }\n }\n return { nodes, edges: [] };\n },\n};\n","import type { Scanner, ScanResult } from './types.js';\nimport type { DiscoveryNode, NodeType } from '../types.js';\nimport { scanListeningPorts } from '../platform.js';\n\n/** Well-known listening ports → node type + service name. */\nexport const PORT_MAP: Record<number, { type: NodeType; service: string }> = {\n 5432: { type: 'database_server', service: 'postgresql' },\n 3306: { type: 'database_server', service: 'mysql' },\n 1433: { type: 'database_server', service: 'sqlserver' },\n 27017: { type: 'database_server', service: 'mongodb' },\n 9200: { type: 'database_server', service: 'elasticsearch' },\n 6379: { type: 'cache_server', service: 'redis' },\n 11211: { type: 'cache_server', service: 'memcached' },\n 9092: { type: 'message_broker', service: 'kafka' },\n 5672: { type: 'message_broker', service: 'rabbitmq' },\n 4222: { type: 'message_broker', service: 'nats' },\n 9090: { type: 'web_service', service: 'prometheus' },\n 3000: { type: 'web_service', service: 'http-app' },\n 8080: { type: 'web_service', service: 'http-app' },\n 8000: { type: 'web_service', service: 'http-app' },\n 80: { type: 'web_service', service: 'http' },\n 443: { type: 'web_service', service: 'https' },\n 8200: { type: 'web_service', service: 'vault' },\n 8500: { type: 'web_service', service: 'consul' },\n 2379: { type: 'web_service', service: 'etcd' },\n 5601: { type: 'web_service', service: 'kibana' },\n 15672: { type: 'web_service', service: 'rabbitmq-management' },\n};\n\n/** Extract distinct listening port numbers from ss/lsof/PowerShell output. */\nexport function extractListeningPorts(raw: string): number[] {\n const ports = new Set<number>();\n for (const m of raw.matchAll(/[:.](\\d{2,5})\\b/g)) {\n const p = Number(m[1]);\n if (p in PORT_MAP) ports.add(p);\n }\n return [...ports];\n}\n\nexport const portsScanner: Scanner = {\n id: 'local-ports',\n title: 'Local listening ports',\n platforms: 'all',\n allowedCommands: ['ss', 'lsof', 'Get-NetTCPConnection'],\n detect: () => true,\n async scan(ctx): Promise<ScanResult> {\n const raw = (ctx.scanListeningPorts ?? scanListeningPorts)();\n const nodes: DiscoveryNode[] = [];\n for (const port of extractListeningPorts(raw)) {\n const { type, service } = PORT_MAP[port]!;\n nodes.push({\n id: `${type}:localhost:${port}`,\n type,\n name: `${service} (:${port})`,\n discoveredVia: 'listening-port',\n confidence: 0.9,\n tags: ['local', service],\n metadata: { port, service, host: 'localhost' },\n });\n }\n return { nodes, edges: [] };\n },\n};\n","/**\n * Confidence rubric for inferred dependency edges (3.2).\n *\n * A single source of truth so every scanner that infers an edge draws its\n * `confidence` from the same strictly-ordered tier set, and downstream consumers\n * (compliance 3.4, anomaly 3.6) can rank evidence quality consistently. All values\n * are in `(0,1]`; confidence is never fabricated — an absent evidence source yields\n * no edge rather than a low-confidence guess.\n */\n\n/** Evidence tiers for inferred dependency edges, strongest first. */\nexport type EvidenceKind =\n | 'established-connection'\n | 'config-declared'\n | 'connection-string'\n | 'co-location';\n\n/** Single source of truth for edge confidence. Strictly ordered, all in (0,1]. */\nexport const CONFIDENCE: Record<EvidenceKind, number> = {\n 'established-connection': 0.85,\n 'config-declared': 0.7,\n 'connection-string': 0.6,\n 'co-location': 0.4,\n};\n\n/** Build a self-describing, auditable evidence string with an ISO-8601 UTC stamp. */\nexport function evidenceLine(kind: EvidenceKind, detail: string): string {\n return `[${kind}] ${detail} @ ${new Date().toISOString()}`;\n}\n","/**\n * Established-connections scanner (3.2).\n *\n * Reads the host's live TCP connections (read-only) and infers `connects_to`\n * edges from a single local `host:localhost` node to any recognized listening\n * service (`${type}:localhost:${port}` from {@link PORT_MAP}). The server-side\n * node is only kept when `portsScanner` also mapped that port in the same run —\n * the in-batch endpoint gate in `runLocalDiscovery` drops edges to ports nothing\n * is listening on (graceful degradation, never an error).\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport { PORT_MAP } from './ports.js';\nimport { CONFIDENCE, evidenceLine } from './confidence.js';\nimport { scanEstablishedConnections } from '../platform.js';\n\nexport interface EstablishedConn { localPort: number; remoteHost: string; remotePort: number; }\n\nconst PORT_RE = /^\\d{1,5}$/;\n\n/** Parse a `host:port` token (IPv4 or `[v6]:port`) into `{ host, port }` or null. */\nfunction splitHostPort(token: string): { host: string; port: number } | null {\n const t = token.trim();\n if (!t) return null;\n const idx = t.lastIndexOf(':');\n if (idx <= 0) return null;\n const host = t.slice(0, idx).replace(/^\\[|\\]$/g, '');\n const portStr = t.slice(idx + 1);\n if (!PORT_RE.test(portStr)) return null;\n const port = Number(portStr);\n if (port < 1 || port > 65535) return null;\n return { host, port };\n}\n\n/**\n * Parse established connections from `ss -tnp`, `lsof -nP`, or PowerShell output\n * (pure, host-independent). Deduplicates and silently ignores unparseable rows.\n *\n * Recognized shapes:\n * - ss: `ESTAB 0 0 127.0.0.1:54321 127.0.0.1:5432 …`\n * - lsof: `node 1 u … TCP 127.0.0.1:54321->127.0.0.1:5432 (ESTABLISHED)`\n * - PS: `127.0.0.1:54321 -> 127.0.0.1:5432`\n */\nexport function parseEstablished(raw: string): EstablishedConn[] {\n const out: EstablishedConn[] = [];\n const seen = new Set<string>();\n const push = (local: { host: string; port: number }, remote: { host: string; port: number }): void => {\n const key = `${local.port}->${remote.host}:${remote.port}`;\n if (seen.has(key)) return;\n seen.add(key);\n out.push({ localPort: local.port, remoteHost: remote.host, remotePort: remote.port });\n };\n\n for (const line of raw.split(/\\r?\\n/)) {\n const l = line.trim();\n if (!l) continue;\n\n // lsof / PowerShell arrow form: local->remote or local -> remote\n const arrow = l.match(/([^\\s]+:\\d{1,5})\\s*->\\s*([^\\s(]+:\\d{1,5})/);\n if (arrow) {\n const local = splitHostPort(arrow[1]);\n const remote = splitHostPort(arrow[2]);\n if (local && remote) push(local, remote);\n continue;\n }\n\n // ss form: whitespace-separated columns; the last two host:port columns are local+peer.\n if (/^ESTAB\\b/i.test(l) || /\\bESTAB\\b/i.test(l)) {\n const cols = l.split(/\\s+/).filter(Boolean);\n const hostPorts = cols\n .map((c) => splitHostPort(c))\n .filter((x): x is { host: string; port: number } => x !== null);\n if (hostPorts.length >= 2) {\n const local = hostPorts[hostPorts.length - 2];\n const remote = hostPorts[hostPorts.length - 1];\n push(local, remote);\n }\n }\n }\n return out;\n}\n\nexport const connectionsScanner: Scanner = {\n id: 'local-connections',\n title: 'Local established connections',\n platforms: 'all',\n allowedCommands: ['ss', 'lsof', 'netstat', 'Get-NetTCPConnection'],\n detect: () => true,\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const conns = parseEstablished((ctx.scanEstablishedConnections ?? scanEstablishedConnections)());\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n if (conns.length === 0) return { nodes, edges };\n\n // One stable self node so the in-batch endpoint gate can anchor client-side edges.\n const selfId = 'host:localhost';\n nodes.push({\n id: selfId,\n type: 'host',\n name: 'localhost',\n discoveredVia: 'established-connection',\n confidence: 0.9,\n metadata: { host: 'localhost' },\n tags: ['local'],\n });\n\n const seenEdge = new Set<string>();\n for (const c of conns) {\n const svc = PORT_MAP[c.remotePort];\n if (!svc) continue; // only connect to recognized services\n const targetId = `${svc.type}:localhost:${c.remotePort}`;\n if (seenEdge.has(targetId)) continue;\n seenEdge.add(targetId);\n edges.push({\n sourceId: selfId,\n targetId,\n relationship: 'connects_to',\n evidence: evidenceLine('established-connection', `${c.localPort} -> ${c.remoteHost}:${c.remotePort}`),\n confidence: CONFIDENCE['established-connection'],\n });\n }\n return { nodes, edges };\n },\n};\n","/**\n * Service & reverse-proxy config scanner (3.2).\n *\n * Infers *declared* dependency edges from local config without running any service:\n * - connection-string env vars (`DATABASE_URL`, `REDIS_URL`, …) → `reads_from` /\n * `depends_on` to `${type}:localhost:${port}`, confidence `connection-string` (0.6);\n * - nginx `upstream` / `proxy_pass` host:port → `depends_on`, confidence `config-declared` (0.7);\n * - docker-compose `depends_on:` → `container:<svc> depends_on container:<dep>`, 0.7.\n *\n * **Security boundary:** connection strings routinely embed credentials and\n * `sanitizeUntrusted` (applied later in `insertEdge`) is NOT a credential filter, so\n * this scanner redacts userinfo / `password=` *before* the value reaches `evidence`.\n */\n\nimport type { Scanner, ScanContext, ScanResult } from './types.js';\nimport type { DiscoveryNode, DiscoveryEdge, EdgeRelationship } from '../types.js';\nimport { PORT_MAP } from './ports.js';\nimport { CONFIDENCE, evidenceLine } from './confidence.js';\nimport { findFiles, IS_WIN } from '../platform.js';\n\n/** Connection-string env var names worth inferring an edge from. */\nconst CONN_VAR_RE = /^(DATABASE_URL|[A-Z0-9_]*_DATABASE_URL|REDIS_URL|[A-Z0-9_]*_REDIS_URL|MONGO_URL|MONGODB_URI|AMQP_URL|[A-Z0-9_]*_URL)$/;\n\n/**\n * Connection-string scheme → well-known port. Deliberately **excludes** `http`/\n * `https`: a generic `FOO_URL=https://example.com` is a web link, not a service\n * dependency, and inferring `reads_from` from it would fabricate edges (e.g. CI's\n * `GITHUB_SERVER_URL`). Only datastore/broker schemes qualify.\n */\nconst SCHEME_PORT: Record<string, number> = {\n postgres: 5432, postgresql: 5432, mysql: 3306, mongodb: 27017, redis: 6379,\n amqp: 5672, kafka: 9092, elasticsearch: 9200,\n};\n\n/** Directories to search for service config, platform-appropriate + cwd. */\nfunction configDirs(): string[] {\n const dirs = [process.cwd()];\n if (!IS_WIN) dirs.push('/etc/nginx', '/etc/nginx/conf.d');\n return dirs;\n}\n\n/**\n * Replace credentials in a connection string with `****` (the security boundary).\n * Strips the `user:pass@` userinfo and any `password=`/`pwd=` query token.\n */\nexport function redactConnectionString(url: string): string {\n return url\n // Match the userinfo up to the *last* `@` before the path (`[^/\\s]+`, greedy),\n // so a password containing a literal `@` (e.g. `root:p@ss@host`) is fully redacted.\n .replace(/(\\b[a-z][a-z0-9+.-]*:\\/\\/)([^/\\s]+)@/i, (_m, scheme: string) => `${scheme}****@`)\n .replace(/\\b(password|pwd)=([^&\\s]+)/gi, '$1=****');\n}\n\n/**\n * Parse nginx `upstream { server host:port; }` and `proxy_pass http://host:port`\n * directives into `{ host, port }` targets (pure, host-independent).\n */\nexport function parseNginxUpstreams(raw: string): { host: string; port: number }[] {\n const out: { host: string; port: number }[] = [];\n const seen = new Set<string>();\n const add = (host: string, port: number): void => {\n const key = `${host}:${port}`;\n if (seen.has(key)) return;\n seen.add(key);\n out.push({ host, port });\n };\n for (const m of raw.matchAll(/\\bserver\\s+([a-zA-Z0-9_.-]+):(\\d{1,5})/g)) add(m[1], Number(m[2]));\n for (const m of raw.matchAll(/\\bproxy_pass\\s+https?:\\/\\/([a-zA-Z0-9_.-]+):(\\d{1,5})/g)) add(m[1], Number(m[2]));\n return out;\n}\n\n/**\n * Parse docker-compose `depends_on:` blocks (both list and map form) into a\n * `{ service, dependsOn[] }` array (pure). Indentation-based, dependency-free.\n */\nexport function parseComposeDeps(raw: string): { service: string; dependsOn: string[] }[] {\n const lines = raw.split(/\\r?\\n/);\n const result: { service: string; dependsOn: string[] }[] = [];\n let inServices = false;\n let servicesIndent = -1;\n let current: { service: string; dependsOn: string[] } | null = null;\n let serviceIndent = -1;\n let inDeps = false;\n let depsIndent = -1;\n\n const indentOf = (s: string): number => s.length - s.trimStart().length;\n\n for (const rawLine of lines) {\n if (!rawLine.trim() || rawLine.trim().startsWith('#')) continue;\n const indent = indentOf(rawLine);\n const trimmed = rawLine.trim();\n\n if (/^services:\\s*$/.test(trimmed)) { inServices = true; servicesIndent = indent; continue; }\n if (!inServices) continue;\n if (indent <= servicesIndent && !/^services:/.test(trimmed)) { inServices = false; current = null; inDeps = false; continue; }\n\n // A service header: `<name>:` at the services' child indent. This always ends\n // any open depends_on block (dep items are *more* indented than the service),\n // so it must take precedence even when inDeps is still true.\n const svcMatch = trimmed.match(/^([a-zA-Z0-9._-]+):\\s*$/);\n const isServiceHeader = !!svcMatch && indent > servicesIndent && (serviceIndent === -1 || indent === serviceIndent);\n if (isServiceHeader) {\n current = { service: svcMatch![1], dependsOn: [] };\n result.push(current);\n serviceIndent = indent;\n inDeps = false;\n continue;\n }\n if (!current) continue;\n\n if (/^depends_on:/.test(trimmed)) {\n inDeps = true;\n depsIndent = indent;\n // inline list form: depends_on: [a, b]\n const inline = trimmed.match(/^depends_on:\\s*\\[(.*)\\]\\s*$/);\n if (inline) {\n for (const dep of inline[1].split(',').map((d) => d.trim().replace(/['\"]/g, '')).filter(Boolean)) {\n current.dependsOn.push(dep);\n }\n inDeps = false;\n }\n continue;\n }\n\n if (inDeps) {\n if (indent <= depsIndent) { inDeps = false; }\n else {\n const listItem = trimmed.match(/^-\\s*([a-zA-Z0-9._-]+)\\s*$/); // - dep\n const mapItem = trimmed.match(/^([a-zA-Z0-9._-]+):\\s*$/); // dep: (long form)\n const dep = listItem?.[1] ?? mapItem?.[1];\n if (dep) { if (!current.dependsOn.includes(dep)) current.dependsOn.push(dep); continue; }\n }\n }\n }\n return result;\n}\n\n/**\n * Map a connection-string env var to a candidate edge (pure). Returns the\n * relationship, the target service port, and a **credential-redacted** evidence\n * string — or null when the scheme/port is not recognized.\n */\nexport function parseConnectionString(name: string, url: string):\n { relationship: EdgeRelationship; service: string; port: number; evidence: string } | null {\n const m = url.match(/^([a-z][a-z0-9+.-]*):\\/\\//i);\n if (!m) return null;\n const scheme = m[1].toLowerCase();\n if (!(scheme in SCHEME_PORT)) return null; // datastore/broker schemes only — never http(s)\n const portMatch = url.match(/:\\/\\/[^/]*?:(\\d{1,5})(?:[/?]|$)/);\n const port = portMatch ? Number(portMatch[1]) : SCHEME_PORT[scheme];\n if (!port || !(port in PORT_MAP)) return null;\n\n // postgres/mysql/mongo/redis → reads_from (conservative; writes_to is never\n // inferred to avoid fabricating certainty). amqp/kafka → depends_on.\n const relationship: EdgeRelationship =\n scheme === 'amqp' || scheme === 'kafka' ? 'depends_on' : 'reads_from';\n const redacted = redactConnectionString(url);\n return {\n relationship,\n service: PORT_MAP[port].service,\n port,\n evidence: evidenceLine('connection-string', `${name}=${redacted}`),\n };\n}\n\n/**\n * True only when an env var's **value** is a real datastore/broker connection\n * string (not merely a `*_URL`-shaped name). Value-based so a generic web `*_URL`\n * (e.g. CI's `GITHUB_SERVER_URL`) never makes the scanner self-activate.\n */\nfunction hasConnVar(env: string): boolean {\n for (const line of env.split(/\\r?\\n/)) {\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const name = line.slice(0, eq).trim();\n if (!CONN_VAR_RE.test(name)) continue;\n if (parseConnectionString(name, line.slice(eq + 1).trim())) return true;\n }\n return false;\n}\n\nexport const serviceConfigScanner: Scanner = {\n id: 'service-config',\n title: 'Service & reverse-proxy config',\n platforms: 'all',\n allowedCommands: ['cat', 'grep', 'find', 'printenv', 'ls', 'Get-ChildItem'],\n detect(ctx: ScanContext): boolean {\n const files = (ctx.findFiles ?? findFiles)(configDirs(), ['*.conf', 'nginx.conf', 'docker-compose.y*ml', 'compose.y*ml'], 4, 50);\n if (files.trim().length > 0) return true;\n return hasConnVar(ctx.run('printenv'));\n },\n async scan(ctx: ScanContext): Promise<ScanResult> {\n const nodes: DiscoveryNode[] = [];\n const edges: DiscoveryEdge[] = [];\n const selfId = 'host:localhost';\n let selfEmitted = false;\n const emitSelf = (): void => {\n if (selfEmitted) return;\n selfEmitted = true;\n nodes.push({ id: selfId, type: 'host', name: 'localhost', discoveredVia: 'service-config', confidence: 0.9, metadata: { host: 'localhost' }, tags: ['local'] });\n };\n\n // ── Connection-string env vars ──────────────────────────────────────────\n for (const line of ctx.run('printenv').split(/\\r?\\n/)) {\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const name = line.slice(0, eq).trim();\n const value = line.slice(eq + 1).trim();\n if (!CONN_VAR_RE.test(name)) continue;\n const parsed = parseConnectionString(name, value);\n if (!parsed) continue;\n emitSelf();\n edges.push({\n sourceId: selfId,\n targetId: `${PORT_MAP[parsed.port].type}:localhost:${parsed.port}`,\n relationship: parsed.relationship,\n evidence: parsed.evidence,\n confidence: CONFIDENCE['connection-string'],\n });\n }\n\n // ── nginx upstream / proxy_pass ─────────────────────────────────────────\n const findFilesFn = ctx.findFiles ?? findFiles;\n const nginxFiles = findFilesFn(configDirs(), ['*.conf', 'nginx.conf'], 4, 50)\n .split(/\\r?\\n/).map((s) => s.trim()).filter(Boolean);\n for (const file of nginxFiles) {\n const content = ctx.run(`cat \"${file}\"`);\n if (!content) continue;\n for (const { host, port } of parseNginxUpstreams(content)) {\n if (!(port in PORT_MAP)) continue;\n emitSelf();\n edges.push({\n sourceId: selfId,\n targetId: `${PORT_MAP[port].type}:localhost:${port}`,\n relationship: 'depends_on',\n evidence: evidenceLine('config-declared', `nginx ${file}: ${host}:${port}`),\n confidence: CONFIDENCE['config-declared'],\n });\n }\n }\n\n // ── docker-compose depends_on ───────────────────────────────────────────\n const composeFiles = findFilesFn(configDirs(), ['docker-compose.y*ml', 'compose.y*ml'], 4, 50)\n .split(/\\r?\\n/).map((s) => s.trim()).filter(Boolean);\n for (const file of composeFiles) {\n const content = ctx.run(`cat \"${file}\"`);\n if (!content) continue;\n for (const { service, dependsOn } of parseComposeDeps(content)) {\n if (dependsOn.length === 0) continue;\n const srcId = `container:${service}`;\n nodes.push({ id: srcId, type: 'container', name: service, discoveredVia: 'service-config', confidence: 0.7, metadata: { compose: file }, tags: ['compose'] });\n for (const dep of dependsOn) {\n const depId = `container:${dep}`;\n nodes.push({ id: depId, type: 'container', name: dep, discoveredVia: 'service-config', confidence: 0.7, metadata: { compose: file }, tags: ['compose'] });\n edges.push({\n sourceId: srcId,\n targetId: depId,\n relationship: 'depends_on',\n evidence: evidenceLine('config-declared', `compose ${file}: ${service} depends_on ${dep}`),\n confidence: CONFIDENCE['config-declared'],\n });\n }\n }\n }\n\n return { nodes, edges };\n },\n};\n","import { ScannerRegistry } from './types.js';\nimport { bookmarksScanner } from './bookmarks.js';\nimport { installedAppsScanner } from './installed-apps.js';\nimport { portsScanner } from './ports.js';\nimport { cloudAwsScanner } from './cloud-aws.js';\nimport { cloudGcpScanner } from './cloud-gcp.js';\nimport { cloudAzureScanner } from './cloud-azure.js';\nimport { k8sScanner } from './k8s.js';\nimport { databasesScanner } from './databases.js';\nimport { connectionsScanner } from './connections.js';\nimport { serviceConfigScanner } from './service-config.js';\n\nexport { ScannerRegistry } from './types.js';\nexport type { Scanner, ScanContext, ScanResult } from './types.js';\nexport { bookmarksScanner } from './bookmarks.js';\nexport { installedAppsScanner } from './installed-apps.js';\nexport { portsScanner, extractListeningPorts, PORT_MAP } from './ports.js';\nexport { cloudAwsScanner } from './cloud-aws.js';\nexport { cloudGcpScanner } from './cloud-gcp.js';\nexport { cloudAzureScanner } from './cloud-azure.js';\nexport { k8sScanner } from './k8s.js';\nexport { databasesScanner } from './databases.js';\nexport { connectionsScanner, parseEstablished } from './connections.js';\nexport type { EstablishedConn } from './connections.js';\nexport {\n serviceConfigScanner, parseNginxUpstreams, parseComposeDeps, parseConnectionString, redactConnectionString,\n} from './service-config.js';\nexport { CONFIDENCE, evidenceLine } from './confidence.js';\nexport type { EvidenceKind } from './confidence.js';\nexport { safeJson, parseScanHint, buildReport } from './cloud-util.js';\nexport type { ScanHintParams } from './cloud-util.js';\nexport { definePlugin, validateScanner, ScannerShape } from './spi.js';\nexport type { ScannerPlugin, ScannerPluginApi } from './spi.js';\n\n/** A registry pre-loaded with the built-in deterministic scanners. */\nexport function defaultRegistry(): ScannerRegistry {\n return new ScannerRegistry()\n .register(bookmarksScanner)\n .register(installedAppsScanner)\n .register(portsScanner)\n .register(cloudAwsScanner)\n .register(cloudGcpScanner)\n .register(cloudAzureScanner)\n .register(k8sScanner)\n .register(databasesScanner)\n .register(connectionsScanner)\n .register(serviceConfigScanner);\n}\n","/**\n * Opt-in scanner-plugin loader.\n *\n * Resolves the explicitly configured plugin package names (consent-first: no\n * filesystem auto-scan), dynamically `import()`s each in its own try/catch, and\n * registers its scanners through {@link ScannerRegistry.registerExternal}\n * (validated, namespaced, command-gated, frozen). One bad plugin — a\n * non-resolvable name, a missing/invalid default export, a `register()` that\n * throws, or a malformed scanner that fails zod validation — is logged via\n * `logWarn` and skipped. It never aborts discovery (optional-deps degrade\n * pattern, mirroring `src/semantic/search.ts`).\n */\n\nimport { logInfo, logWarn } from '../logger.js';\nimport type { ScannerRegistry } from './types.js';\nimport type { ScannerPlugin, ScannerPluginApi } from './spi.js';\n\n/**\n * Load each configured plugin package into the given registry. Returns the\n * package names that loaded and registered successfully (for audit/reporting).\n */\nexport async function loadPlugins(\n registry: ScannerRegistry,\n pkgs: readonly string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const pkg of pkgs) {\n try {\n const mod = (await import(pkg)) as { default?: ScannerPlugin };\n const plugin = mod.default;\n if (!plugin || typeof plugin.register !== 'function') {\n logWarn('scanner plugin missing default export', { pkg });\n continue;\n }\n const api: ScannerPluginApi = {\n registerScanner: (scanner) => {\n registry.registerExternal(pkg, scanner);\n },\n };\n plugin.register(api);\n loaded.push(pkg);\n logInfo('scanner plugin loaded', { pkg, name: plugin.name });\n } catch (err) {\n logWarn('scanner plugin load failed (skipped)', {\n pkg,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n return loaded;\n}\n","/**\n * Deterministic, LLM-free local discovery.\n *\n * Runs every applicable scanner from a {@link ScannerRegistry}, deduplicates and\n * persists the resulting nodes/edges, and returns counts. This is what powers the\n * MCP `run_discovery` tool without requiring any model — the host LLM can then\n * enrich the catalog via the read tools. (The Claude-driven loop in agent.ts\n * remains available as the optional, richer turnkey path.)\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { DiscoveryNode, DiscoveryEdge, Contributor } from '../types.js';\nimport { diffTopology } from '../diff.js';\nimport type { TopologyInput, TopologyDelta } from '../diff.js';\nimport { PLATFORM, run, commandExists, scanListeningPorts, scanEstablishedConnections, findFiles } from '../platform.js';\nimport { scanAllBookmarks } from '../bookmarks.js';\nimport { defaultRegistry, ScannerRegistry } from '../scanners/registry.js';\nimport { loadPlugins } from '../scanners/loader.js';\nimport type { ScanContext } from '../scanners/types.js';\n\nexport interface LocalDiscoveryOptions {\n hint?: string;\n registry?: ScannerRegistry;\n /**\n * Opt-in scanner plugin package names to load (consent-first). Honoured only\n * when no explicit `registry` is supplied — an injected registry is used\n * verbatim, so tests and advanced callers stay in full control.\n */\n plugins?: string[];\n /** Called after each scanner with a short progress line. */\n onProgress?: (line: string) => void;\n /**\n * Override individual {@link ScanContext} fields. Production omits this and the\n * real platform helpers are used; tests inject deterministic sources\n * (`commandExists`, `scanListeningPorts`, `scanBookmarks`) so the built-in\n * scanners run against known inputs without depending on the host.\n */\n ctx?: Partial<ScanContext>;\n /**\n * `'replace'` (default) preserves today's append-all behavior: every scanned\n * node/edge is upserted/inserted into the session. `'update'` (2.1 incremental\n * discovery) reads the session's prior state, diffs it against the current scan,\n * and applies only the delta — upserting changed/added, pruning removed (nodes\n * and edges), and stamping `last_scanned_at`. Same `session_id` either way.\n */\n mode?: 'replace' | 'update';\n}\n\nexport interface LocalDiscoveryResult {\n nodes: number;\n edges: number;\n scanners: string[];\n /** Present only in `'update'` mode: the delta applied to the session (2.1). */\n delta?: TopologyDelta;\n}\n\n/**\n * Lift scanner `DiscoveryNode`/`DiscoveryEdge` output into the `NodeRow`/`EdgeRow`\n * shape {@link diffTopology} compares. Only the `DRIFT_FIELDS` (node) and the\n * `(sourceId, targetId, relationship)` logical key (edge) are read by the diff, so\n * `id`/`depth`/`discoveredAt` carry placeholders the diff ignores. Nodes keep the\n * raw `id` so they key against the prior state's `getNodes` ids correctly.\n */\nfunction projectScan(sessionId: string, nodes: DiscoveryNode[], edges: DiscoveryEdge[]): TopologyInput {\n const ts = new Date().toISOString();\n return {\n nodes: nodes.map((n) => ({\n ...n, sessionId, discoveredAt: ts, depth: 0,\n tags: n.tags ?? [], metadata: n.metadata ?? {},\n })),\n edges: edges.map((e) => ({ ...e, id: '', sessionId, discoveredAt: ts })),\n };\n}\n\nexport async function runLocalDiscovery(\n db: CartographyDB,\n sessionId: string,\n opts: LocalDiscoveryOptions = {},\n): Promise<LocalDiscoveryResult> {\n const registry = opts.registry ?? defaultRegistry();\n // Load opt-in plugins only onto the default registry — never mutate an\n // explicitly injected one. A broken plugin is logged + skipped, never fatal.\n if (!opts.registry && opts.plugins && opts.plugins.length > 0) {\n await loadPlugins(registry, opts.plugins);\n }\n const ctx: ScanContext = { hint: opts.hint, platform: PLATFORM, run, commandExists, scanListeningPorts, scanEstablishedConnections, findFiles, scanBookmarks: scanAllBookmarks, ...opts.ctx };\n\n const nodes = new Map<string, DiscoveryNode>();\n const edges: DiscoveryEdge[] = [];\n const ran: string[] = [];\n\n for (const scanner of registry.forPlatform(PLATFORM)) {\n try {\n if (!(await scanner.detect(ctx))) continue;\n const result = await scanner.scan(ctx);\n ran.push(scanner.id);\n for (const node of result.nodes) {\n // Keep the highest-confidence record on id collision.\n const prev = nodes.get(node.id);\n if (!prev || node.confidence > prev.confidence) nodes.set(node.id, node);\n }\n edges.push(...result.edges);\n opts.onProgress?.(`${scanner.title}: +${result.nodes.length} nodes`);\n } catch (err) {\n opts.onProgress?.(`${scanner.title}: failed (${err instanceof Error ? err.message : String(err)})`);\n }\n }\n\n // 2.9: derive a Contributor once from the (attributed) session row so the same\n // logical node accumulates one contributor per machine instead of being clobbered.\n const session = db.getSession(sessionId);\n const baseAttribution: Omit<Contributor, 'confidence'> | undefined = session?.machineId\n ? {\n machineId: session.machineId,\n hostname: session.hostname ?? 'unknown-host',\n user: session.user ?? 'unknown-user',\n organization: session.tenant,\n at: new Date().toISOString(),\n }\n : undefined;\n\n // Only persist edges whose endpoints exist (matches the replace-mode invariant).\n const validEdges = edges.filter((e) => nodes.has(e.sourceId) && nodes.has(e.targetId));\n\n if (opts.mode === 'update') {\n // Incremental rescan (2.1): diff the current scan against the session's prior\n // state and apply only the delta — keeping the same session_id, pruning gone\n // entities, and preserving 2.9 contributors on unchanged nodes (they are not\n // re-upserted) while appending the running machine's contributor to changed/\n // added nodes via `attribution`.\n const prior: TopologyInput = { nodes: db.getNodes(sessionId), edges: db.getEdges(sessionId) };\n const current = projectScan(sessionId, [...nodes.values()], validEdges);\n const delta = diffTopology(prior, current);\n db.applyTopologyDelta(\n sessionId, delta,\n baseAttribution ? { ...baseAttribution, confidence: 0.5 } : undefined,\n );\n return { nodes: current.nodes.length, edges: current.edges.length, scanners: ran, delta };\n }\n\n // replace (default): existing append-all behavior, unchanged.\n for (const node of nodes.values()) {\n db.upsertNode(\n sessionId, node, 0,\n baseAttribution ? { ...baseAttribution, confidence: node.confidence } : undefined,\n );\n }\n for (const edge of validEdges) db.insertEdge(sessionId, edge);\n\n return { nodes: nodes.size, edges: validEdges.length, scanners: ran };\n}\n\n/**\n * Adapter matching the MCP `DiscoveryFn` signature. When no explicit `registry`\n * is provided, the opt-in `plugins` list is loaded onto the default registry on\n * each discovery run (so late-installed plugins are picked up).\n */\nexport function localDiscoveryFn(registry?: ScannerRegistry, plugins?: string[]) {\n return async (db: CartographyDB, sessionId: string, opts: { hint?: string; mode?: 'replace' | 'update' }) => {\n const r = await runLocalDiscovery(db, sessionId, { hint: opts.hint, mode: opts.mode, registry, plugins });\n return { nodes: r.nodes, edges: r.edges, ...(r.delta ? { delta: r.delta } : {}) };\n };\n}\n","/**\n * Format-agnostic (de)serialization for host config files. JSON keeps 2-space\n * indentation; TOML uses smol-toml; YAML uses the `yaml` package. Empty or\n * whitespace-only input parses to an empty object so a fresh install starts clean.\n */\n\nimport { parse as parseToml, stringify as stringifyToml } from 'smol-toml';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ConfigFormat } from './types.js';\n\nexport function parseConfig(text: string, format: ConfigFormat): Record<string, unknown> {\n if (!text.trim()) return {};\n try {\n switch (format) {\n case 'json':\n return JSON.parse(text) as Record<string, unknown>;\n case 'toml':\n return parseToml(text) as Record<string, unknown>;\n case 'yaml':\n return (parseYaml(text) as Record<string, unknown>) ?? {};\n }\n } catch (err) {\n // Fail loudly with an actionable message rather than crashing with a raw\n // parser stack or silently discarding (and then clobbering) the user's config.\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse existing ${format.toUpperCase()} config: ${detail}`);\n }\n}\n\nexport function serializeConfig(obj: Record<string, unknown>, format: ConfigFormat): string {\n switch (format) {\n case 'json':\n return JSON.stringify(obj, null, 2) + '\\n';\n case 'toml':\n return stringifyToml(obj) + '\\n';\n case 'yaml':\n return stringifyYaml(obj);\n }\n}\n","/**\n * Idempotent deep-merge of plain objects. Used by client specs to splice a server\n * entry into an existing config without clobbering unrelated keys. Arrays and\n * scalars from `source` replace those in `target`; nested plain objects merge\n * recursively. `source` is never mutated; `target` is cloned.\n */\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T {\n const out: Record<string, unknown> = { ...target };\n for (const [key, value] of Object.entries(source)) {\n const existing = out[key];\n if (isPlainObject(existing) && isPlainObject(value)) {\n out[key] = deepMerge(existing, value);\n } else {\n out[key] = value;\n }\n }\n return out as T;\n}\n","/**\n * Shared entry-shape helpers. Most hosts accept the Claude-Desktop-style object\n * (`command`/`args`/`env` for stdio, `url`/`type` for HTTP); specs that diverge\n * (VS Code `type`, Zed `source`, Codex TOML, …) compose or override these.\n */\n\nimport type { ServerEntry } from './types.js';\n\n/** The common `{ command, args, env }` | `{ type:'http', url }` server object. */\nexport function mcpServerObject(entry: ServerEntry): Record<string, unknown> {\n if (entry.url) {\n return { type: 'http', url: entry.url, ...(entry.env ? { env: entry.env } : {}) };\n }\n return {\n command: entry.command,\n args: entry.args ?? [],\n ...(entry.env ? { env: entry.env } : {}),\n };\n}\n","/**\n * The canonical Cartography MCP server entry every client spec receives. Kept in\n * one place so the npx invocation (and any future packaging change) is defined\n * exactly once and reused across all hosts.\n */\n\nimport type { ServerEntry } from './types.js';\n\nexport const PACKAGE_NAME = '@datasynx/agentic-ai-cartography';\nexport const MCP_BIN = 'cartography-mcp';\nexport const DEFAULT_SERVER_NAME = 'cartography';\n\nexport interface EntryOptions {\n /** `http` produces a `url` entry; otherwise stdio via npx. */\n transport?: 'stdio' | 'http';\n /** HTTP endpoint (used when transport === 'http'). */\n url?: string;\n /** Extra environment variables to inject. */\n env?: Record<string, string>;\n /** Extra package arguments appended after the bin name (e.g. `--db`, `--session`). */\n packageArgs?: string[];\n}\n\n/** Build the default server entry (stdio via `npx` unless an HTTP url is given). */\nexport function defaultServerEntry(opts: EntryOptions = {}): ServerEntry {\n if (opts.transport === 'http') {\n return { url: opts.url ?? 'http://127.0.0.1:3737/mcp', ...(opts.env ? { env: opts.env } : {}) };\n }\n const args = ['-y', '--package', PACKAGE_NAME, MCP_BIN, ...(opts.packageArgs ?? [])];\n return { command: 'npx', args, ...(opts.env ? { env: opts.env } : {}) };\n}\n","/**\n * Generic install planning/applying. `planInstall` is pure relative to a provided\n * context (reads the existing file, computes the merged result, never writes) so\n * it powers both `--dry-run` and the real write in `applyInstall`.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { parseConfig, serializeConfig } from './format.js';\nimport { DEFAULT_SERVER_NAME } from './entry.js';\nimport type { ClientSpec, ConfigFormat, OsKind, ResolveContext, Scope, ServerEntry } from './types.js';\n\nexport interface InstallPlan {\n client: string;\n label: string;\n path: string;\n format: ConfigFormat;\n /** Existing file contents ('' when the file does not exist). */\n before: string;\n /** Contents that would be written. */\n after: string;\n fileExists: boolean;\n changed: boolean;\n note?: string;\n}\n\n/** Detect the current OS kind. */\nexport function currentOs(): OsKind {\n if (process.platform === 'win32') return 'win';\n if (process.platform === 'darwin') return 'mac';\n return 'linux';\n}\n\n/** Build a real resolve context from the running environment. */\nexport function defaultContext(scope: Scope): ResolveContext {\n return { scope, os: currentOs(), home: homedir(), cwd: process.cwd(), env: process.env };\n}\n\nexport interface PlanOptions {\n serverName?: string;\n entry: ServerEntry;\n}\n\n/** Compute what installing `entry` into `spec` would change. Reads the config file but never writes. */\nexport function planInstall(spec: ClientSpec, ctx: ResolveContext, opts: PlanOptions): InstallPlan {\n const path = spec.path(ctx);\n if (!path) {\n throw new Error(`${spec.label} does not support the \"${ctx.scope}\" scope.`);\n }\n const fileExists = existsSync(path);\n const before = fileExists ? readFileSync(path, 'utf8') : '';\n const existing = parseConfig(before, spec.format);\n const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);\n const after = serializeConfig(merged, spec.format);\n return {\n client: spec.id,\n label: spec.label,\n path,\n format: spec.format,\n before,\n after,\n fileExists,\n changed: after !== before,\n ...(spec.note ? { note: spec.note } : {}),\n };\n}\n\n/** Write a plan's result to disk, creating parent directories as needed. */\nexport function applyInstall(plan: InstallPlan): void {\n mkdirSync(dirname(plan.path), { recursive: true });\n writeFileSync(plan.path, plan.after, 'utf8');\n}\n\n/** A minimal line-oriented diff for `--dry-run` output. */\nexport function renderDiff(before: string, after: string): string {\n if (before === after) return ' (no changes)';\n const b = before.length ? before.split('\\n') : [];\n const a = after.split('\\n');\n const out: string[] = [];\n const max = Math.max(b.length, a.length);\n for (let i = 0; i < max; i++) {\n if (b[i] === a[i]) {\n if (a[i] !== undefined) out.push(` ${a[i]}`);\n } else {\n if (b[i] !== undefined) out.push(`- ${b[i]}`);\n if (a[i] !== undefined) out.push(`+ ${a[i]}`);\n }\n }\n return out.join('\\n');\n}\n","/**\n * Registry of supported MCP hosts. The merge engine and CLI are generic; every\n * host-specific detail (config path, format, schema key) lives in a `ClientSpec`\n * here. New hosts are added by appending a spec — no engine changes required.\n */\n\nimport { join } from 'node:path';\nimport { deepMerge } from './merge.js';\nimport { mcpServerObject } from './shapes.js';\nimport type { ClientSpec, ResolveContext, ServerEntry } from './types.js';\n\n/** Helper: a host that stores stdio/http servers under a top-level JSON key. */\nfunction jsonKeyedClient(args: {\n id: string;\n label: string;\n key: string;\n globalPath: ClientSpec['path'];\n projectPath?: ClientSpec['path'];\n note?: string;\n}): ClientSpec {\n return {\n id: args.id,\n label: args.label,\n format: 'json',\n note: args.note,\n path: (ctx) => (ctx.scope === 'project' ? args.projectPath?.(ctx) : args.globalPath(ctx)),\n apply: (existing, name, entry) =>\n deepMerge(existing, { [args.key]: { [name]: mcpServerObject(entry) } }),\n };\n}\n\n// ── Claude Code (reference JSON host) ────────────────────────────────────────\nconst claudeCode = jsonKeyedClient({\n id: 'claude-code',\n label: 'Claude Code',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.claude.json'),\n projectPath: (ctx) => join(ctx.cwd, '.mcp.json'),\n});\n\n// ── Cursor ───────────────────────────────────────────────────────────────────\nconst cursor = jsonKeyedClient({\n id: 'cursor',\n label: 'Cursor',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.cursor', 'mcp.json'),\n projectPath: (ctx) => join(ctx.cwd, '.cursor', 'mcp.json'),\n});\n\n// ── VS Code / GitHub Copilot ─────────────────────────────────────────────────\n// Diverges from the norm: the key is `servers` (not `mcpServers`) and stdio\n// entries carry an explicit `type: \"stdio\"`.\nfunction vscodeServerObject(entry: ServerEntry): Record<string, unknown> {\n if (entry.url) return { type: 'http', url: entry.url, ...(entry.env ? { env: entry.env } : {}) };\n return { type: 'stdio', command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n}\n\n/** VS Code user-profile directory per OS. */\nfunction vscodeUserDir(ctx: ResolveContext): string {\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Code', 'User');\n if (ctx.os === 'mac') return join(ctx.home, 'Library', 'Application Support', 'Code', 'User');\n return join(ctx.home, '.config', 'Code', 'User');\n}\n\nconst vscode: ClientSpec = {\n id: 'vscode',\n label: 'VS Code (Copilot)',\n format: 'json',\n note: 'Uses the `servers` key (not `mcpServers`) — the most common copy-paste mistake.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.vscode', 'mcp.json') : join(vscodeUserDir(ctx), 'mcp.json')),\n apply: (existing, name, entry) => deepMerge(existing, { servers: { [name]: vscodeServerObject(entry) } }),\n};\n\n// ── OpenAI Codex CLI ─────────────────────────────────────────────────────────\n// TOML, table form `[mcp_servers.<name>]` (NOT `[mcp.servers.\"name\"]`).\nconst codex: ClientSpec = {\n id: 'codex',\n label: 'Codex CLI',\n format: 'toml',\n note: 'Project scope only loads in \"trusted\" projects.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.codex', 'config.toml') : join(ctx.home, '.codex', 'config.toml')),\n apply: (existing, name, entry) => deepMerge(existing, { mcp_servers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Windsurf (Codeium) ───────────────────────────────────────────────────────\n// Global-only; path is identical across OSes (USERPROFILE === home on Windows).\nconst windsurf = jsonKeyedClient({\n id: 'windsurf',\n label: 'Windsurf',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.codeium', 'windsurf', 'mcp_config.json'),\n});\n\n// ── Cline & Roo Code (VS Code globalStorage hosts) ───────────────────────────\n/** `<VS Code user dir>/globalStorage/<extensionId>/settings/cline_mcp_settings.json`. */\nfunction codeGlobalStorage(ctx: ResolveContext, extensionId: string): string {\n return join(vscodeUserDir(ctx), 'globalStorage', extensionId, 'settings', 'cline_mcp_settings.json');\n}\n\nconst cline: ClientSpec = {\n id: 'cline',\n label: 'Cline',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? undefined : codeGlobalStorage(ctx, 'saoudrizwan.claude-dev')),\n // Cline augments the standard object with its own auto-approve/disable flags.\n apply: (existing, name, entry) =>\n deepMerge(existing, { mcpServers: { [name]: { ...mcpServerObject(entry), alwaysAllow: [], disabled: false } } }),\n};\n\nconst roo: ClientSpec = {\n id: 'roo',\n label: 'Roo Code',\n format: 'json',\n note: 'Project .roo/mcp.json takes precedence over the global settings.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.roo', 'mcp.json') : codeGlobalStorage(ctx, 'rooveterinaryinc.roo-cline')),\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Zed ──────────────────────────────────────────────────────────────────────\n// Key is `context_servers`; manual entries require `\"source\": \"custom\"`.\nconst zed: ClientSpec = {\n id: 'zed',\n label: 'Zed',\n format: 'json',\n note: 'Manual servers need \"source\": \"custom\"; remote uses an mcp-remote bridge.',\n path: (ctx) => {\n if (ctx.scope === 'project') return join(ctx.cwd, '.zed', 'settings.json');\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Zed', 'settings.json');\n return join(ctx.home, '.config', 'zed', 'settings.json');\n },\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { source: 'custom', url: entry.url }\n : { source: 'custom', command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n return deepMerge(existing, { context_servers: { [name]: inner } });\n },\n};\n\n// ── JetBrains AI Assistant / Junie ───────────────────────────────────────────\nconst junie: ClientSpec = {\n id: 'junie',\n label: 'JetBrains / Junie',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.junie', 'mcp', 'mcp.json') : join(ctx.home, '.junie', 'mcp', 'mcp.json')),\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Gemini CLI ───────────────────────────────────────────────────────────────\n// stdio: command/args/env; HTTP uses the `httpUrl` key (not `url`).\nconst gemini: ClientSpec = {\n id: 'gemini',\n label: 'Gemini CLI',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.gemini', 'settings.json') : join(ctx.home, '.gemini', 'settings.json')),\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { httpUrl: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : mcpServerObject(entry);\n return deepMerge(existing, { mcpServers: { [name]: inner } });\n },\n};\n\n// ── Goose (Block) ────────────────────────────────────────────────────────────\n// YAML under `extensions:`. Built-ins (`type: builtin`) are preserved by the merge.\nconst goose: ClientSpec = {\n id: 'goose',\n label: 'Goose',\n format: 'yaml',\n note: 'Verify the extension shape against current Goose docs; built-ins are left untouched.',\n path: (ctx) => {\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Block', 'goose', 'config', 'config.yaml');\n return join(ctx.home, '.config', 'goose', 'config.yaml');\n },\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { name, type: 'streamable_http', enabled: true, uri: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : { name, type: 'stdio', enabled: true, command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n return deepMerge(existing, { extensions: { [name]: inner } });\n },\n};\n\n// ── OpenHands (All-Hands-AI) ─────────────────────────────────────────────────\n// `[mcp]` holds arrays (stdio_servers / shttp_servers / sse_servers), not a keyed\n// object — so entries are appended/replaced by identity, not deep-merged.\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nconst openhands: ClientSpec = {\n id: 'openhands',\n label: 'OpenHands',\n format: 'toml',\n note: 'SHTTP is preferred; SSE is legacy. Only api_key is supported (no arbitrary headers).',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, 'config.toml') : join(ctx.home, '.openhands', 'config.toml')),\n apply: (existing, name, entry) => {\n const mcp = isObj(existing.mcp) ? { ...existing.mcp } : {};\n const key = entry.url ? 'shttp_servers' : 'stdio_servers';\n const arr = Array.isArray(mcp[key]) ? [...(mcp[key] as Record<string, unknown>[])] : [];\n const item: Record<string, unknown> = entry.url\n ? { url: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : { name, command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n const matches = (s: Record<string, unknown>) => (entry.url ? s.url === entry.url : s.name === name);\n const idx = arr.findIndex(matches);\n if (idx >= 0) arr[idx] = item;\n else arr.push(item);\n mcp[key] = arr;\n return { ...existing, mcp };\n },\n};\n\n// ── Claude Desktop ───────────────────────────────────────────────────────────\n// JSON `mcpServers`. (One-click installs are also available via the .mcpb bundle.)\nconst claudeDesktop: ClientSpec = {\n id: 'claude-desktop',\n label: 'Claude Desktop',\n format: 'json',\n note: 'One-click install is also available via the .mcpb bundle (npm run build:mcpb).',\n path: (ctx) => {\n if (ctx.scope === 'project') return undefined;\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');\n if (ctx.os === 'mac') return join(ctx.home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n return join(ctx.home, '.config', 'Claude', 'claude_desktop_config.json');\n },\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n/** All registered clients, in display order. Extended by later milestones. */\nexport const CLIENTS: ClientSpec[] = [\n claudeCode, cursor, vscode, codex, windsurf,\n cline, roo, zed, junie, gemini,\n goose, openhands, claudeDesktop,\n];\n\nexport function getClient(id: string): ClientSpec | undefined {\n return CLIENTS.find((c) => c.id === id);\n}\n\nexport function listClients(): ReadonlyArray<Pick<ClientSpec, 'id' | 'label' | 'format' | 'note'>> {\n return CLIENTS.map(({ id, label, format, note }) => ({ id, label, format, note }));\n}\n","/**\n * One-click install deeplinks for hosts that support them. Cursor expects a\n * **Base64**-encoded server config; VS Code expects **URL-encoded** JSON — mixing\n * the two encodings is a classic mistake, so each has its own helper.\n */\n\nimport { mcpServerObject } from './shapes.js';\nimport type { ServerEntry } from './types.js';\n\n/** `cursor://…/mcp/install?name=<name>&config=<base64 JSON of the server config>`. */\nexport function cursorDeeplink(name: string, entry: ServerEntry): string {\n const config = Buffer.from(JSON.stringify(mcpServerObject(entry))).toString('base64');\n const params = new URLSearchParams({ name, config });\n return `cursor://anysphere.cursor-deeplink/mcp/install?${params.toString()}`;\n}\n\nexport interface VscodeDeeplinkOptions {\n /** Target VS Code Insiders (`vscode-insiders://`). */\n insiders?: boolean;\n}\n\n/** `vscode://mcp/install?<URL-encoded JSON>` where the JSON is `{ name, ...serverConfig }`. */\nexport function vscodeDeeplink(name: string, entry: ServerEntry, opts: VscodeDeeplinkOptions = {}): string {\n const scheme = opts.insiders ? 'vscode-insiders' : 'vscode';\n const payload = encodeURIComponent(JSON.stringify({ name, ...mcpServerObject(entry) }));\n return `${scheme}://mcp/install?${payload}`;\n}\n\n/** A `code --add-mcp '<json>'` CLI one-liner (alternative to the deeplink). */\nexport function codeAddMcpCommand(name: string, entry: ServerEntry): string {\n return `code --add-mcp '${JSON.stringify({ name, ...mcpServerObject(entry) })}'`;\n}\n","// PreToolUse Safety Hook — enforces the read-only allowlist on all Bash calls.\n//\n// This is the Claude-Code-specific adapter. The authoritative policy lives in\n// ./allowlist.ts and is shared with the MCP server, so safety is identical no\n// matter which agent or model drives discovery.\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\nimport { checkReadOnly } from './allowlist.js';\n\nexport type { HookCallback };\n\nexport const safetyHook: HookCallback = async (input, _toolUseID, _options) => {\n // Only intercept PreToolUse events (other hook events don't have tool_name)\n if (!('tool_name' in input)) return {};\n if ((input as { tool_name: string }).tool_name !== 'Bash') return {};\n\n const cmd = (((input as { tool_input: { command?: string } }).tool_input)?.command ?? '').trim();\n\n // An empty command runs nothing — allow it (matches Claude Code's no-op behavior).\n if (!cmd) {\n return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };\n }\n\n const decision = checkReadOnly(cmd);\n if (!decision.allowed) {\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `BLOCKED: ${decision.reason} — read-only allowlist policy`,\n },\n };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n },\n };\n};\n","// Provider-neutral agent layer — the seam that decouples the discovery loop from\n// any single LLM SDK. Each provider yields the existing DiscoveryEvent stream, so\n// runDiscovery() and every caller of it stay backend-agnostic.\n\nimport type { CartographyDB } from '../db.js';\nimport type { CartographyConfig, ProviderName } from '../types.js';\nimport type { DiscoveryEvent, AskUserFn } from '../agent.js';\n\nexport type { ProviderName } from '../types.js';\n\n/** Inputs a provider needs to run one discovery session. */\nexport interface AgentRunContext {\n config: CartographyConfig;\n db: CartographyDB;\n sessionId: string;\n systemPrompt: string;\n initialPrompt: string;\n onAskUser?: AskUserFn;\n /** Wall-clock deadline (epoch ms). Providers MUST stop and emit `done` past this. */\n deadlineMs: number;\n}\n\n/**\n * A provider-neutral agent backend. Yields the existing `DiscoveryEvent` stream.\n * Implementations MUST: enforce `config.maxTurns`, honor `ctx.deadlineMs`, route\n * every shell command through `checkReadOnly` + `run()`, and write an audit row per\n * executed tool so the audit trail is identical across providers.\n */\nexport interface AgentProvider {\n readonly name: ProviderName;\n /** Throw a clear Error if the optional dep / API key / CLI is missing. */\n ensureAvailable(config: CartographyConfig): Promise<void>;\n run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent>;\n}\n\nexport type ProviderFactory = () => AgentProvider;\n\n/** Registry of provider factories; the single source of truth for valid provider names. */\nexport class ProviderRegistry {\n private readonly factories = new Map<ProviderName, ProviderFactory>();\n\n register(name: ProviderName, factory: ProviderFactory): void {\n this.factories.set(name, factory);\n }\n\n has(name: string): name is ProviderName {\n return this.factories.has(name as ProviderName);\n }\n\n resolve(name: ProviderName): AgentProvider {\n const f = this.factories.get(name);\n if (!f) throw new Error(`Unknown provider \"${name}\". Available: ${this.names().join(', ')}`);\n return f();\n }\n\n names(): ProviderName[] {\n return [...this.factories.keys()];\n }\n}\n","// PostToolUse Audit Hook — records every executed tool call into activity_events.\n//\n// Governance/audit trail: for each tool the agent runs during discovery we persist\n// { tool, command, result bytes, timestamp } so an operator can review what ran.\n// Audit failures must never break discovery, so the write is best-effort.\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\nimport type { CartographyDB } from './db.js';\nimport { logDebug } from './logger.js';\n\n/** Build a PostToolUse hook bound to a session that logs executed tools to the catalog. */\nexport function createAuditHook(db: CartographyDB, sessionId: string): HookCallback {\n return async (input) => {\n try {\n if (!('tool_name' in input)) return {};\n const i = input as { tool_name: string; tool_input?: { command?: string }; tool_response?: unknown };\n const command = i.tool_input?.command ?? JSON.stringify(i.tool_input ?? {}).slice(0, 2000);\n const response = typeof i.tool_response === 'string' ? i.tool_response : JSON.stringify(i.tool_response ?? '');\n db.insertEvent(sessionId, {\n eventType: 'tool_executed',\n process: i.tool_name,\n pid: process.pid,\n command,\n resultBytes: Buffer.byteLength(response),\n });\n } catch (err) {\n logDebug(`audit hook failed to record event: ${String(err)}`);\n }\n return {};\n };\n}\n","// Claude provider — the Anthropic Claude Agent SDK backend. This is the verbatim\n// move of the original runDiscovery() loop (src/agent.ts) behind the AgentProvider\n// seam, so the Claude path is behavior-preserved. The SDK is dynamically imported\n// so its absence degrades gracefully (ProviderUnavailableError).\n\nimport { createCartographyTools } from '../tools.js';\nimport { safetyHook } from '../safety.js';\nimport { createAuditHook } from '../audit.js';\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\n\nfunction createClaudeProvider(): AgentProvider {\n return {\n name: 'claude',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n try {\n await import('@anthropic-ai/claude-agent-sdk');\n } catch {\n throw new Error(\n 'Claude provider unavailable: the @anthropic-ai/claude-agent-sdk package is not installed.\\n' +\n ' Install: npm install @anthropic-ai/claude-agent-sdk',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const { query } = await import('@anthropic-ai/claude-agent-sdk');\n const tools = await createCartographyTools(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n\n let turnCount = 0;\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.models.lead,\n maxTurns: config.maxTurns,\n systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartography__save_node',\n 'mcp__cartography__save_edge',\n 'mcp__cartography__get_catalog',\n 'mcp__cartography__scan_bookmarks',\n 'mcp__cartography__scan_browser_history',\n 'mcp__cartography__scan_installed_apps',\n 'mcp__cartography__scan_local_databases',\n 'mcp__cartography__scan_k8s_resources',\n 'mcp__cartography__scan_aws_resources',\n 'mcp__cartography__scan_gcp_resources',\n 'mcp__cartography__scan_azure_resources',\n 'mcp__cartography__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n PostToolUse: [{ hooks: [createAuditHook(db, sessionId)] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n // Wall-clock timeout guard\n if (Date.now() > deadlineMs) {\n yield { kind: 'error', text: 'Discovery timeout — wall-clock limit reached' };\n yield { kind: 'done' };\n return;\n }\n\n if (msg.type === 'assistant') {\n turnCount++;\n yield { kind: 'turn', turn: turnCount };\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n yield { kind: 'thinking', text: block.text };\n }\n if (block.type === 'tool_use') {\n yield {\n kind: 'tool_call',\n tool: block.name as string,\n input: block.input as Record<string, unknown>,\n };\n }\n }\n }\n\n if (msg.type === 'user') {\n const content = msg.message?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (\n typeof block === 'object' &&\n block !== null &&\n 'type' in block &&\n (block as { type: string }).type === 'tool_result'\n ) {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n yield { kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text };\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n yield { kind: 'done' };\n return;\n }\n }\n },\n };\n}\n\nexport { createClaudeProvider };\n","// The neutral Bash tool for non-SDK providers (OpenAI, Ollama).\n//\n// The Claude Agent SDK ships a built-in `Bash` tool gated by `safetyHook`. OpenAI\n// and Ollama have no such built-in, so this exposes an in-process `Bash` tool whose\n// handler enforces the read-only allowlist via `checkReadOnly` BEFORE delegating to\n// `run()` (which re-checks `checkReadOnly` again — defense in depth). This is the\n// load-bearing safety parity: every provider's shell calls pass through the same\n// authoritative policy in src/allowlist.ts.\n\nimport { z } from 'zod';\nimport { checkReadOnly } from '../allowlist.js';\nimport { run, IS_WIN } from '../platform.js';\nimport type { AgentTool } from '../tools.js';\n\nexport function createBashTool(): AgentTool {\n const shell = IS_WIN ? 'powershell' : 'posix';\n return {\n name: 'Bash',\n description:\n 'Run a read-only shell command (inspect ports, processes, config). ' +\n 'Mutating or destructive commands are blocked by the read-only allowlist.',\n inputShape: { command: z.string().describe('The read-only shell command to run') },\n annotations: { readOnlyHint: true, openWorldHint: true },\n handler: async (args) => {\n const command = String(args['command'] ?? '').trim();\n if (!command) return { content: [{ type: 'text', text: '' }] };\n const decision = checkReadOnly(command, { shell });\n if (!decision.allowed) {\n return {\n content: [\n { type: 'text', text: `BLOCKED: ${decision.reason ?? 'not read-only'} — read-only allowlist policy` },\n ],\n };\n }\n const output = run(command) || '(no output)';\n return { content: [{ type: 'text', text: output }] };\n },\n };\n}\n","// Minimal zod → JSON Schema converter for the discovery tools' input shapes.\n//\n// OpenAI and Ollama function-calling both expect JSON Schema for tool parameters.\n// Rather than add the `zod-to-json-schema` runtime dependency, this implements a\n// small, sufficient converter covering exactly the zod constructs the discovery\n// tools use (string, number with min/max, boolean, enum, array, record, optional,\n// default, describe). An unsupported construct throws a clear Error naming the\n// field, so a future tool can't be silently mis-converted.\n\nimport { z } from 'zod';\n\nexport interface JsonSchema {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n additionalProperties: boolean;\n}\n\n/** Unwrap optional/default/describe wrappers, tracking whether the field is required. */\ninterface Unwrapped {\n schema: z.ZodTypeAny;\n required: boolean;\n description?: string;\n}\n\nfunction unwrap(schema: z.ZodTypeAny): Unwrapped {\n let current = schema;\n let required = true;\n let description: string | undefined = current.description;\n\n // Peel wrapper types until we reach the concrete inner type.\n // zod v4 exposes the inner type via `.def.innerType`.\n for (;;) {\n const def = (current as unknown as { def?: { type?: string; innerType?: z.ZodTypeAny } }).def;\n const typeName = def?.type;\n if (typeName === 'optional' || typeName === 'default') {\n required = false;\n const inner = def?.innerType;\n if (!inner) break;\n current = inner;\n description = description ?? current.description;\n continue;\n }\n if (typeName === 'nullable') {\n const inner = def?.innerType;\n if (!inner) break;\n current = inner;\n description = description ?? current.description;\n continue;\n }\n break;\n }\n return { schema: current, required, description };\n}\n\nfunction convert(schema: z.ZodTypeAny, field: string): Record<string, unknown> {\n const def = (schema as unknown as { def?: Record<string, unknown> }).def;\n const typeName = def?.['type'] as string | undefined;\n\n switch (typeName) {\n case 'string':\n return { type: 'string' };\n case 'number': {\n const out: Record<string, unknown> = { type: 'number' };\n // zod v4 stores checks under def.checks\n const checks = (def?.['checks'] as { _zod?: { def?: { check?: string; value?: number } } }[]) ?? [];\n for (const c of checks) {\n const cd = c?._zod?.def;\n if (cd?.check === 'greater_than') out['minimum'] = cd.value;\n if (cd?.check === 'less_than') out['maximum'] = cd.value;\n }\n return out;\n }\n case 'boolean':\n return { type: 'boolean' };\n case 'enum': {\n const entries = def?.['entries'] as Record<string, string> | undefined;\n const values = entries ? Object.values(entries) : [];\n return { type: 'string', enum: values };\n }\n case 'array': {\n const element = def?.['element'] as z.ZodTypeAny | undefined;\n return { type: 'array', items: element ? convert(unwrap(element).schema, field) : {} };\n }\n case 'record':\n return { type: 'object', additionalProperties: true };\n default:\n throw new Error(\n `zod-schema: unsupported zod construct \"${typeName ?? 'unknown'}\" on field \"${field}\". ` +\n 'Extend src/providers/zod-schema.ts to support it.',\n );\n }\n}\n\n/** Convert a flat ZodRawShape used by the discovery tools to a JSON Schema object. */\nexport function shapeToJsonSchema(shape: z.ZodRawShape): JsonSchema {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, raw] of Object.entries(shape)) {\n const { schema, required: isRequired, description } = unwrap(raw as z.ZodTypeAny);\n const prop = convert(schema, key);\n if (description) prop['description'] = description;\n properties[key] = prop;\n if (isRequired) required.push(key);\n }\n\n return { type: 'object', properties, required, additionalProperties: false };\n}\n","// Provider-neutral audit writer — extracted from the Claude PostToolUse hook\n// (src/audit.ts) so non-SDK providers (OpenAI, Ollama) share the exact same audit\n// trail. Every executed tool writes one `tool_executed` row into `activity_events`.\n// Audit failures must never break discovery, so the write is best-effort.\n\nimport type { CartographyDB } from '../db.js';\nimport { logDebug } from '../logger.js';\n\n/** Record one executed tool call into the catalog's audit trail (best-effort). */\nexport function recordToolEvent(\n db: CartographyDB,\n sessionId: string,\n evt: { tool: string; command: string; response: string },\n): void {\n try {\n db.insertEvent(sessionId, {\n eventType: 'tool_executed',\n process: evt.tool,\n pid: process.pid,\n command: evt.command,\n resultBytes: Buffer.byteLength(evt.response),\n });\n } catch (err) {\n logDebug(`audit writer failed to record event: ${String(err)}`);\n }\n}\n","// Shared tool-calling loop for non-SDK providers (OpenAI, Ollama).\n//\n// Both providers delegate here, parameterized by a `chat()` callback that performs\n// one model round-trip. This loop owns: turn counting + maxTurns enforcement,\n// deadlineMs (wall-clock) checks, dispatch of tool calls to AgentTool handlers\n// (matched by name), the neutral Bash tool, the audit write (db.insertEvent via\n// recordToolEvent), and emission of turn/thinking/tool_call/tool_result/error/done.\n// This guarantees OpenAI and Ollama emit identical DiscoveryEvent kinds.\n\nimport type { CartographyDB } from '../db.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentTool } from '../tools.js';\nimport { recordToolEvent } from './audit.js';\n\n/** A model's request to call one tool. */\nexport interface ToolCall {\n /** Opaque id correlating the call to its result (provider-specific). */\n id: string;\n name: string;\n args: Record<string, unknown>;\n}\n\n/** The neutral result of one model round-trip. */\nexport interface ChatTurn {\n /** Assistant free text for this turn (may be empty). */\n text: string;\n /** Tool calls the model requested this turn (empty ⇒ the model is done). */\n toolCalls: ToolCall[];\n}\n\n/** One executed tool call's result, fed back to the model on the next round-trip. */\nexport interface ToolOutcome {\n id: string;\n name: string;\n output: string;\n}\n\n/**\n * Performs one model round-trip given the prior tool outcomes. On the first call\n * `outcomes` is empty. Returns the assistant turn (text + any tool calls). The\n * callback owns building/threading the provider-specific message history.\n */\nexport type ChatFn = (outcomes: ToolOutcome[]) => Promise<ChatTurn>;\n\nexport interface LoopOptions {\n db: CartographyDB;\n sessionId: string;\n tools: AgentTool[];\n maxTurns: number;\n deadlineMs: number;\n}\n\n/** Dispatch a single tool call to its handler, recording the audit row. */\nasync function dispatchTool(\n call: ToolCall,\n tools: AgentTool[],\n db: CartographyDB,\n sessionId: string,\n): Promise<string> {\n const tool = tools.find((t) => t.name === call.name);\n if (!tool) {\n const text = `ERROR: unknown tool \"${call.name}\"`;\n recordToolEvent(db, sessionId, { tool: call.name, command: JSON.stringify(call.args).slice(0, 2000), response: text });\n return text;\n }\n let output: string;\n try {\n const result = await tool.handler(call.args);\n output = result.content.map((c) => c.text).join('\\n');\n } catch (err) {\n output = `ERROR: ${err instanceof Error ? err.message : String(err)}`;\n }\n // Audit: the Bash command for Bash, else the (clamped) JSON args — mirrors src/audit.ts.\n const command =\n call.name === 'Bash'\n ? String(call.args['command'] ?? '')\n : JSON.stringify(call.args).slice(0, 2000);\n recordToolEvent(db, sessionId, { tool: call.name, command, response: output });\n return output;\n}\n\n/**\n * Drive a provider's tool-calling loop, yielding the neutral DiscoveryEvent stream.\n * Honors maxTurns and the wall-clock deadline; always terminates with `done` (or an\n * `error` + `done` pair on failure).\n */\nexport async function* runToolLoop(opts: LoopOptions, chat: ChatFn): AsyncIterable<DiscoveryEvent> {\n const { db, sessionId, tools, maxTurns, deadlineMs } = opts;\n let outcomes: ToolOutcome[] = [];\n let turn = 0;\n\n try {\n while (turn < maxTurns) {\n if (Date.now() > deadlineMs) {\n yield { kind: 'error', text: 'Discovery timeout — wall-clock limit reached' };\n yield { kind: 'done' };\n return;\n }\n\n const result = await chat(outcomes);\n turn++;\n yield { kind: 'turn', turn };\n\n if (result.text) yield { kind: 'thinking', text: result.text };\n\n if (result.toolCalls.length === 0) {\n // No tool calls ⇒ the model has finished.\n yield { kind: 'done' };\n return;\n }\n\n const nextOutcomes: ToolOutcome[] = [];\n for (const call of result.toolCalls) {\n yield { kind: 'tool_call', tool: call.name, input: call.args };\n const output = await dispatchTool(call, tools, db, sessionId);\n yield { kind: 'tool_result', tool: call.name, output };\n nextOutcomes.push({ id: call.id, name: call.name, output });\n }\n outcomes = nextOutcomes;\n }\n\n // maxTurns exhausted without a natural stop — terminate cleanly.\n yield { kind: 'done' };\n } catch (err) {\n yield { kind: 'error', text: `Discovery error: ${err instanceof Error ? err.message : String(err)}` };\n yield { kind: 'done' };\n }\n}\n","// OpenAI provider — drives discovery via the chat-completions function-calling\n// loop over the SAME neutral tool handlers the Claude path uses. The `openai`\n// package is an optional dependency, lazily imported; its absence (or a missing\n// OPENAI_API_KEY) degrades with a clear Error, never a crash.\n\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\nimport { buildCartographyToolHandlers, type AgentTool } from '../tools.js';\nimport { createBashTool } from './shell.js';\nimport { shapeToJsonSchema } from './zod-schema.js';\nimport { runToolLoop, type ChatFn, type ToolOutcome } from './loop.js';\n\n/** Minimal structural types for the slice of the OpenAI SDK we use (no `any`). */\ninterface OpenAIToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\ninterface OpenAIMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string | null;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n}\ninterface OpenAIChoice {\n message: { role: 'assistant'; content: string | null; tool_calls?: OpenAIToolCall[] };\n}\ninterface OpenAICompletion {\n choices: OpenAIChoice[];\n}\ninterface OpenAIClient {\n chat: {\n completions: {\n create(req: {\n model: string;\n messages: OpenAIMessage[];\n tools: { type: 'function'; function: { name: string; description: string; parameters: unknown } }[];\n tool_choice: 'auto';\n }): Promise<OpenAICompletion>;\n };\n };\n}\ntype OpenAIModule = { default: new (opts: { apiKey: string; baseURL?: string }) => OpenAIClient };\n\nfunction toOpenAITools(tools: AgentTool[]) {\n return tools.map((t) => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: shapeToJsonSchema(t.inputShape) },\n }));\n}\n\nfunction createOpenAIProvider(): AgentProvider {\n return {\n name: 'openai',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n try {\n await import('openai' as string);\n } catch {\n throw new Error(\n 'OpenAI provider unavailable: the `openai` package is not installed.\\n' +\n ' Install: npm install openai',\n );\n }\n if (!process.env['OPENAI_API_KEY']) {\n throw new Error(\n 'OpenAI provider unavailable: OPENAI_API_KEY is not set.\\n' +\n ' Set it: export OPENAI_API_KEY=sk-...',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const mod = (await import('openai' as string)) as unknown as OpenAIModule;\n const apiKey = process.env['OPENAI_API_KEY'] ?? '';\n const baseURL = process.env['OPENAI_BASE_URL'];\n const client = new mod.default({ apiKey, ...(baseURL ? { baseURL } : {}) });\n\n const handlers = await buildCartographyToolHandlers(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n const tools: AgentTool[] = [...handlers, createBashTool()];\n const openaiTools = toOpenAITools(tools);\n\n const messages: OpenAIMessage[] = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: initialPrompt },\n ];\n\n const chat: ChatFn = async (outcomes: ToolOutcome[]) => {\n // Append the previous turn's tool results before the next round-trip.\n for (const oc of outcomes) {\n messages.push({ role: 'tool', tool_call_id: oc.id, content: oc.output });\n }\n\n const completion = await client.chat.completions.create({\n model: config.models.lead,\n messages,\n tools: openaiTools,\n tool_choice: 'auto',\n });\n const choice = completion.choices[0]?.message;\n const text = choice?.content ?? '';\n const toolCalls = choice?.tool_calls ?? [];\n\n // Record the assistant turn so the next round-trip has full context.\n messages.push({ role: 'assistant', content: text || null, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });\n\n return {\n text,\n toolCalls: toolCalls.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n args: parseArgs(tc.function.arguments),\n })),\n };\n };\n\n yield* runToolLoop({ db, sessionId, tools, maxTurns: config.maxTurns, deadlineMs }, chat);\n },\n };\n}\n\nfunction parseArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw || '{}') as unknown;\n return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport { createOpenAIProvider };\n","// Ollama provider — drives discovery via Ollama's native /api/chat endpoint using\n// built-in `fetch` (no SDK dependency). Reuses the SAME neutral tool handlers and\n// the shared tool-calling loop as OpenAI, so it emits identical DiscoveryEvent\n// kinds and shares the read-only/audit safety path. Resolves the host from\n// OLLAMA_HOST (default http://127.0.0.1:11434); an unreachable host degrades with a\n// clear `ollama serve` hint rather than crashing.\n\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\nimport { buildCartographyToolHandlers, type AgentTool } from '../tools.js';\nimport { createBashTool } from './shell.js';\nimport { shapeToJsonSchema } from './zod-schema.js';\nimport { runToolLoop, type ChatFn, type ToolOutcome } from './loop.js';\n\nconst DEFAULT_HOST = 'http://127.0.0.1:11434';\n\nfunction host(): string {\n return (process.env['OLLAMA_HOST'] || DEFAULT_HOST).replace(/\\/+$/, '');\n}\n\n/** Minimal structural types for the slice of the Ollama /api/chat API we use. */\ninterface OllamaToolCall {\n function: { name: string; arguments: Record<string, unknown> };\n}\ninterface OllamaMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string;\n tool_calls?: OllamaToolCall[];\n}\ninterface OllamaChatResponse {\n message?: { role: 'assistant'; content?: string; tool_calls?: OllamaToolCall[] };\n}\n\nfunction toOllamaTools(tools: AgentTool[]) {\n return tools.map((t) => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: shapeToJsonSchema(t.inputShape) },\n }));\n}\n\nfunction createOllamaProvider(): AgentProvider {\n return {\n name: 'ollama',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n const base = host();\n try {\n const res = await fetch(`${base}/api/tags`, { method: 'GET' });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n } catch {\n throw new Error(\n `Ollama provider unavailable: not reachable at ${base}.\\n` +\n ' Start it: ollama serve (or set OLLAMA_HOST=<url>)',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const base = host();\n\n const handlers = await buildCartographyToolHandlers(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n const tools: AgentTool[] = [...handlers, createBashTool()];\n const ollamaTools = toOllamaTools(tools);\n\n const messages: OllamaMessage[] = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: initialPrompt },\n ];\n\n const chat: ChatFn = async (outcomes: ToolOutcome[]) => {\n // Ollama tool results are matched positionally (no per-call id).\n for (const oc of outcomes) {\n messages.push({ role: 'tool', content: oc.output });\n }\n\n const res = await fetch(`${base}/api/chat`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ model: config.models.lead, messages, tools: ollamaTools, stream: false }),\n });\n if (!res.ok) {\n throw new Error(`Ollama /api/chat returned HTTP ${res.status}`);\n }\n const data = (await res.json()) as OllamaChatResponse;\n const text = data.message?.content ?? '';\n const toolCalls = data.message?.tool_calls ?? [];\n\n messages.push({\n role: 'assistant',\n content: text,\n ...(toolCalls.length ? { tool_calls: toolCalls } : {}),\n });\n\n return {\n text,\n toolCalls: toolCalls.map((tc, i) => ({\n id: `${tc.function.name}:${i}`,\n name: tc.function.name,\n args: tc.function.arguments ?? {},\n })),\n };\n };\n\n yield* runToolLoop({ db, sessionId, tools, maxTurns: config.maxTurns, deadlineMs }, chat);\n },\n };\n}\n\nexport { createOllamaProvider };\n","// The default provider registry: the single source of truth for valid provider\n// names. Factories lazily build providers; each provider lazily imports its own\n// optional dependency, so importing this module never pulls an optional SDK.\n\nimport { ProviderRegistry } from './types.js';\nimport { createClaudeProvider } from './claude.js';\nimport { createOpenAIProvider } from './openai.js';\nimport { createOllamaProvider } from './ollama.js';\n\nexport function createDefaultRegistry(): ProviderRegistry {\n const r = new ProviderRegistry();\n r.register('claude', createClaudeProvider);\n r.register('openai', createOpenAIProvider);\n r.register('ollama', createOllamaProvider);\n return r;\n}\n\nexport const defaultProviderRegistry = createDefaultRegistry();\n","import type { CartographyDB } from './db.js';\nimport type { CartographyConfig } from './types.js';\nimport { IS_WIN, IS_MAC, PLATFORM } from './platform.js';\nimport { defaultProviderRegistry } from './providers/registry.js';\nimport type { AgentRunContext } from './providers/types.js';\n\n// ── Discovery Event Types ────────────────────────────────────────────────────\n\nexport type DiscoveryEvent =\n | { kind: 'thinking'; text: string }\n | { kind: 'tool_call'; tool: string; input: Record<string, unknown> }\n | { kind: 'tool_result'; tool: string; output: string }\n | { kind: 'turn'; turn: number }\n | { kind: 'error'; text: string }\n | { kind: 'done' };\n\nexport type AskUserFn = (question: string, context?: string) => Promise<string>;\n\n// ── runDiscovery ─────────────────────────────────────────────────────────────\n\nexport async function runDiscovery(\n config: CartographyConfig,\n db: CartographyDB,\n sessionId: string,\n onEvent?: (event: DiscoveryEvent) => void,\n onAskUser?: AskUserFn,\n hint?: string,\n): Promise<void> {\n const hintSection = hint\n ? `\\n⚡ USER HINT (HIGH PRIORITY): The user wants to find these specific tools: \"${hint}\"\\n → Run scan_installed_apps(searchHint: \"${hint}\") IMMEDIATELY and save found tools as saas_tool nodes!\\n`\n : '';\n\n // Platform-specific instructions for the agent\n const platformName = IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux';\n const networkScanCmd = IS_WIN\n ? 'Get-NetTCPConnection -State Listen (PowerShell) → identify all listening ports/processes'\n : IS_MAC\n ? 'lsof -iTCP -sTCP:LISTEN -n -P && ps aux → identify all listening ports/processes'\n : 'ss -tlnp && ps aux → identify all listening ports/processes';\n const readOnlyTools = IS_WIN\n ? 'Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem, curl, docker inspect, kubectl get'\n : IS_MAC\n ? 'lsof, ps, cat, head, curl -s, docker inspect, kubectl get'\n : 'ss, ps, cat, head, curl -s, docker inspect, kubectl get';\n const processCmd = IS_WIN ? 'Get-Process' : 'ps aux';\n\n const systemPrompt = `You are an infrastructure discovery agent. Map the complete system landscape — local services, SaaS tools, AND all installed apps/tools of the user.\nPLATFORM: ${platformName} (${PLATFORM})\n${hintSection}\n━━ MANDATORY SEQUENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSTEP 1 — Browser Bookmarks (ALWAYS FIRST):\n Call scan_bookmarks() → classify every returned domain:\n • Business tools (GitHub, Notion, Jira, Linear, Vercel, AWS, Datadog, etc.) → save_node as saas_tool\n • Internal hosts (IPs, custom.company.com:PORT) → save_node as web_service\n • Personal (social media, news, streaming, shopping) → IGNORE, do NOT save\n\nSTEP 2 — Browser History (ASK FOR CONSENT FIRST):\n Call ask_user with question: \"May I scan your browser history anonymously? I only extract hostnames (no URLs, no personal data) to discover additional tools you use regularly. Answer yes or no.\"\n If user says yes → call scan_browser_history(minVisits: 5) → classify business tools as saas_tool nodes\n If user says no → skip and proceed to Step 3\n\nSTEP 3 — Installed Apps & Tools (VERY IMPORTANT):\n Call scan_installed_apps() → classify ALL found apps/tools:\n • IDEs (VS Code, Cursor, Windsurf, JetBrains, etc.) → save_node as saas_tool with category=\"ide\"\n • Office & productivity (Word, Excel, Notion, Obsidian, etc.) → save_node as saas_tool with category=\"productivity\"\n • Dev tools (Docker, kubectl, git, Node, Python, etc.) → save_node as saas_tool with category=\"dev-tool\"\n • Business apps (Slack, Zoom, HubSpot, Salesforce, etc.) → save_node as saas_tool with category=\"business\"\n • Browsers (Chrome, Firefox, Safari, etc.) → save_node as saas_tool with category=\"browser\"\n • Design tools (Figma, Sketch, Adobe, etc.) → save_node as saas_tool with category=\"design\"\n Save ALL relevant tools — even offline/local ones!\n\nSTEP 4 — Local Databases & Infrastructure:\n Call scan_local_databases() → discover running DB servers and SQLite files from installed apps\n • PostgreSQL running → save_node as database_server (id: \"database_server:localhost:5432\")\n • MySQL running → save_node as database_server (id: \"database_server:localhost:3306\")\n • MongoDB running → save_node as database_server\n • Redis running → save_node as cache_server\n • SQLite files in app directories → save_node as database if clearly a business app DB\n Then run: ${networkScanCmd}\n Also run: ${processCmd} → identify running services\n Deepen each service: DB→schemas, API→endpoints, Queue→topics\n\nSTEP 5 — Cloud & Kubernetes (if CLI available):\n scan_k8s_resources() → Nodes, Services, Pods, Deployments, Ingresses\n scan_aws_resources() → EC2, RDS, ELB, EKS, ElastiCache, S3 (if AWS CLI + credentials)\n scan_gcp_resources() → Compute, SQL, GKE, Cloud Run, Functions (if gcloud + auth)\n scan_azure_resources() → VMs, AKS, SQL, Redis, WebApps (if az CLI + login)\n Errors / \"not available\" → ignore, continue with next tool\n\nSTEP 6 — Config Files:\n .env, docker-compose.yml, application.yml, kubernetes/*.yml\n Extract host:port only — NO credentials\n\nSTEP 7 — Clarifying Questions:\n Use ask_user() when: a service is unclear, context is missing, or user input would be helpful\n Examples: \"What environment is this (dev/staging/prod)?\", \"Is <host> an internal tool?\"\n\nSTEP 8 — EDGES (CRITICAL — do NOT skip!):\n After discovering nodes, ALWAYS map relationships with save_edge:\n • Developer uses IDE → save_edge(\"saas_tool:vscode\", \"saas_tool:github.com\", \"uses\")\n • App connects to Database → save_edge(app_id, db_id, \"connects_to\")\n • Service calls API → save_edge(service_id, api_id, \"calls\")\n • Container contains Service → save_edge(container_id, service_id, \"contains\")\n • Service reads from Queue → save_edge(service_id, queue_id, \"reads_from\")\n • Service writes to Database → save_edge(service_id, db_id, \"writes_to\")\n • App depends on Cache → save_edge(app_id, cache_id, \"depends_on\")\n Think: which tools does the developer use together? What connects to what?\n Use get_catalog to see all node IDs before saving edges.\n\nSTEP 9 — Done when all leads are exhausted.\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nPORT MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,\n9092=kafka, 5672=rabbitmq, 80/443/8080/3000=web_service,\n9090=prometheus, 8500=consul, 8200=vault, 2379=etcd\n\nPLATFORM-SPECIFIC NOTES (${platformName}):\n${IS_WIN ? `• Use PowerShell commands: Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem\n• Do NOT use Unix commands (ss, ps aux, find, which, head, grep) — they won't work on Windows\n• Use $env:LOCALAPPDATA, $env:APPDATA for app data paths\n• Registry scan for installed programs is handled by scan_installed_apps` : IS_MAC ? `• Use lsof -iTCP -sTCP:LISTEN -n -P for port scanning (ss is NOT available on macOS)\n• Use ps aux for process listing\n• Applications are in /Applications and ~/Applications\n• Homebrew (brew) for package management` : `• Use ss -tlnp for port scanning\n• Use ps aux for process listing\n• Check dpkg, snap, flatpak for installed packages\n• Check Snap/Flatpak browser variants for bookmarks`}\n\nRULES:\n• Read-only only (${readOnlyTools})\n• Node IDs: \"type:host:port\" or \"type:name\" — no paths, no credentials\n• saas_tool IDs: \"saas_tool:github.com\", \"saas_tool:vscode\", \"saas_tool:cursor\"\n• Installed-app IDs: \"saas_tool:<appname>\" e.g. \"saas_tool:slack\", \"saas_tool:docker-desktop\"\n• Confidence: 0.9 directly observed, 0.7 from config/bookmarks/apps, 0.5 inferred\n• metadata allowed: { description, category, port, version, path } — no passwords\n• Call get_catalog before save_node → avoid duplicates\n• Save edges whenever connections are clearly identifiable\n• Max crawl depth: ${config.maxDepth} hops from an entry point — do not chase leads deeper than this\n\nEntry points: ${config.entryPoints.join(', ')}`;\n\n const initialPrompt = hint\n ? `Start discovery with USER HINT: \"${hint}\".\nImmediately run scan_installed_apps(searchHint: \"${hint}\") to search for these tools.\nThen scan_bookmarks, then local services.\nUse ask_user when you need context from the user.`\n : `Start discovery now.\nFirst, IMMEDIATELY run scan_bookmarks — before using ss or ps.\nThen ask for browser history consent (Step 2).\nThen scan_installed_apps() for all installed apps and tools.\nThen scan_local_databases() for database servers and SQLite files.\nThen systematically scan local services, then config files.\nFinally, map all edges (Step 8 — critical!) before finishing.\nUse ask_user when you need context from the user.`;\n\n const MAX_DISCOVERY_MS = 30 * 60 * 1000; // 30-minute wall-clock timeout\n const startTime = Date.now();\n const deadlineMs = startTime + MAX_DISCOVERY_MS;\n\n // Resolve the agent backend from the registry. The Claude path is byte-for-byte\n // the original loop, moved verbatim into src/providers/claude.ts; OpenAI/Ollama\n // run the shared tool-calling loop over the same neutral tool handlers.\n const provider = defaultProviderRegistry.resolve(config.provider ?? 'claude');\n await provider.ensureAvailable(config);\n\n const ctx: AgentRunContext = {\n config,\n db,\n sessionId,\n systemPrompt,\n initialPrompt,\n onAskUser,\n deadlineMs,\n };\n\n try {\n for await (const event of provider.run(ctx)) {\n onEvent?.(event);\n if (event.kind === 'done') return;\n // Fallback wall-clock guard in addition to the provider's own check.\n if (Date.now() > deadlineMs) {\n onEvent?.({ kind: 'error', text: `Discovery timeout after ${MAX_DISCOVERY_MS / 60000} minutes` });\n onEvent?.({ kind: 'done' });\n return;\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n onEvent?.({ kind: 'error', text: `Discovery error: ${message}` });\n throw err;\n }\n}\n\n","/**\n * Cost attribution (3.3) — turn the topology into a FinOps lens.\n *\n * A pluggable `CostSource` yields `{ owner, cost }` keyed by node id; `enrichCosts`\n * applies it to a session via targeted, idempotent UPDATEs (never `INSERT OR\n * REPLACE`, so other node fields are untouched). The first source is `CsvCostSource`\n * — deterministic, provider-agnostic, no new dependency. Live billing-API sources\n * (AWS Cost Explorer, GCP/Azure) are future implementations of the same interface.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport { CostEntrySchema } from './types.js';\nimport type { CostEntry } from './types.js';\nimport { logWarn } from './logger.js';\n\n/** One attribution line keyed to a node. `cost`/`owner` may be partially present. */\nexport interface CostRecord {\n nodeId: string;\n owner?: string;\n cost?: CostEntry;\n}\n\n/** A pluggable provider of cost/owner attribution, keyed by node id. */\nexport interface CostSource {\n readonly id: string;\n /** Attribution keyed by node id. An absent/unauthorized source resolves to an empty map (degrade). */\n fetch(): Promise<Map<string, CostRecord>>;\n}\n\nexport interface EnrichResult {\n source: string;\n total: number;\n matched: number;\n unmatched: number;\n unmatchedIds: string[];\n}\n\n/** How a CSV row is resolved to a node id when no explicit `nodeId` column is given. */\nexport type MatchStrategy = 'nodeId' | 'name' | 'tag';\n\nexport interface CsvCostSourceOptions {\n filePath: string;\n match?: MatchStrategy;\n db?: CartographyDB;\n sessionId?: string;\n}\n\n/** Split one CSV line into fields, honoring double-quoted fields with escaped quotes. */\nfunction splitCsvLine(line: string): string[] {\n const out: string[] = [];\n let cur = '';\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (line[i + 1] === '\"') { cur += '\"'; i++; } else { inQuotes = false; }\n } else cur += ch;\n } else if (ch === '\"') {\n inQuotes = true;\n } else if (ch === ',') {\n out.push(cur); cur = '';\n } else cur += ch;\n }\n out.push(cur);\n return out.map((s) => s.trim());\n}\n\n/**\n * Parse a cost CSV (`nodeId,owner,amount,currency,period[,source]`, header required)\n * into validated `CostRecord[]`. Each row's cost fields are validated via\n * `CostEntrySchema`; a malformed row is skipped with a `logWarn` (counts only, no\n * owner PII) — the batch never aborts.\n */\nexport function parseCostCsv(text: string): CostRecord[] {\n const lines = text.split(/\\r?\\n/).filter((l) => l.trim().length > 0);\n if (lines.length === 0) return [];\n const header = splitCsvLine(lines[0]).map((h) => h.toLowerCase());\n const col = (name: string): number => header.indexOf(name);\n const iNode = col('nodeid');\n const iOwner = col('owner');\n const iAmount = col('amount');\n const iCurrency = col('currency');\n const iPeriod = col('period');\n const iSource = col('source');\n if (iNode < 0) {\n logWarn('cost csv: missing required \"nodeId\" header column');\n return [];\n }\n\n const records: CostRecord[] = [];\n for (let r = 1; r < lines.length; r++) {\n const f = splitCsvLine(lines[r]);\n const nodeId = f[iNode];\n if (!nodeId) { logWarn(`cost csv: row ${r + 1} skipped (empty nodeId)`); continue; }\n\n const rec: CostRecord = { nodeId };\n if (iOwner >= 0 && f[iOwner]) rec.owner = f[iOwner];\n\n const amountRaw = iAmount >= 0 ? f[iAmount] : '';\n if (amountRaw) {\n const parsed = CostEntrySchema.safeParse({\n amount: Number(amountRaw),\n currency: iCurrency >= 0 ? f[iCurrency] : undefined,\n period: iPeriod >= 0 ? f[iPeriod] : undefined,\n ...(iSource >= 0 && f[iSource] ? { source: f[iSource] } : {}),\n });\n if (!parsed.success) {\n logWarn(`cost csv: row ${r + 1} skipped (invalid cost fields)`);\n // Keep an owner-only record if owner is present; otherwise drop the row.\n if (!rec.owner) continue;\n } else {\n rec.cost = parsed.data;\n }\n }\n if (rec.owner || rec.cost) records.push(rec);\n }\n return records;\n}\n\n/** CSV-backed cost source. Resolves row→node ids per the chosen `MatchStrategy`. */\nexport class CsvCostSource implements CostSource {\n readonly id: string;\n constructor(private readonly opts: CsvCostSourceOptions) {\n const base = opts.filePath.split(/[\\\\/]/).pop() ?? opts.filePath;\n this.id = `csv:${base}`;\n }\n\n async fetch(): Promise<Map<string, CostRecord>> {\n const text = readFileSync(resolve(this.opts.filePath), 'utf-8');\n const records = parseCostCsv(text);\n const match = this.opts.match ?? 'nodeId';\n const out = new Map<string, CostRecord>();\n\n if (match === 'nodeId') {\n for (const rec of records) out.set(rec.nodeId, rec);\n return out;\n }\n\n // name / tag resolution needs the session's nodes to build a lookup index.\n if (!this.opts.db || !this.opts.sessionId) {\n logWarn(`cost csv: match '${match}' requires db + sessionId; falling back to nodeId`);\n for (const rec of records) out.set(rec.nodeId, rec);\n return out;\n }\n const nodes = this.opts.db.getNodes(this.opts.sessionId);\n const index = new Map<string, string>(); // key (name or tag) → node id\n for (const n of nodes) {\n if (match === 'name') index.set(n.name, n.id);\n else for (const t of n.tags) index.set(t, n.id);\n }\n for (const rec of records) {\n const resolved = index.get(rec.nodeId);\n out.set(resolved ?? rec.nodeId, { ...rec, nodeId: resolved ?? rec.nodeId });\n }\n return out;\n }\n}\n\n/**\n * Idempotent post-pass: apply a source's attribution to a session via targeted\n * UPDATEs. Re-running with the same source yields the same DB state. Unmatched\n * rows (no such node id) are reported, never written.\n */\nexport async function enrichCosts(db: CartographyDB, sessionId: string, source: CostSource): Promise<EnrichResult> {\n const records = await source.fetch();\n let matched = 0;\n const unmatchedIds: string[] = [];\n for (const [nodeId, rec] of records) {\n const ok = db.enrichNodeAttribution(sessionId, nodeId, {\n owner: rec.owner ?? undefined,\n cost: rec.cost ?? undefined,\n });\n if (ok) matched++; else unmatchedIds.push(nodeId);\n }\n return { source: source.id, total: records.size, matched, unmatched: unmatchedIds.length, unmatchedIds };\n}\n","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB, GraphSummary } from './db.js';\nimport type { ComplianceReport } from './compliance/types.js';\nimport type { NodeRow, EdgeRow, TopologyDiff } from './types.js';\nimport { NODE_TYPE_GROUPS } from './types.js';\nimport { buildMapData } from './mapper.js';\nimport { shadeVariant } from './cluster.js';\nimport { hexToPixel } from './hex.js';\n\n// ── Layer assignment ─────────────────────────────────────────────────────────\n\nfunction nodeLayer(type: string): string {\n for (const [layer, types] of Object.entries(NODE_TYPE_GROUPS)) {\n if ((types as readonly string[]).includes(type)) return layer;\n }\n return 'other';\n}\n\nconst LAYER_LABELS: Record<string, string> = {\n saas: '☁ SaaS Tools',\n web: '🌐 Web / API',\n data: '🗄 Data Layer',\n messaging: '📨 Messaging',\n infra: '🖥 Infrastructure',\n config: '📄 Config',\n other: '❓ Other',\n};\n\nconst LAYER_ORDER = ['saas', 'web', 'data', 'messaging', 'infra', 'config', 'other'];\n\n// ── Icons & Labels ───────────────────────────────────────────────────────────\n\nconst MERMAID_ICONS: Record<string, string> = {\n host: '🖥',\n database_server: '🗄',\n database: '🗄',\n table: '📋',\n web_service: '🌐',\n api_endpoint: '🔌',\n cache_server: '⚡',\n message_broker: '📨',\n queue: '📬',\n topic: '📢',\n container: '📦',\n pod: '☸',\n k8s_cluster: '☸',\n config_file: '📄',\n saas_tool: '☁',\n unknown: '❓',\n};\n\nconst EDGE_LABELS: Record<string, string> = {\n connects_to: '→',\n reads_from: 'reads',\n writes_to: 'writes',\n calls: 'calls',\n contains: 'contains',\n depends_on: 'depends on',\n};\n\n// Class colors per type (dark-theme friendly)\nconst MERMAID_CLASSES: Record<string, string> = {\n host: 'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database_server:'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database: 'fill:#163352,stroke:#3a8ad4,color:#bdf',\n table: 'fill:#0f2a40,stroke:#2a6090,color:#9bd',\n web_service: 'fill:#1a3a1a,stroke:#3a9a3a,color:#bfb',\n api_endpoint: 'fill:#0f2a0f,stroke:#2a7a2a,color:#9d9',\n cache_server: 'fill:#3a2a0a,stroke:#ca8a0a,color:#fda',\n message_broker: 'fill:#2a1a3a,stroke:#7a3aaa,color:#daf',\n queue: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n topic: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n container: 'fill:#1a2a3a,stroke:#3a6a9a,color:#acd',\n pod: 'fill:#0f1f2f,stroke:#2a5a8a,color:#8bc',\n k8s_cluster: 'fill:#0a1520,stroke:#1a4a7a,color:#7ab',\n config_file: 'fill:#2a2a1a,stroke:#7a7a2a,color:#ddc',\n saas_tool: 'fill:#2a1a2a,stroke:#9a3a9a,color:#daf',\n unknown: 'fill:#2a2a2a,stroke:#5a5a5a,color:#aaa',\n};\n\n// ── Mermaid ──────────────────────────────────────────────────────────────────\n\nfunction sanitize(id: string): string {\n return id.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\nfunction nodeLabel(node: NodeRow): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const parts = node.id.split(':');\n const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? '';\n const conf = `${Math.round(node.confidence * 100)}%`;\n\n // Pull 1-2 key metadata fields (no credentials)\n const meta = node.metadata as Record<string, unknown>;\n const extras: string[] = [];\n for (const key of ['category', 'version', 'description']) {\n const v = meta[key];\n if (typeof v === 'string' && v.length > 0) {\n extras.push(v.substring(0, 28));\n break; // max 1 extra line for readability\n }\n }\n\n const locLine = location ? `<br/><small>${location}</small>` : '';\n const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : '';\n return `[\"${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} · ${conf}</small>\"]`;\n}\n\nexport function generateTopologyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n if (nodes.length === 0) return 'graph TB\\n empty[\"No nodes discovered yet\"]';\n\n const lines: string[] = ['graph TB'];\n\n // classDef per used type\n const usedTypes = new Set(nodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n // Group by semantic layer (ordered top→bottom)\n const layerMap = new Map<string, NodeRow[]>();\n for (const node of nodes) {\n const layer = nodeLayer(node.type);\n if (!layerMap.has(layer)) layerMap.set(layer, []);\n layerMap.get(layer)!.push(node);\n }\n\n for (const layerKey of LAYER_ORDER) {\n const layerNodes = layerMap.get(layerKey);\n if (!layerNodes || layerNodes.length === 0) continue;\n const label = LAYER_LABELS[layerKey] ?? layerKey;\n lines.push(` subgraph ${layerKey}[\"${label}\"]`);\n for (const node of layerNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push(' end');\n lines.push('');\n }\n\n // Edges: dashed for low-confidence (<0.6), solid otherwise\n for (const edge of edges) {\n const src = sanitize(edge.sourceId);\n const tgt = sanitize(edge.targetId);\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n const arrow = edge.confidence < 0.6 ? `-. \"${label}\" .->` : `-->|\"${label}\"|`;\n lines.push(` ${src} ${arrow} ${tgt}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateDependencyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n const depEdges = edges.filter(e =>\n ['calls', 'reads_from', 'writes_to', 'depends_on'].includes(e.relationship)\n );\n\n if (depEdges.length === 0) return 'graph LR\\n empty[\"No dependency edges found\"]';\n\n const lines: string[] = ['graph LR'];\n\n const usedIds = new Set<string>();\n for (const edge of depEdges) {\n usedIds.add(edge.sourceId);\n usedIds.add(edge.targetId);\n }\n\n const usedNodes = nodes.filter(n => usedIds.has(n.id));\n const usedTypes = new Set(usedNodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n for (const node of usedNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push('');\n\n for (const edge of depEdges) {\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n lines.push(` ${sanitize(edge.sourceId)} -->|\"${label}\"| ${sanitize(edge.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\n// ── Diff / Drift Mermaid ───────────────────────────────────────────────────────\n\nconst DIFF_CLASSES: Record<'added' | 'removed' | 'changed' | 'context', string> = {\n added: 'fill:#0d3d0d,stroke:#22c55e,color:#86efac',\n removed: 'fill:#3d0d0d,stroke:#ef4444,color:#fca5a5',\n changed: 'fill:#3d2f0d,stroke:#f59e0b,color:#fcd34d',\n context: 'fill:#1e1e1e,stroke:#555555,color:#999999',\n};\n\nfunction diffNodeLabel(node: NodeRow, suffix?: string): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const extra = suffix ? `<br/><small>Δ ${suffix}</small>` : '';\n return `[\"${icon} <b>${node.name}</b><br/><small>${node.type}</small>${extra}\"]`;\n}\n\n/**\n * Render a topology diff as a Mermaid graph: added nodes/edges in green, removed\n * in red, changed in amber. Endpoints of added/removed edges that are otherwise\n * unchanged are drawn as neutral \"context\" nodes so every edge has both ends.\n */\nexport function generateDiffMermaid(diff: TopologyDiff): string {\n const total =\n diff.summary.nodesAdded + diff.summary.nodesRemoved + diff.summary.nodesChanged +\n diff.summary.edgesAdded + diff.summary.edgesRemoved;\n if (total === 0) return 'graph TB\\n nodrift[\"✓ No drift between the two sessions\"]';\n\n const lines: string[] = ['graph TB'];\n for (const [k, style] of Object.entries(DIFF_CLASSES)) lines.push(` classDef ${k} ${style}`);\n lines.push('');\n\n type Cls = 'added' | 'removed' | 'changed' | 'context';\n const rank: Record<Cls, number> = { added: 3, removed: 3, changed: 3, context: 0 };\n const entries = new Map<string, { node: NodeRow; cls: Cls; suffix?: string }>();\n const place = (node: NodeRow, cls: Cls, suffix?: string) => {\n const prev = entries.get(node.id);\n if (prev && rank[prev.cls] >= rank[cls]) return;\n entries.set(node.id, { node, cls, suffix });\n };\n\n for (const n of diff.nodes.added) place(n, 'added');\n for (const n of diff.nodes.removed) place(n, 'removed');\n for (const c of diff.nodes.changed) place(c.after, 'changed', c.changedFields.join(', '));\n\n // Stub a neutral context node for an edge endpoint we have no full record for.\n const contextNode = (id: string): NodeRow => ({\n id, type: 'unknown', name: id, discoveredVia: 'diff',\n confidence: 1, metadata: {}, tags: [], sessionId: '', discoveredAt: '', depth: 0,\n });\n const ensureEndpoint = (id: string) => { if (!entries.has(id)) place(contextNode(id), 'context'); };\n for (const e of [...diff.edges.added, ...diff.edges.removed]) { ensureEndpoint(e.sourceId); ensureEndpoint(e.targetId); }\n\n for (const { node, cls, suffix } of entries.values()) {\n lines.push(` ${sanitize(node.id)}${diffNodeLabel(node, suffix)}:::${cls}`);\n }\n lines.push('');\n\n for (const e of diff.edges.added) {\n const label = EDGE_LABELS[e.relationship] ?? e.relationship;\n lines.push(` ${sanitize(e.sourceId)} ==>|\"+ ${label}\"| ${sanitize(e.targetId)}`);\n }\n for (const e of diff.edges.removed) {\n const label = EDGE_LABELS[e.relationship] ?? e.relationship;\n lines.push(` ${sanitize(e.sourceId)} -.->|\"- ${label}\"| ${sanitize(e.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\n// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n const owner = org ?? 'unknown';\n const docs: string[] = [];\n\n for (const node of nodes) {\n const isComponent = ['web_service', 'container', 'pod'].includes(node.type);\n const isAPI = node.type === 'api_endpoint';\n const kind = isComponent ? 'Component' : isAPI ? 'API' : 'Resource';\n\n const deps = edges\n .filter(e => e.sourceId === node.id)\n .map(e => ` - resource:default/${sanitize(e.targetId)}`);\n\n const doc = [\n `apiVersion: backstage.io/v1alpha1`,\n `kind: ${kind}`,\n `metadata:`,\n ` name: ${sanitize(node.id)}`,\n ` annotations:`,\n ` cartography/discovered-at: \"${node.discoveredAt}\"`,\n ` cartography/confidence: \"${node.confidence}\"`,\n `spec:`,\n ` type: ${node.type}`,\n ` lifecycle: production`,\n ` owner: ${node.owner ?? owner}`,\n ...(deps.length > 0 ? [' dependsOn:', ...deps] : []),\n ].join('\\n');\n\n docs.push(doc);\n }\n\n return docs.join('\\n---\\n');\n}\n\n// ── JSON ─────────────────────────────────────────────────────────────────────\n\nexport function exportJSON(db: CartographyDB, sessionId: string): string {\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n const events = db.getEvents(sessionId);\n const tasks = db.getTasks(sessionId);\n const stats = db.getStats(sessionId);\n\n return JSON.stringify({\n sessionId,\n exportedAt: new Date().toISOString(),\n stats,\n nodes,\n edges,\n events,\n tasks,\n }, null, 2);\n}\n\n// ── HTML (D3.js Hexagonal Cartography Map) ────────────────────────────────────\n\nexport function exportHTML(nodes: NodeRow[], edges: EdgeRow[]): string {\n const graphData = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id,\n name: n.name,\n type: n.type,\n layer: nodeLayer(n.type),\n confidence: n.confidence,\n discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt,\n tags: n.tags,\n metadata: n.metadata,\n })),\n links: edges.map(e => ({\n source: e.sourceId,\n target: e.targetId,\n relationship: e.relationship,\n confidence: e.confidence,\n evidence: e.evidence,\n })),\n });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — Infrastructure Map</title>\n <script src=\"https://d3js.org/d3.v7.min.js\"></script>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { background: #0a0e14; color: #e6edf3; font-family: 'SF Mono','Fira Code','Cascadia Code',monospace; display: flex; overflow: hidden; height: 100vh; }\n\n /* ── Left node panel ─────────────────────────────── */\n #node-panel {\n width: 220px; min-width: 220px; height: 100vh; overflow: hidden;\n background: #0d1117; border-right: 1px solid #1b2028;\n display: flex; flex-direction: column;\n }\n #node-panel-header {\n padding: 10px 12px 8px; border-bottom: 1px solid #1b2028;\n font-size: 11px; color: #6e7681; text-transform: uppercase; letter-spacing: 0.6px;\n }\n #node-search {\n width: calc(100% - 16px); margin: 8px; padding: 5px 8px;\n background: #161b22; border: 1px solid #30363d; border-radius: 5px;\n color: #e6edf3; font-size: 11px; font-family: inherit; outline: none;\n }\n #node-search:focus { border-color: #58a6ff; }\n #node-list { flex: 1; overflow-y: auto; padding-bottom: 8px; }\n .node-list-item {\n padding: 5px 12px; cursor: pointer; font-size: 11px;\n display: flex; align-items: center; gap: 6px; border-left: 2px solid transparent;\n }\n .node-list-item:hover { background: #161b22; }\n .node-list-item.active { background: #1a2436; border-left-color: #58a6ff; }\n .node-list-dot { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }\n .node-list-name { color: #c9d1d9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }\n .node-list-type { color: #484f58; font-size: 9px; flex-shrink: 0; }\n\n /* ── Center graph ────────────────────────────────── */\n #graph { flex: 1; height: 100vh; position: relative; }\n svg { width: 100%; height: 100%; }\n .hull { opacity: 0.12; stroke-width: 1.5; stroke-opacity: 0.25; }\n .hull-label { font-size: 13px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; fill-opacity: 0.5; pointer-events: none; }\n .link { stroke-opacity: 0.4; }\n .link-label { font-size: 8px; fill: #6e7681; pointer-events: none; opacity: 0; }\n .node-hex { stroke-width: 1.8; cursor: pointer; transition: opacity 0.15s; }\n .node-hex:hover { filter: brightness(1.3); stroke-width: 3; }\n .node-hex.selected { stroke-width: 3.5; filter: brightness(1.5); }\n .node-label { font-size: 10px; fill: #c9d1d9; pointer-events: none; opacity: 0; }\n\n /* ── Right sidebar ───────────────────────────────── */\n #sidebar {\n width: 300px; min-width: 300px; height: 100vh; overflow-y: auto;\n background: #0d1117; border-left: 1px solid #1b2028;\n padding: 16px; font-size: 12px; line-height: 1.6;\n }\n #sidebar h2 { margin: 0 0 8px; font-size: 14px; color: #58a6ff; }\n #sidebar .meta-table { width: 100%; border-collapse: collapse; }\n #sidebar .meta-table td { padding: 3px 6px; border-bottom: 1px solid #161b22; vertical-align: top; }\n #sidebar .meta-table td:first-child { color: #6e7681; white-space: nowrap; width: 90px; }\n #sidebar .tag { display: inline-block; background: #161b22; border-radius: 3px; padding: 1px 5px; margin: 1px; font-size: 10px; }\n #sidebar .conf-bar { height: 5px; border-radius: 3px; background: #161b22; margin-top: 3px; }\n #sidebar .conf-fill { height: 100%; border-radius: 3px; }\n #sidebar .edges-list { margin-top: 12px; }\n #sidebar .edge-item { padding: 4px 0; border-bottom: 1px solid #161b22; color: #6e7681; font-size: 11px; }\n #sidebar .edge-item span { color: #c9d1d9; }\n #sidebar .action-row { display: flex; gap: 6px; margin-top: 14px; }\n .btn-delete {\n flex: 1; padding: 6px 10px; background: transparent; border: 1px solid #6e191d;\n color: #f85149; border-radius: 5px; font-size: 11px; font-family: inherit;\n cursor: pointer; text-align: center;\n }\n .btn-delete:hover { background: #3d0c0c; }\n .hint { color: #3d434b; font-size: 11px; margin-top: 8px; }\n\n /* ── HUD ─────────────────────────────────────────── */\n #hud { position: absolute; top: 10px; left: 10px; background: rgba(10,14,20,0.88);\n padding: 10px 14px; border-radius: 8px; font-size: 12px; border: 1px solid #1b2028; pointer-events: none; }\n #hud strong { color: #58a6ff; }\n #hud .stats { color: #6e7681; }\n #hud .zoom-level { color: #3d434b; font-size: 10px; margin-top: 2px; }\n\n /* ── Toolbar (filters + JGF export) ─────────────── */\n #toolbar { position: absolute; top: 10px; right: 10px; display: flex; flex-wrap: wrap; gap: 4px; pointer-events: auto; align-items: center; }\n .filter-btn {\n background: rgba(10,14,20,0.85); border: 1px solid #1b2028; border-radius: 6px;\n color: #c9d1d9; padding: 4px 10px; font-size: 11px; cursor: pointer;\n font-family: inherit; display: flex; align-items: center; gap: 5px;\n }\n .filter-btn:hover { border-color: #30363d; }\n .filter-btn.off { opacity: 0.35; }\n .filter-dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }\n .export-btn {\n background: rgba(10,14,20,0.85); border: 1px solid #1b2028; border-radius: 6px;\n color: #58a6ff; padding: 4px 12px; font-size: 11px; cursor: pointer;\n font-family: inherit;\n }\n .export-btn:hover { border-color: #58a6ff; background: rgba(88,166,255,0.08); }\n </style>\n</head>\n<body>\n\n<!-- Left: node list panel -->\n<div id=\"node-panel\">\n <div id=\"node-panel-header\">Nodes (${nodes.length})</div>\n <input id=\"node-search\" type=\"text\" placeholder=\"Search nodes…\" autocomplete=\"off\" spellcheck=\"false\">\n <div id=\"node-list\"></div>\n</div>\n\n<!-- Center: graph -->\n<div id=\"graph\">\n <div id=\"hud\">\n <strong>Cartography</strong> &nbsp;\n <span class=\"stats\" id=\"hud-stats\">${nodes.length} nodes · ${edges.length} edges</span><br>\n <span class=\"zoom-level\">Scroll = zoom · Drag = pan · Click = details</span>\n </div>\n <div id=\"toolbar\"></div>\n <svg></svg>\n</div>\n\n<!-- Right: detail sidebar -->\n<div id=\"sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Click a node to view details.</p>\n</div>\n\n<script>\nconst data = ${graphData};\n\n// ── Color palette per node type ───────────────────────────────────────────\nconst TYPE_COLORS = {\n host: '#4a9eff', database_server: '#ff6b6b', database: '#ff8c42',\n web_service: '#6bcb77', api_endpoint: '#4d96ff', cache_server: '#ffd93d',\n message_broker: '#c77dff', queue: '#e0aaff', topic: '#9d4edd',\n container: '#48cae4', pod: '#00b4d8', k8s_cluster: '#0077b6',\n config_file: '#adb5bd', saas_tool: '#c084fc', table: '#f97316', unknown: '#6c757d',\n};\n\nconst LAYER_COLORS = {\n saas: '#c084fc', web: '#6bcb77', data: '#ff6b6b',\n messaging: '#c77dff', infra: '#4a9eff', config: '#adb5bd', other: '#6c757d',\n};\nconst LAYER_NAMES = {\n saas: 'SaaS Tools', web: 'Web / API', data: 'Data Layer',\n messaging: 'Messaging', infra: 'Infrastructure', config: 'Config', other: 'Other',\n};\n\n// ── Hexagon path ──────────────────────────────────────────────────────────\nconst HEX_SIZE = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\nfunction hexSize(d) { return HEX_SIZE[d.type] || HEX_SIZE.default; }\nfunction hexPath(size) {\n const pts = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(p => p.join(',')).join('L') + 'Z';\n}\n\n// ── Left panel ────────────────────────────────────────────────────────────\nconst nodeListEl = document.getElementById('node-list');\nconst nodeSearchEl = document.getElementById('node-search');\nlet selectedNodeId = null;\n\nfunction buildNodeList(filter) {\n const q = (filter || '').toLowerCase();\n nodeListEl.innerHTML = '';\n const sorted = [...data.nodes].sort((a, b) => a.name.localeCompare(b.name));\n for (const d of sorted) {\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n const item = document.createElement('div');\n item.className = 'node-list-item' + (d.id === selectedNodeId ? ' active' : '');\n item.dataset.id = d.id;\n const color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = \\`<span class=\"node-list-dot\" style=\"background:\\${color}\"></span>\n <span class=\"node-list-name\" title=\"\\${d.id}\">\\${d.name}</span>\n <span class=\"node-list-type\">\\${d.type.replace(/_/g,' ')}</span>\\`;\n item.onclick = () => { selectNode(d); focusNode(d); };\n nodeListEl.appendChild(item);\n }\n}\n\nnodeSearchEl.addEventListener('input', e => buildNodeList(e.target.value));\n\n// ── Sidebar detail view ───────────────────────────────────────────────────\nconst sidebar = document.getElementById('sidebar');\n\nfunction selectNode(d) {\n selectedNodeId = d.id;\n buildNodeList(nodeSearchEl.value);\n showNode(d);\n // highlight hex\n d3.selectAll('.node-hex').classed('selected', nd => nd.id === d.id);\n}\n\nfunction showNode(d) {\n const c = TYPE_COLORS[d.type] || '#aaa';\n const confPct = Math.round(d.confidence * 100);\n const tags = (d.tags || []).map(t => \\`<span class=\"tag\">\\${t}</span>\\`).join('');\n const metaRows = Object.entries(d.metadata || {})\n .filter(([,v]) => v !== null && v !== undefined && String(v).length > 0)\n .map(([k,v]) => \\`<tr><td>\\${k}</td><td>\\${JSON.stringify(v)}</td></tr>\\`)\n .join('');\n const related = data.links.filter(l =>\n (l.source.id||l.source) === d.id || (l.target.id||l.target) === d.id\n );\n const edgeItems = related.map(l => {\n const isOut = (l.source.id||l.source) === d.id;\n const other = isOut ? (l.target.id||l.target) : (l.source.id||l.source);\n return \\`<div class=\"edge-item\">\\${isOut ? '→' : '←'} <span>\\${other}</span> <small>[\\${l.relationship}]</small></div>\\`;\n }).join('');\n\n sidebar.innerHTML = \\`\n <h2>\\${d.name}</h2>\n <table class=\"meta-table\">\n <tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">\\${d.id}</td></tr>\n <tr><td>Type</td><td><span style=\"color:\\${c}\">\\${d.type}</span></td></tr>\n <tr><td>Layer</td><td>\\${d.layer}</td></tr>\n <tr><td>Confidence</td><td>\n \\${confPct}%\n <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:\\${confPct}%;background:\\${c}\"></div></div>\n </td></tr>\n <tr><td>Discovered via</td><td>\\${d.discoveredVia || '—'}</td></tr>\n <tr><td>Timestamp</td><td>\\${d.discoveredAt ? d.discoveredAt.substring(0,19).replace('T',' ') : '—'}</td></tr>\n \\${tags ? '<tr><td>Tags</td><td>'+tags+'</td></tr>' : ''}\n \\${metaRows}\n </table>\n \\${related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>'+edgeItems+'</div>' : ''}\n <div class=\"action-row\">\n <button class=\"btn-delete\" onclick=\"deleteNode('\\${d.id}')\">🗑 Delete node</button>\n </div>\n \\`;\n}\n\n// ── Delete node ───────────────────────────────────────────────────────────\nfunction deleteNode(id) {\n const idx = data.nodes.findIndex(n => n.id === id);\n if (idx === -1) return;\n data.nodes.splice(idx, 1);\n data.links = data.links.filter(l =>\n (l.source.id || l.source) !== id && (l.target.id || l.target) !== id\n );\n selectedNodeId = null;\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n document.getElementById('hud-stats').textContent =\n data.nodes.length + ' nodes · ' + data.links.length + ' edges';\n rebuildGraph();\n buildNodeList(nodeSearchEl.value);\n}\n\n// ── SVG setup ─────────────────────────────────────────────────────────────\nconst svgEl = d3.select('svg');\nconst graphDiv = document.getElementById('graph');\nconst W = () => graphDiv.clientWidth;\nconst H = () => graphDiv.clientHeight;\nconst g = svgEl.append('g');\n\nsvgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\nlet currentZoom = 1;\nconst zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', e => {\n g.attr('transform', e.transform);\n currentZoom = e.transform.k;\n updateLOD(currentZoom);\n});\nsvgEl.call(zoomBehavior);\n\n// ── Layer filter state ────────────────────────────────────────────────────\nconst layers = [...new Set(data.nodes.map(d => d.layer))];\nconst layerVisible = {};\nlayers.forEach(l => layerVisible[l] = true);\n\nconst toolbarEl = document.getElementById('toolbar');\n\n// Filter buttons\nlayers.forEach(layer => {\n const btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = \\`<span class=\"filter-dot\" style=\"background:\\${LAYER_COLORS[layer]||'#666'}\"></span>\\${LAYER_NAMES[layer]||layer}\\`;\n btn.onclick = () => {\n layerVisible[layer] = !layerVisible[layer];\n btn.classList.toggle('off', !layerVisible[layer]);\n updateVisibility();\n };\n toolbarEl.appendChild(btn);\n});\n\n// JGF export button\nconst jgfBtn = document.createElement('button');\njgfBtn.className = 'export-btn';\njgfBtn.textContent = '↓ JGF';\njgfBtn.title = 'Export JSON Graph Format';\njgfBtn.onclick = exportJGF;\ntoolbarEl.appendChild(jgfBtn);\n\n// ── JGF export ────────────────────────────────────────────────────────────\nfunction exportJGF() {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(data.nodes.map(n => [n.id, {\n label: n.name,\n metadata: { type: n.type, layer: n.layer, confidence: n.confidence,\n discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt,\n tags: n.tags, ...n.metadata }\n }])),\n edges: data.links.map(l => ({\n source: l.source.id || l.source,\n target: l.target.id || l.target,\n relation: l.relationship,\n metadata: { confidence: l.confidence, evidence: l.evidence }\n })),\n }\n };\n const blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n}\n\n// ── Cluster force ─────────────────────────────────────────────────────────\nfunction clusterForce(alpha) {\n const centroids = {};\n const counts = {};\n data.nodes.forEach(d => {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0;\n centroids[d.layer].y += d.y || 0;\n counts[d.layer]++;\n });\n for (const l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n const strength = alpha * 0.15;\n data.nodes.forEach(d => {\n const c = centroids[d.layer];\n if (c) { d.vx += (c.x - d.x) * strength; d.vy += (c.y - d.y) * strength; }\n });\n}\n\n// ── Hull group ────────────────────────────────────────────────────────────\nconst hullGroup = g.append('g').attr('class', 'hulls');\nconst hullPaths = {};\nconst hullLabels = {};\nlayers.forEach(layer => {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n});\n\nfunction updateHulls() {\n layers.forEach(layer => {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n const pts = data.nodes.filter(d => d.layer === layer && layerVisible[d.layer]).map(d => [d.x, d.y]);\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n const hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n const cx = d3.mean(hull, p => p[0]);\n const cy = d3.mean(hull, p => p[1]);\n const padded = hull.map(p => {\n const dx = p[0] - cx, dy = p[1] - cy;\n const len = Math.sqrt(dx*dx + dy*dy) || 1;\n return [p[0] + dx/len * 40, p[1] + dy/len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, p => Math.abs(p[1] - cy)) - 30);\n });\n}\n\n// ── Graph rendering (rebuildable after delete) ────────────────────────────\nlet linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\nconst linkGroup = g.append('g');\nconst nodeGroup = g.append('g');\n\nfunction focusNode(d) {\n if (!d.x || !d.y) return;\n const w = W(), h = H();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n}\n\nfunction rebuildGraph() {\n if (sim) sim.stop();\n\n // Links\n linkSel = linkGroup.selectAll('line').data(data.links, d => \\`\\${d.source.id||d.source}>\\${d.target.id||d.target}\\`);\n linkSel.exit().remove();\n const linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', d => d.confidence < 0.6 ? '#2a2e35' : '#3d434b')\n .attr('stroke-dasharray', d => d.confidence < 0.6 ? '4 3' : null)\n .attr('stroke-width', d => d.confidence < 0.6 ? 0.8 : 1.2)\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(d => \\`\\${d.relationship} (\\${Math.round(d.confidence*100)}%)\\n\\${d.evidence||''}\\`);\n\n // Link labels\n linkLabelSel = linkGroup.selectAll('text').data(data.links, d => \\`\\${d.source.id||d.source}>\\${d.target.id||d.target}\\`);\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel)\n .text(d => d.relationship);\n\n // Nodes\n nodeSel = nodeGroup.selectAll('g').data(data.nodes, d => d.id);\n nodeSel.exit().remove();\n const nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })\n .on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', (e, d) => { e.stopPropagation(); selectNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', d => hexPath(hexSize(d)))\n .attr('fill', d => TYPE_COLORS[d.type] || '#aaa')\n .attr('stroke', d => { const c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', d => 0.6 + d.confidence * 0.4)\n .classed('selected', d => d.id === selectedNodeId);\n nodeSel.select('title').text(d => \\`\\${d.name} (\\${d.type})\\nconf: \\${Math.round(d.confidence*100)}%\\`);\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', d => hexSize(d) + 13)\n .text(d => d.name.length > 20 ? d.name.substring(0, 18) + '…' : d.name);\n\n // Simulation\n sim = d3.forceSimulation(data.nodes)\n .force('link', d3.forceLink(data.links).id(d => d.id).distance(d => d.relationship === 'contains' ? 50 : 100).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(W() / 2, H() / 2))\n .force('collision', d3.forceCollide().radius(d => hexSize(d) + 10))\n .force('cluster', clusterForce)\n .on('tick', () => {\n updateHulls();\n linkSel.attr('x1', d => d.source.x).attr('y1', d => d.source.y)\n .attr('x2', d => d.target.x).attr('y2', d => d.target.y);\n linkLabelSel.attr('x', d => (d.source.x + d.target.x) / 2)\n .attr('y', d => (d.source.y + d.target.y) / 2 - 4);\n nodeSel.attr('transform', d => \\`translate(\\${d.x},\\${d.y})\\`);\n });\n}\n\n// ── LOD & visibility ──────────────────────────────────────────────────────\nfunction updateLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n}\n\nfunction updateVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', d => layerVisible[d.layer] ? null : 'none');\n linkSel.style('display', d => {\n const s = data.nodes.find(n => n.id === (d.source.id||d.source));\n const t = data.nodes.find(n => n.id === (d.target.id||d.target));\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', d => {\n const s = data.nodes.find(n => n.id === (d.source.id||d.source));\n const t = data.nodes.find(n => n.id === (d.target.id||d.target));\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n}\n\n// ── Init ──────────────────────────────────────────────────────────────────\nrebuildGraph();\nbuildNodeList();\nupdateLOD(1);\n\nsvgEl.on('click', () => {\n selectedNodeId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildNodeList(nodeSearchEl.value);\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n});\n</script>\n</body>\n</html>`;\n}\n\n// ── Cartography Map Export ─────────────────────────────────────────────────────\n\nexport function exportCartographyMap(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): string {\n const mapData = buildMapData(nodes, edges, options);\n const { assets, clusters, connections, meta } = mapData;\n const isEmpty = assets.length === 0;\n const HEX_SIZE = 24;\n\n const dataJson = JSON.stringify({\n assets: assets.map(a => ({\n id: a.id, name: a.name, domain: a.domain, subDomain: a.subDomain ?? null,\n qualityScore: a.qualityScore ?? null, metadata: a.metadata,\n q: a.position.q, r: a.position.r,\n })),\n clusters: clusters.map(c => ({\n id: c.id, label: c.label, domain: c.domain, color: c.color,\n assetIds: c.assetIds, centroid: c.centroid,\n })),\n connections: connections.map(c => ({\n id: c.id, sourceAssetId: c.sourceAssetId, targetAssetId: c.targetAssetId,\n type: c.type ?? 'connection',\n })),\n });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n<title>Data Cartography Map</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\nbody{display:flex;flex-direction:column;background:${meta.theme === 'dark' ? '#0f172a' : '#f8fafc'};color:${meta.theme === 'dark' ? '#e2e8f0' : '#1e293b'}}\n#topbar{\n height:48px;display:flex;align-items:center;gap:16px;padding:0 20px;\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};border-bottom:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};z-index:10;flex-shrink:0;\n}\n#topbar h1{font-size:15px;font-weight:600;letter-spacing:-0.01em}\n#search-box{\n display:flex;align-items:center;gap:8px;background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'};\n border-radius:8px;padding:5px 10px;margin-left:auto;\n}\n#search-box input{\n border:none;background:transparent;font-size:13px;outline:none;width:180px;color:inherit;\n}\n#search-box input::placeholder{color:#94a3b8}\n#main{flex:1;display:flex;overflow:hidden;position:relative}\n#canvas-wrap{flex:1;position:relative;overflow:hidden;cursor:grab}\n#canvas-wrap.dragging{cursor:grabbing}\n#canvas-wrap.connecting{cursor:crosshair}\ncanvas{display:block;width:100%;height:100%}\n/* Detail panel */\n#detail-panel{\n width:280px;background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};border-left:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n display:flex;flex-direction:column;transform:translateX(100%);\n transition:transform .2s ease;z-index:5;flex-shrink:0;overflow-y:auto;\n}\n#detail-panel.open{transform:translateX(0)}\n#detail-panel .panel-header{\n padding:16px;border-bottom:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};display:flex;align-items:center;gap:10px;\n}\n#detail-panel .panel-header h3{font-size:14px;font-weight:600;flex:1;word-break:break-word}\n#detail-panel .close-btn{\n width:24px;height:24px;border:none;background:transparent;cursor:pointer;\n color:#94a3b8;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;\n}\n#detail-panel .close-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n#detail-panel .panel-body{padding:12px 16px;display:flex;flex-direction:column;gap:12px}\n#detail-panel .meta-row{display:flex;flex-direction:column;gap:3px}\n#detail-panel .meta-label{font-size:11px;font-weight:500;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em}\n#detail-panel .meta-value{font-size:13px;word-break:break-all}\n#detail-panel .quality-bar{height:6px;border-radius:3px;background:${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};margin-top:4px}\n#detail-panel .quality-fill{height:6px;border-radius:3px;transition:width .3s}\n/* Bottom-left toolbar */\n#toolbar-left{\n position:absolute;bottom:20px;left:20px;display:flex;gap:8px;z-index:10;\n}\n.tb-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};box-shadow:0 1px 4px rgba(0,0,0,.08);cursor:pointer;\n display:flex;align-items:center;justify-content:center;font-size:18px;\n transition:all .15s;color:inherit;\n}\n.tb-btn:hover{border-color:#94a3b8}\n.tb-btn.active{background:${meta.theme === 'dark' ? '#1e3a5f' : '#eff6ff'};border-color:#3b82f6}\n/* Bottom-right toolbar */\n#toolbar-right{\n position:absolute;bottom:20px;right:20px;display:flex;flex-direction:column;\n align-items:flex-end;gap:8px;z-index:10;\n}\n#zoom-controls{display:flex;align-items:center;gap:6px}\n.zoom-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;color:inherit;display:flex;align-items:center;justify-content:center;\n}\n.zoom-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n#zoom-pct{font-size:12px;font-weight:500;color:#64748b;min-width:38px;text-align:center}\n#detail-selector{display:flex;flex-direction:column;gap:4px}\n.detail-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:12px;font-weight:600;color:#64748b;display:flex;align-items:center;justify-content:center;\n}\n.detail-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n.detail-btn.active{background:${meta.theme === 'dark' ? '#1e3a5f' : '#eff6ff'};border-color:#3b82f6;color:#2563eb}\n#connect-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;display:flex;align-items:center;justify-content:center;color:inherit;\n}\n#connect-btn.active{background:#fef3c7;border-color:#f59e0b}\n/* Tooltip */\n#tooltip{\n position:fixed;background:#1e293b;color:#fff;border-radius:8px;\n padding:8px 12px;font-size:12px;pointer-events:none;z-index:100;\n display:none;max-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.15);\n}\n#tooltip .tt-name{font-weight:600;margin-bottom:2px}\n#tooltip .tt-domain{color:#94a3b8;font-size:11px}\n#tooltip .tt-quality{font-size:11px;margin-top:2px}\n/* Empty state */\n#empty-state{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:12px;color:#94a3b8;\n}\n#empty-state p{font-size:14px}\n/* Theme toggle */\n#theme-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;display:flex;align-items:center;justify-content:center;color:inherit;\n}\n/* Dark mode overrides (toggled via JS) */\nbody.dark{background:#0f172a;color:#e2e8f0}\nbody.dark #topbar{background:#1e293b;border-color:#334155}\nbody.dark #search-box{background:#334155}\nbody.dark #detail-panel{background:#1e293b;border-color:#334155}\nbody.dark .tb-btn,body.dark .zoom-btn,body.dark .detail-btn,body.dark #connect-btn,body.dark #theme-btn{\n background:#1e293b;border-color:#334155;color:#e2e8f0;\n}\n/* Light mode overrides */\nbody.light{background:#f8fafc;color:#1e293b}\nbody.light #topbar{background:#fff;border-color:#e2e8f0}\n/* Connection hint */\n#connect-hint{\n position:absolute;top:12px;left:50%;transform:translateX(-50%);\n background:#fef3c7;border:1px solid #f59e0b;color:#92400e;\n padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500;\n display:none;z-index:20;pointer-events:none;\n}\n/* Screen reader only */\n.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}\n</style>\n</head>\n<body class=\"${meta.theme}\">\n<!-- Top bar -->\n<div id=\"topbar\">\n <h1>Data Cartography Map</h1>\n <div id=\"search-box\">\n <span style=\"color:#94a3b8;font-size:14px\">&#8981;</span>\n <input id=\"search-input\" type=\"text\" placeholder=\"Search assets...\" aria-label=\"Search data assets\"/>\n </div>\n <button id=\"theme-btn\" title=\"Toggle dark/light mode\" aria-label=\"Toggle theme\">${meta.theme === 'dark' ? '&#9788;' : '&#9790;'}</button>\n</div>\n<!-- SR summary -->\n<div class=\"sr-only\" role=\"status\" aria-live=\"polite\" id=\"sr-summary\">\n Data cartography map with ${assets.length} assets in ${clusters.length} clusters.\n</div>\n<!-- Main area -->\n<div id=\"main\">\n <div id=\"canvas-wrap\" role=\"application\" aria-label=\"Data cartography hex map\" tabindex=\"0\">\n <canvas id=\"hexmap\" aria-hidden=\"true\"></canvas>\n ${isEmpty ? '<div id=\"empty-state\"><p style=\"font-size:48px\">&#128506;</p><p>No data assets available</p><p style=\"font-size:12px\">Run <code>datasynx-cartography discover</code> to populate the map</p></div>' : ''}\n </div>\n <div id=\"detail-panel\" role=\"complementary\" aria-label=\"Asset details\">\n <div class=\"panel-header\">\n <h3 id=\"dp-name\">&mdash;</h3>\n <button class=\"close-btn\" id=\"dp-close\" aria-label=\"Close panel\">&#10005;</button>\n </div>\n <div class=\"panel-body\" id=\"dp-body\"></div>\n </div>\n</div>\n<!-- Bottom-left toolbar -->\n<div id=\"toolbar-left\">\n <button class=\"tb-btn active\" id=\"btn-labels\" title=\"Show labels\" aria-pressed=\"true\" aria-label=\"Toggle labels\">&#127991;</button>\n <button class=\"tb-btn\" id=\"btn-quality\" title=\"Quality layer\" aria-pressed=\"false\" aria-label=\"Toggle quality layer\">&#128065;</button>\n</div>\n<!-- Bottom-right toolbar -->\n<div id=\"toolbar-right\">\n <div id=\"zoom-controls\">\n <button class=\"zoom-btn\" id=\"zoom-out\" aria-label=\"Zoom out\">&minus;</button>\n <span id=\"zoom-pct\">100%</span>\n <button class=\"zoom-btn\" id=\"zoom-in\" aria-label=\"Zoom in\">+</button>\n </div>\n <div id=\"detail-selector\">\n <button class=\"detail-btn\" id=\"dl-1\" aria-label=\"Detail level 1\">1</button>\n <button class=\"detail-btn active\" id=\"dl-2\" aria-label=\"Detail level 2\">2</button>\n <button class=\"detail-btn\" id=\"dl-3\" aria-label=\"Detail level 3\">3</button>\n <button class=\"detail-btn\" id=\"dl-4\" aria-label=\"Detail level 4\">4</button>\n </div>\n <button id=\"connect-btn\" title=\"Connection tool\" aria-label=\"Toggle connection tool\">&#128279;</button>\n</div>\n<!-- Connection hint -->\n<div id=\"connect-hint\">Click two assets to create a connection</div>\n<!-- Tooltip -->\n<div id=\"tooltip\" role=\"tooltip\">\n <div class=\"tt-name\" id=\"tt-name\"></div>\n <div class=\"tt-domain\" id=\"tt-domain\"></div>\n <div class=\"tt-quality\" id=\"tt-quality\"></div>\n</div>\n\n<script>\n(function() {\n'use strict';\n\n// ── Data ─────────────────────────────────────────────────────────────────────\nconst MAP = ${dataJson};\nconst HEX_SIZE = ${HEX_SIZE};\nconst IS_EMPTY = ${isEmpty};\n\n// Build asset index\nconst assetIndex = new Map();\nconst clusterByAsset = new Map();\nfor (const c of MAP.clusters) {\n for (const aid of c.assetIds) {\n clusterByAsset.set(aid, c);\n }\n}\nfor (const a of MAP.assets) {\n assetIndex.set(a.id, a);\n}\n\n// ── Canvas ────────────────────────────────────────────────────────────────────\nconst canvas = document.getElementById('hexmap');\nconst ctx = canvas.getContext('2d');\nconst wrap = document.getElementById('canvas-wrap');\nlet W = 0, H = 0;\n\nfunction resize() {\n const dpr = window.devicePixelRatio || 1;\n W = wrap.clientWidth; H = wrap.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n draw();\n}\nwindow.addEventListener('resize', resize);\n\n// ── Viewport ──────────────────────────────────────────────────────────────────\nlet vx = 0, vy = 0, scale = 1;\nlet detailLevel = 2, showLabels = true, showQuality = false;\nlet isDark = document.body.classList.contains('dark');\nlet connectMode = false, connectFirst = null;\nlet hoveredAssetId = null, selectedAssetId = null;\nlet searchQuery = '';\nlet localConnections = [...MAP.connections];\n\n// Flat-top hex math\nfunction htp_x(q, r) { return HEX_SIZE * (3/2 * q); }\nfunction htp_y(q, r) { return HEX_SIZE * (Math.sqrt(3)/2 * q + Math.sqrt(3) * r); }\nfunction w2s(wx, wy) { return { x: wx*scale+vx, y: wy*scale+vy }; }\nfunction s2w(sx, sy) { return { x: (sx-vx)/scale, y: (sy-vy)/scale }; }\n\nfunction fitToView() {\n if (IS_EMPTY || MAP.assets.length === 0) { vx = 0; vy = 0; scale = 1; return; }\n let mnx=Infinity,mny=Infinity,mxx=-Infinity,mxy=-Infinity;\n for (const a of MAP.assets) {\n const px=htp_x(a.q,a.r), py=htp_y(a.q,a.r);\n if(px<mnx)mnx=px;if(py<mny)mny=py;if(px>mxx)mxx=px;if(py>mxy)mxy=py;\n }\n const pw=mxx-mnx+HEX_SIZE*4, ph=mxy-mny+HEX_SIZE*4;\n scale = Math.min(W/pw, H/ph, 2) * 0.85;\n vx = W/2 - ((mnx+mxx)/2)*scale;\n vy = H/2 - ((mny+mxy)/2)*scale;\n}\n\n// ── Drawing ───────────────────────────────────────────────────────────────────\nfunction hexPath(cx, cy, r) {\n ctx.beginPath();\n for (let i=0;i<6;i++) {\n const angle = Math.PI/180*(60*i);\n const x=cx+r*Math.cos(angle), y=cy+r*Math.sin(angle);\n i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);\n }\n ctx.closePath();\n}\n\nfunction shadeV(hex, amt) {\n if(!hex||hex.length<7)return hex;\n const n=parseInt(hex.replace('#',''),16);\n const r=Math.min(255,(n>>16)+amt), g=Math.min(255,((n>>8)&0xff)+amt), b=Math.min(255,(n&0xff)+amt);\n return '#'+r.toString(16).padStart(2,'0')+g.toString(16).padStart(2,'0')+b.toString(16).padStart(2,'0');\n}\n\nfunction draw() {\n ctx.clearRect(0,0,W,H);\n ctx.fillStyle = isDark ? '#0f172a' : '#f8fafc';\n ctx.fillRect(0,0,W,H);\n if (IS_EMPTY) return;\n\n const size = HEX_SIZE * scale;\n const matchedIds = getSearchMatches();\n const hasSearch = searchQuery.length > 0;\n\n // Draw connections\n ctx.save();\n ctx.strokeStyle = isDark ? 'rgba(148,163,184,0.35)' : 'rgba(100,116,139,0.25)';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4,4]);\n for (const conn of localConnections) {\n const src = assetIndex.get(conn.sourceAssetId);\n const tgt = assetIndex.get(conn.targetAssetId);\n if (!src||!tgt) continue;\n const sp=w2s(htp_x(src.q,src.r),htp_y(src.q,src.r));\n const tp=w2s(htp_x(tgt.q,tgt.r),htp_y(tgt.q,tgt.r));\n ctx.beginPath();ctx.moveTo(sp.x,sp.y);ctx.lineTo(tp.x,tp.y);ctx.stroke();\n }\n ctx.setLineDash([]);\n ctx.restore();\n\n // Draw hexagons per cluster\n for (const cluster of MAP.clusters) {\n const baseColor = cluster.color;\n const clusterAssets = cluster.assetIds.map(id=>assetIndex.get(id)).filter(Boolean);\n const isClusterMatch = !hasSearch || clusterAssets.some(a => matchedIds.has(a.id));\n const clusterDim = hasSearch && !isClusterMatch;\n\n for (let ai=0; ai<clusterAssets.length; ai++) {\n const asset = clusterAssets[ai];\n const wx=htp_x(asset.q,asset.r), wy=htp_y(asset.q,asset.r);\n const s=w2s(wx,wy);\n const cx=s.x, cy=s.y;\n\n // Frustum cull\n if(cx+size<0||cx-size>W||cy+size<0||cy-size>H) continue;\n\n // Shade variation\n const shade = ai%3===0?18:ai%3===1?8:0;\n let fillColor = shadeV(baseColor, shade);\n\n // Quality overlay\n if (showQuality && asset.qualityScore !== null && asset.qualityScore !== undefined) {\n const q = asset.qualityScore;\n if (q < 40) fillColor = '#ef4444';\n else if (q < 70) fillColor = '#f97316';\n }\n\n const alpha = clusterDim ? 0.18 : 1;\n const isHovered = asset.id === hoveredAssetId;\n const isSelected = asset.id === selectedAssetId;\n const isConnectFirst = asset.id === connectFirst;\n\n ctx.save();\n ctx.globalAlpha = alpha;\n hexPath(cx, cy, size*0.92);\n\n if (isDark && (isHovered||isSelected||isConnectFirst)) {\n ctx.shadowColor = fillColor;\n ctx.shadowBlur = isSelected ? 16 : 8;\n }\n\n ctx.fillStyle = fillColor;\n ctx.fill();\n\n if (isSelected||isConnectFirst) {\n ctx.strokeStyle = isConnectFirst ? '#f59e0b' : '#fff';\n ctx.lineWidth = 2.5;\n ctx.stroke();\n } else if (isHovered) {\n ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)';\n ctx.lineWidth = 1.5;\n ctx.stroke();\n } else {\n ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.4)';\n ctx.lineWidth = 1;\n ctx.stroke();\n }\n ctx.restore();\n\n // Quality dot\n if (showQuality && asset.qualityScore!==null && asset.qualityScore!==undefined && size>8) {\n const q = asset.qualityScore;\n if (q < 70) {\n ctx.beginPath();\n ctx.arc(cx+size*0.4, cy-size*0.4, Math.max(3,size*0.14), 0, Math.PI*2);\n ctx.fillStyle = q<40?'#ef4444':'#f97316';\n ctx.fill();\n }\n }\n\n // Asset labels (detail 4, or 3 at high zoom)\n const showAssetLabel = showLabels && !clusterDim &&\n ((detailLevel>=4)||(detailLevel===3 && scale>=0.8));\n if (showAssetLabel && size>14) {\n const label = asset.name.length>12 ? asset.name.substring(0,11)+'...' : asset.name;\n ctx.save();\n ctx.font = Math.max(8,Math.min(11,size*0.38))+'px -apple-system,sans-serif';\n ctx.fillStyle = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.9)';\n ctx.textAlign='center';ctx.textBaseline='middle';\n ctx.fillText(label, cx, cy);\n ctx.restore();\n }\n }\n }\n\n // Cluster labels (pill badges)\n if (showLabels && detailLevel>=1) {\n for (const cluster of MAP.clusters) {\n if (cluster.assetIds.length===0) continue;\n if (hasSearch && !cluster.assetIds.some(id=>matchedIds.has(id))) continue;\n const s=w2s(cluster.centroid.x, cluster.centroid.y);\n drawPill(s.x, s.y-size*1.2, cluster.label, cluster.color, 14);\n }\n }\n\n // Sub-domain labels (detail 2+)\n if (showLabels && detailLevel>=2) {\n const subGroups = new Map();\n for (const a of MAP.assets) {\n if (!a.subDomain) continue;\n const key = a.domain+'|'+a.subDomain;\n if (!subGroups.has(key)) subGroups.set(key, []);\n subGroups.get(key).push(a);\n }\n for (const [, group] of subGroups) {\n let sx=0,sy=0;\n for (const a of group) { sx+=htp_x(a.q,a.r); sy+=htp_y(a.q,a.r); }\n const cx=sx/group.length, cy=sy/group.length;\n const s = w2s(cx, cy);\n drawPill(s.x, s.y+size*1.5, group[0].subDomain, '#64748b', 11);\n }\n }\n}\n\nfunction drawPill(x, y, text, color, fontSize) {\n if(!text) return;\n ctx.save();\n ctx.font = '600 '+fontSize+'px -apple-system,sans-serif';\n const tw=ctx.measureText(text).width;\n const ph=fontSize+8, pw=tw+20;\n ctx.beginPath();\n if (ctx.roundRect) ctx.roundRect(x-pw/2, y-ph/2, pw, ph, ph/2);\n else { ctx.rect(x-pw/2, y-ph/2, pw, ph); }\n ctx.fillStyle = isDark ? 'rgba(30,41,59,0.9)' : 'rgba(255,255,255,0.92)';\n ctx.shadowColor='rgba(0,0,0,0.15)'; ctx.shadowBlur=6;\n ctx.fill(); ctx.shadowBlur=0;\n ctx.fillStyle = isDark ? '#e2e8f0' : '#0f172a';\n ctx.textAlign='center'; ctx.textBaseline='middle';\n ctx.fillText(text, x, y);\n ctx.restore();\n}\n\n// ── Hit testing ───────────────────────────────────────────────────────────────\nfunction getAssetAt(sx, sy) {\n const w=s2w(sx,sy);\n for (const a of MAP.assets) {\n const wx=htp_x(a.q,a.r), wy=htp_y(a.q,a.r);\n const dx=Math.abs(w.x-wx), dy=Math.abs(w.y-wy);\n if (dx>HEX_SIZE||dy>HEX_SIZE) continue;\n if (dx*dx+dy*dy < HEX_SIZE*HEX_SIZE) return a;\n }\n return null;\n}\n\n// ── Search ────────────────────────────────────────────────────────────────────\nfunction getSearchMatches() {\n if(!searchQuery) return new Set();\n const q=searchQuery.toLowerCase();\n const m=new Set();\n for(const a of MAP.assets){\n if(a.name.toLowerCase().includes(q)||(a.domain&&a.domain.toLowerCase().includes(q))||\n (a.subDomain&&a.subDomain.toLowerCase().includes(q))) m.add(a.id);\n }\n return m;\n}\n\n// ── Pan & Zoom ────────────────────────────────────────────────────────────────\nlet dragging=false, lastMX=0, lastMY=0;\n\nwrap.addEventListener('mousedown', e=>{\n if(e.button!==0)return;\n dragging=true; lastMX=e.clientX; lastMY=e.clientY;\n wrap.classList.add('dragging');\n});\nwindow.addEventListener('mouseup', ()=>{dragging=false;wrap.classList.remove('dragging');});\nwindow.addEventListener('mousemove', e=>{\n if(dragging){\n vx+=e.clientX-lastMX; vy+=e.clientY-lastMY;\n lastMX=e.clientX; lastMY=e.clientY; draw(); return;\n }\n const rect=wrap.getBoundingClientRect();\n const sx=e.clientX-rect.left, sy=e.clientY-rect.top;\n const asset=getAssetAt(sx,sy);\n const newId=asset?asset.id:null;\n if(newId!==hoveredAssetId){hoveredAssetId=newId;draw();}\n const tt=document.getElementById('tooltip');\n if(asset){\n document.getElementById('tt-name').textContent=asset.name;\n document.getElementById('tt-domain').textContent=asset.domain+(asset.subDomain?' > '+asset.subDomain:'');\n document.getElementById('tt-quality').textContent=asset.qualityScore!==null?'Quality: '+asset.qualityScore+'/100':'';\n tt.style.display='block';tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY-8)+'px';\n } else { tt.style.display='none'; }\n});\n\nwrap.addEventListener('click', e=>{\n const rect=wrap.getBoundingClientRect();\n const sx=e.clientX-rect.left, sy=e.clientY-rect.top;\n const asset=getAssetAt(sx,sy);\n if(connectMode){\n if(!asset) return;\n if(!connectFirst){connectFirst=asset.id;draw();}\n else if(connectFirst!==asset.id){\n localConnections.push({id:crypto.randomUUID(),sourceAssetId:connectFirst,targetAssetId:asset.id,type:'connection'});\n connectFirst=null;draw();\n }\n return;\n }\n if(asset){selectedAssetId=asset.id;showDetailPanel(asset);}\n else{selectedAssetId=null;document.getElementById('detail-panel').classList.remove('open');}\n draw();\n});\n\n// Touch\nlet lastTouches=[];\nwrap.addEventListener('touchstart',e=>{lastTouches=[...e.touches];},{passive:true});\nwrap.addEventListener('touchmove',e=>{\n if(e.touches.length===1){\n vx+=e.touches[0].clientX-lastTouches[0].clientX;\n vy+=e.touches[0].clientY-lastTouches[0].clientY;draw();\n } else if(e.touches.length===2){\n const d0=Math.hypot(lastTouches[0].clientX-lastTouches[1].clientX,lastTouches[0].clientY-lastTouches[1].clientY);\n const d1=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);\n const mx=(e.touches[0].clientX+e.touches[1].clientX)/2;\n const my=(e.touches[0].clientY+e.touches[1].clientY)/2;\n applyZoom(d1/d0,mx,my);\n }\n lastTouches=[...e.touches];\n},{passive:true});\n\nwrap.addEventListener('wheel',e=>{\n e.preventDefault();\n const rect=wrap.getBoundingClientRect();\n applyZoom(e.deltaY<0?1.12:1/1.12,e.clientX-rect.left,e.clientY-rect.top);\n},{passive:false});\n\nfunction applyZoom(factor,sx,sy){\n const ns=Math.max(0.05,Math.min(8,scale*factor));\n const wx=(sx-vx)/scale,wy=(sy-vy)/scale;\n scale=ns;vx=sx-wx*scale;vy=sy-wy*scale;\n document.getElementById('zoom-pct').textContent=Math.round(scale*100)+'%';draw();\n}\ndocument.getElementById('zoom-in').addEventListener('click',()=>applyZoom(1.25,W/2,H/2));\ndocument.getElementById('zoom-out').addEventListener('click',()=>applyZoom(1/1.25,W/2,H/2));\n\n// Keyboard\nwrap.addEventListener('keydown',e=>{\n const step=40;\n if(e.key==='ArrowLeft'){vx+=step;draw();}\n else if(e.key==='ArrowRight'){vx-=step;draw();}\n else if(e.key==='ArrowUp'){vy+=step;draw();}\n else if(e.key==='ArrowDown'){vy-=step;draw();}\n else if(e.key==='+'||e.key==='=')applyZoom(1.2,W/2,H/2);\n else if(e.key==='-')applyZoom(1/1.2,W/2,H/2);\n else if(e.key==='Escape'){\n selectedAssetId=null;document.getElementById('detail-panel').classList.remove('open');\n if(connectMode)toggleConnect();draw();\n }\n});\n\n// ── Detail Panel ──────────────────────────────────────────────────────────────\nfunction showDetailPanel(asset) {\n document.getElementById('dp-name').textContent=asset.name;\n const body=document.getElementById('dp-body');\n const rows=[['Domain',asset.domain],['Sub-domain',asset.subDomain],\n ['Quality Score',asset.qualityScore!==null?renderQuality(asset.qualityScore):null],\n ...Object.entries(asset.metadata||{}).slice(0,8).map(([k,v])=>[k,String(v)])\n ].filter(([,v])=>v!==null&&v!==undefined&&v!=='');\n body.innerHTML=rows.map(([l,v])=>'<div class=\"meta-row\"><div class=\"meta-label\">'+esc(String(l))+'</div><div class=\"meta-value\">'+v+'</div></div>').join('');\n const related=localConnections.filter(c=>c.sourceAssetId===asset.id||c.targetAssetId===asset.id);\n if(related.length>0){\n body.innerHTML+='<div class=\"meta-row\"><div class=\"meta-label\">Connections ('+related.length+')</div><div>'+\n related.map(c=>{const oid=c.sourceAssetId===asset.id?c.targetAssetId:c.sourceAssetId;\n const o=assetIndex.get(oid);return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">'+(o?esc(o.name):oid)+'</div>';}).join('')+'</div></div>';\n }\n document.getElementById('detail-panel').classList.add('open');\n}\nfunction renderQuality(s){\n const c=s>=70?'#22c55e':s>=40?'#f97316':'#ef4444';\n return s+'/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:'+s+'%;background:'+c+'\"></div></div>';\n}\nfunction esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}\ndocument.getElementById('dp-close').addEventListener('click',()=>{\n document.getElementById('detail-panel').classList.remove('open');selectedAssetId=null;draw();\n});\n\n// ── Toolbar ───────────────────────────────────────────────────────────────────\n[1,2,3,4].forEach(n=>{\n document.getElementById('dl-'+n).addEventListener('click',()=>{\n detailLevel=n;document.querySelectorAll('.detail-btn').forEach(b=>b.classList.remove('active'));\n document.getElementById('dl-'+n).classList.add('active');draw();\n });\n});\ndocument.getElementById('btn-labels').addEventListener('click',()=>{\n showLabels=!showLabels;document.getElementById('btn-labels').classList.toggle('active',showLabels);draw();\n});\ndocument.getElementById('btn-quality').addEventListener('click',()=>{\n showQuality=!showQuality;document.getElementById('btn-quality').classList.toggle('active',showQuality);draw();\n});\nfunction toggleConnect(){\n connectMode=!connectMode;connectFirst=null;\n document.getElementById('connect-btn').classList.toggle('active',connectMode);\n wrap.classList.toggle('connecting',connectMode);\n document.getElementById('connect-hint').style.display=connectMode?'block':'none';draw();\n}\ndocument.getElementById('connect-btn').addEventListener('click',toggleConnect);\ndocument.getElementById('theme-btn').addEventListener('click',()=>{\n isDark=!isDark;\n document.body.classList.toggle('dark',isDark);document.body.classList.toggle('light',!isDark);\n document.getElementById('theme-btn').innerHTML=isDark?'&#9788;':'&#9790;';draw();\n});\ndocument.getElementById('search-input').addEventListener('input',e=>{searchQuery=e.target.value.trim();draw();});\n\n// ── Init ──────────────────────────────────────────────────────────────────────\nresize(); fitToView();\ndocument.getElementById('zoom-pct').textContent=Math.round(scale*100)+'%';\ndraw();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ── Discovery App (Combined Enterprise Frontend) ──────────────────────────────\n\nexport function exportDiscoveryApp(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): string {\n const theme = options?.theme ?? 'dark';\n\n // ── Topology D3 data ──────────────────────────────────────────────────────\n const graphData = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id, name: n.name, type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n })),\n links: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relationship: e.relationship, confidence: e.confidence, evidence: e.evidence,\n })),\n });\n\n // ── Hex map data ──────────────────────────────────────────────────────────\n const { assets, clusters, connections } = buildMapData(nodes, edges, { theme });\n const isEmpty = assets.length === 0;\n const HEX_SIZE = 24;\n const mapJson = JSON.stringify({\n assets: assets.map(a => ({\n id: a.id, name: a.name, domain: a.domain, subDomain: a.subDomain ?? null,\n qualityScore: a.qualityScore ?? null, metadata: a.metadata,\n q: a.position.q, r: a.position.r,\n })),\n clusters: clusters.map(c => ({\n id: c.id, label: c.label, domain: c.domain, color: c.color,\n assetIds: c.assetIds, centroid: c.centroid,\n })),\n connections: connections.map(c => ({\n id: c.id, sourceAssetId: c.sourceAssetId, targetAssetId: c.targetAssetId,\n type: c.type ?? 'connection',\n })),\n });\n\n const nodeCount = nodes.length;\n const edgeCount = edges.length;\n const assetCount = assets.length;\n const clusterCount = clusters.length;\n\n return `<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"${theme}\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n<title>Cartography \\u2014 Datasynx Discovery</title>\n<script src=\"https://d3js.org/d3.v7.min.js\"><\\/script>\n<style>\n/* ── CSS Custom Properties ──────────────────────────────────────────────── */\n:root{\n --bg-base:#0f172a;--bg-surface:#1e293b;--bg-elevated:#273148;\n --border:#334155;--border-dim:#1e293b;\n --text:#e2e8f0;--text-muted:#94a3b8;--text-dim:#475569;\n --accent:#3b82f6;--accent-hover:#2563eb;--accent-dim:rgba(59,130,246,.12);\n}\n[data-theme=\"light\"]{\n --bg-base:#f8fafc;--bg-surface:#ffffff;--bg-elevated:#f1f5f9;\n --border:#e2e8f0;--border-dim:#f1f5f9;\n --text:#0f172a;--text-muted:#64748b;--text-dim:#94a3b8;\n --accent:#2563eb;--accent-hover:#1d4ed8;--accent-dim:rgba(37,99,235,.08);\n}\n\n/* ── Reset ──────────────────────────────────────────────────────────────── */\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter',sans-serif}\nbody{display:flex;flex-direction:column;background:var(--bg-base);color:var(--text)}\n\n/* ── Topbar ─────────────────────────────────────────────────────────────── */\n#topbar{\n height:56px;display:flex;align-items:center;gap:16px;padding:0 20px;\n background:var(--bg-surface);border-bottom:1px solid var(--border);z-index:100;flex-shrink:0;\n}\n.tb-left{display:flex;align-items:center;gap:10px}\n.brand-product{font-size:15px;font-weight:600;color:var(--text-muted)}\n.tb-center{display:flex;align-items:center;gap:2px;margin-left:auto;\n background:var(--bg-elevated);border-radius:8px;padding:3px}\n.tab-btn{\n padding:6px 16px;border:none;border-radius:6px;font-size:13px;font-weight:500;\n cursor:pointer;color:var(--text-muted);background:transparent;font-family:inherit;\n transition:all .15s;\n}\n.tab-btn:hover{color:var(--text)}\n.tab-btn.active{background:var(--accent);color:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2)}\n.tb-right{display:flex;align-items:center;gap:8px;margin-left:auto}\n.tb-search{\n display:flex;align-items:center;gap:6px;background:var(--bg-elevated);\n border:1px solid var(--border);border-radius:8px;padding:5px 10px;\n}\n.tb-search input{\n border:none;background:transparent;font-size:13px;outline:none;width:160px;\n color:var(--text);font-family:inherit;\n}\n.tb-search input::placeholder{color:var(--text-dim)}\n.tb-search svg{flex-shrink:0;color:var(--text-dim)}\n.icon-btn{\n width:36px;height:36px;border-radius:8px;border:1px solid var(--border);\n background:var(--bg-surface);cursor:pointer;display:flex;align-items:center;\n justify-content:center;color:var(--text-muted);text-decoration:none;transition:all .15s;font-size:16px;\n}\n.icon-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}\n.tb-stats{font-size:11px;color:var(--text-dim);white-space:nowrap}\n\n/* ── Views ──────────────────────────────────────────────────────────────── */\n.view{flex:1;display:none;overflow:hidden;position:relative}\n.view.active{display:flex}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n MAP VIEW\n ═══════════════════════════════════════════════════════════════════════════ */\n#map-wrap{flex:1;position:relative;overflow:hidden;cursor:grab}\n#map-wrap.dragging{cursor:grabbing}\n#map-wrap.connecting{cursor:crosshair}\n#map-wrap canvas{display:block;width:100%;height:100%}\n#map-detail{\n width:280px;background:var(--bg-surface);border-left:1px solid var(--border);\n display:flex;flex-direction:column;transform:translateX(100%);\n transition:transform .2s ease;z-index:5;flex-shrink:0;overflow-y:auto;\n}\n#map-detail.open{transform:translateX(0)}\n#map-detail .panel-header{\n padding:16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;\n}\n#map-detail .panel-header h3{font-size:14px;font-weight:600;flex:1;word-break:break-word}\n.close-btn{\n width:24px;height:24px;border:none;background:transparent;cursor:pointer;\n color:var(--text-muted);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;\n}\n.close-btn:hover{background:var(--bg-elevated)}\n.panel-body{padding:12px 16px;display:flex;flex-direction:column;gap:12px}\n.meta-row{display:flex;flex-direction:column;gap:3px}\n.meta-label{font-size:11px;font-weight:500;color:var(--text-dim);text-transform:uppercase;letter-spacing:.05em}\n.meta-value{font-size:13px;word-break:break-all}\n.quality-bar{height:6px;border-radius:3px;background:var(--bg-elevated);margin-top:4px}\n.quality-fill{height:6px;border-radius:3px;transition:width .3s}\n\n/* Map toolbars */\n#map-tb-left{position:absolute;bottom:20px;left:20px;display:flex;gap:8px;z-index:10}\n#map-tb-right{position:absolute;bottom:20px;right:20px;display:flex;flex-direction:column;align-items:flex-end;gap:8px;z-index:10}\n.tb-tool{\n width:40px;height:40px;border-radius:10px;border:1px solid var(--border);\n background:var(--bg-surface);box-shadow:0 1px 4px rgba(0,0,0,.08);cursor:pointer;\n display:flex;align-items:center;justify-content:center;font-size:18px;\n transition:all .15s;color:var(--text);font-family:-apple-system,sans-serif;\n}\n#btn-all-labels{font-size:14px;font-weight:700;letter-spacing:-.02em}\n.tb-tool:hover{border-color:var(--text-muted)}\n.tb-tool.active{background:var(--accent-dim);border-color:var(--accent)}\n.map-zoom{display:flex;align-items:center;gap:6px}\n.zoom-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid var(--border);\n background:var(--bg-surface);cursor:pointer;font-size:18px;color:var(--text);\n display:flex;align-items:center;justify-content:center;\n}\n.zoom-btn:hover{background:var(--bg-elevated)}\n#map-zoom-pct{font-size:12px;font-weight:500;color:var(--text-dim);min-width:38px;text-align:center}\n#map-connect-hint{\n position:absolute;top:12px;left:50%;transform:translateX(-50%);\n background:#fef3c7;border:1px solid #f59e0b;color:#92400e;\n padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500;\n display:none;z-index:20;pointer-events:none;\n}\n#map-tooltip{\n position:fixed;background:var(--bg-surface);color:var(--text);border-radius:8px;\n padding:8px 12px;font-size:12px;pointer-events:none;z-index:200;\n display:none;max-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.25);border:1px solid var(--border);\n}\n#map-tooltip .tt-name{font-weight:600;margin-bottom:2px}\n#map-tooltip .tt-domain{color:var(--text-muted);font-size:11px}\n#map-tooltip .tt-quality{font-size:11px;margin-top:2px}\n#map-empty{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:12px;color:var(--text-muted);\n}\n#map-empty p{font-size:14px}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n TOPOLOGY VIEW\n ═══════════════════════════════════════════════════════════════════════════ */\n#topo-panel{\n width:220px;min-width:220px;height:100%;overflow:hidden;\n background:var(--bg-surface);border-right:1px solid var(--border);\n display:flex;flex-direction:column;\n}\n#topo-panel-header{\n padding:10px 12px 8px;border-bottom:1px solid var(--border);\n font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.6px;\n}\n#topo-search{\n width:calc(100% - 16px);margin:8px;padding:5px 8px;\n background:var(--bg-elevated);border:1px solid var(--border);border-radius:5px;\n color:var(--text);font-size:11px;font-family:inherit;outline:none;\n}\n#topo-search:focus{border-color:var(--accent)}\n#topo-list{flex:1;overflow-y:auto;padding-bottom:8px}\n.topo-item{\n padding:5px 12px;cursor:pointer;font-size:11px;\n display:flex;align-items:center;gap:6px;border-left:2px solid transparent;\n}\n.topo-item:hover{background:var(--bg-elevated)}\n.topo-item.active{background:var(--accent-dim);border-left-color:var(--accent)}\n.topo-dot{width:7px;height:7px;border-radius:2px;flex-shrink:0}\n.topo-name{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}\n.topo-type{color:var(--text-dim);font-size:9px;flex-shrink:0}\n\n#topo-graph{flex:1;height:100%;position:relative}\n#topo-graph svg{width:100%;height:100%}\n.hull{opacity:.12;stroke-width:1.5;stroke-opacity:.25}\n.hull-label{font-size:13px;font-weight:700;letter-spacing:1px;text-transform:uppercase;fill-opacity:.5;pointer-events:none}\n.link{stroke-opacity:.4}\n.link-label{font-size:8px;fill:var(--text-dim);pointer-events:none;opacity:0}\n.node-hex{stroke-width:1.8;cursor:pointer;transition:opacity .15s}\n.node-hex:hover{filter:brightness(1.3);stroke-width:3}\n.node-hex.selected{stroke-width:3.5;filter:brightness(1.5)}\n.node-label{font-size:10px;fill:var(--text);pointer-events:none;opacity:0}\n\n#topo-sidebar{\n width:300px;min-width:300px;height:100%;overflow-y:auto;\n background:var(--bg-surface);border-left:1px solid var(--border);\n padding:16px;font-size:12px;line-height:1.6;\n}\n#topo-sidebar h2{margin:0 0 8px;font-size:14px;color:var(--accent)}\n#topo-sidebar .meta-table{width:100%;border-collapse:collapse}\n#topo-sidebar .meta-table td{padding:3px 6px;border-bottom:1px solid var(--border-dim);vertical-align:top}\n#topo-sidebar .meta-table td:first-child{color:var(--text-dim);white-space:nowrap;width:90px}\n#topo-sidebar .tag{display:inline-block;background:var(--bg-elevated);border-radius:3px;padding:1px 5px;margin:1px;font-size:10px}\n#topo-sidebar .conf-bar{height:5px;border-radius:3px;background:var(--bg-elevated);margin-top:3px}\n#topo-sidebar .conf-fill{height:100%;border-radius:3px}\n#topo-sidebar .edges-list{margin-top:12px}\n#topo-sidebar .edge-item{padding:4px 0;border-bottom:1px solid var(--border-dim);color:var(--text-dim);font-size:11px}\n#topo-sidebar .edge-item span{color:var(--text)}\n.hint{color:var(--text-dim);font-size:11px;margin-top:8px}\n\n#topo-hud{\n position:absolute;top:10px;left:10px;background:rgba(15,23,42,.88);\n padding:10px 14px;border-radius:8px;font-size:12px;border:1px solid var(--border);pointer-events:none;\n}\n#topo-hud strong{color:var(--accent)}\n#topo-hud .stats{color:var(--text-dim)}\n#topo-hud .zoom-level{color:var(--text-dim);font-size:10px;margin-top:2px}\n\n#topo-toolbar{position:absolute;top:10px;right:10px;display:flex;flex-wrap:wrap;gap:4px;pointer-events:auto;align-items:center}\n.filter-btn{\n background:rgba(15,23,42,.85);border:1px solid var(--border);border-radius:6px;\n color:var(--text);padding:4px 10px;font-size:11px;cursor:pointer;\n font-family:inherit;display:flex;align-items:center;gap:5px;\n}\n.filter-btn:hover{border-color:var(--text-dim)}\n.filter-btn.off{opacity:.35}\n.filter-dot{width:8px;height:8px;border-radius:2px;display:inline-block}\n.export-btn{\n background:rgba(15,23,42,.85);border:1px solid var(--border);border-radius:6px;\n color:var(--accent);padding:4px 12px;font-size:11px;cursor:pointer;font-family:inherit;\n}\n.export-btn:hover{border-color:var(--accent);background:var(--accent-dim)}\n</style>\n</head>\n<body>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n TOPBAR\n ═══════════════════════════════════════════════════════════════════════════ -->\n<header id=\"topbar\">\n <div class=\"tb-left\">\n <span class=\"brand-product\">Cartography</span>\n </div>\n <div class=\"tb-center\">\n <button class=\"tab-btn active\" id=\"tab-map-btn\" data-tab=\"map\">Map</button>\n <button class=\"tab-btn\" id=\"tab-topo-btn\" data-tab=\"topo\">Topology</button>\n </div>\n <div class=\"tb-right\">\n <span class=\"tb-stats\">${nodeCount} nodes &middot; ${edgeCount} edges</span>\n <div class=\"tb-search\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"M21 21l-4.35-4.35\"/>\n </svg>\n <input id=\"global-search\" type=\"text\" placeholder=\"Search...\" autocomplete=\"off\" spellcheck=\"false\"/>\n </div>\n <button id=\"theme-btn\" class=\"icon-btn\" title=\"Toggle theme\" aria-label=\"Toggle theme\">\n ${theme === 'dark' ? '&#9788;' : '&#9790;'}\n </button>\n </div>\n</header>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n MAP VIEW\n ═══════════════════════════════════════════════════════════════════════════ -->\n<div id=\"view-map\" class=\"view active\">\n <div id=\"map-wrap\" tabindex=\"0\" aria-label=\"Data cartography hex map\">\n <canvas id=\"hexmap\" aria-hidden=\"true\"></canvas>\n ${isEmpty ? '<div id=\"map-empty\"><p style=\"font-size:48px\">&#128506;</p><p>No data assets discovered yet</p><p style=\"font-size:12px\">Run <code>datasynx-cartography discover</code> to populate the map</p></div>' : ''}\n </div>\n <div id=\"map-detail\">\n <div class=\"panel-header\">\n <h3 id=\"md-name\">&mdash;</h3>\n <button class=\"close-btn\" id=\"md-close\" aria-label=\"Close\">&#10005;</button>\n </div>\n <div class=\"panel-body\" id=\"md-body\"></div>\n </div>\n <div id=\"map-tb-left\">\n <button class=\"tb-tool active\" id=\"btn-labels\" title=\"Toggle labels\">&#127991;</button>\n <button class=\"tb-tool\" id=\"btn-all-labels\" title=\"Show all hex labels\">Aa</button>\n <button class=\"tb-tool\" id=\"btn-quality\" title=\"Quality layer\">&#128065;</button>\n <button class=\"tb-tool\" id=\"btn-connect\" title=\"Connection tool\">&#128279;</button>\n </div>\n <div id=\"map-tb-right\">\n <div class=\"map-zoom\">\n <button class=\"zoom-btn\" id=\"mz-out\">&minus;</button>\n <span id=\"map-zoom-pct\">100%</span>\n <button class=\"zoom-btn\" id=\"mz-in\">+</button>\n </div>\n </div>\n <div id=\"map-connect-hint\">Click two assets to create a connection</div>\n <div id=\"map-tooltip\"><div class=\"tt-name\" id=\"mtt-name\"></div><div class=\"tt-domain\" id=\"mtt-domain\"></div><div class=\"tt-quality\" id=\"mtt-quality\"></div></div>\n</div>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n TOPOLOGY VIEW\n ═══════════════════════════════════════════════════════════════════════════ -->\n<div id=\"view-topo\" class=\"view\">\n <div id=\"topo-panel\">\n <div id=\"topo-panel-header\">Nodes (${nodeCount})</div>\n <input id=\"topo-search\" type=\"text\" placeholder=\"Search nodes\\u2026\" autocomplete=\"off\" spellcheck=\"false\"/>\n <div id=\"topo-list\"></div>\n </div>\n <div id=\"topo-graph\">\n <div id=\"topo-hud\">\n <strong>Topology</strong>&nbsp;\n <span class=\"stats\">${nodeCount} nodes &middot; ${edgeCount} edges</span><br/>\n <span class=\"zoom-level\">Scroll = zoom &middot; Drag = pan &middot; Click = details</span>\n </div>\n <div id=\"topo-toolbar\"></div>\n <svg></svg>\n </div>\n <div id=\"topo-sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Click a node to view details.</p>\n </div>\n</div>\n\n<script>\n// ═══════════════════════════════════════════════════════════════════════════════\n// SHARED STATE\n// ═══════════════════════════════════════════════════════════════════════════════\nlet isDark = document.documentElement.getAttribute('data-theme') === 'dark';\nlet currentTab = 'map';\nlet topoInited = false;\n\n// ── Theme toggle ─────────────────────────────────────────────────────────────\ndocument.getElementById('theme-btn').addEventListener('click', function() {\n isDark = !isDark;\n document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');\n this.innerHTML = isDark ? '\\\\u2606' : '\\\\u263E';\n if (typeof drawMap === 'function') drawMap();\n});\n\n// ── Tab switching ────────────────────────────────────────────────────────────\ndocument.querySelectorAll('.tab-btn').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var tab = this.getAttribute('data-tab');\n if (tab === currentTab) return;\n currentTab = tab;\n document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });\n this.classList.add('active');\n document.querySelectorAll('.view').forEach(function(v) { v.classList.remove('active'); });\n document.getElementById('view-' + tab).classList.add('active');\n if (tab === 'topo' && !topoInited) { initTopology(); topoInited = true; }\n if (tab === 'map' && typeof drawMap === 'function') { resizeMap(); }\n });\n});\n\n// ── Global search ────────────────────────────────────────────────────────────\ndocument.getElementById('global-search').addEventListener('input', function(e) {\n var q = e.target.value.trim();\n if (typeof setMapSearch === 'function') setMapSearch(q);\n if (typeof setTopoSearch === 'function') setTopoSearch(q);\n});\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// MAP VIEW\n// ═══════════════════════════════════════════════════════════════════════════════\nvar MAP = ${mapJson};\nvar MAP_HEX = ${HEX_SIZE};\nvar MAP_EMPTY = ${isEmpty};\n\nvar mapAssetIndex = new Map();\nvar mapClusterByAsset = new Map();\nfor (var ci = 0; ci < MAP.clusters.length; ci++) {\n var c = MAP.clusters[ci];\n for (var ai = 0; ai < c.assetIds.length; ai++) mapClusterByAsset.set(c.assetIds[ai], c);\n}\nfor (var ni = 0; ni < MAP.assets.length; ni++) mapAssetIndex.set(MAP.assets[ni].id, MAP.assets[ni]);\n\nvar mapCanvas = document.getElementById('hexmap');\nvar mapCtx = mapCanvas.getContext('2d');\nvar mapWrap = document.getElementById('map-wrap');\nvar mW = 0, mH = 0;\nvar mvx = 0, mvy = 0, mScale = 1;\nvar mDetailLevel = 2, mShowLabels = true, mShowQuality = false, mShowAllLabels = false;\nvar mConnectMode = false, mConnectFirst = null;\nvar mHoveredId = null, mSelectedId = null;\nvar mSearchQuery = '';\nvar mLocalConns = MAP.connections.slice();\n\nfunction setMapSearch(q) { mSearchQuery = q; drawMap(); }\n\nfunction resizeMap() {\n var dpr = window.devicePixelRatio || 1;\n mW = mapWrap.clientWidth; mH = mapWrap.clientHeight;\n mapCanvas.width = mW * dpr; mapCanvas.height = mH * dpr;\n mapCanvas.style.width = mW + 'px'; mapCanvas.style.height = mH + 'px';\n mapCtx.setTransform(dpr, 0, 0, dpr, 0, 0);\n drawMap();\n}\nwindow.addEventListener('resize', function() { if (currentTab === 'map') resizeMap(); });\n\nfunction mHtp_x(q, r) { return MAP_HEX * (1.5 * q); }\nfunction mHtp_y(q, r) { return MAP_HEX * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r); }\nfunction mW2s(wx, wy) { return { x: wx * mScale + mvx, y: wy * mScale + mvy }; }\nfunction mS2w(sx, sy) { return { x: (sx - mvx) / mScale, y: (sy - mvy) / mScale }; }\n\nfunction mapFitToView() {\n if (MAP_EMPTY || MAP.assets.length === 0) { mvx = 0; mvy = 0; mScale = 1; return; }\n var mnx = Infinity, mny = Infinity, mxx = -Infinity, mxy = -Infinity;\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i], px = mHtp_x(a.q, a.r), py = mHtp_y(a.q, a.r);\n if (px < mnx) mnx = px; if (py < mny) mny = py; if (px > mxx) mxx = px; if (py > mxy) mxy = py;\n }\n var pw = mxx - mnx + MAP_HEX * 4, ph = mxy - mny + MAP_HEX * 4;\n mScale = Math.min(mW / pw, mH / ph, 2) * 0.85;\n mvx = mW / 2 - ((mnx + mxx) / 2) * mScale;\n mvy = mH / 2 - ((mny + mxy) / 2) * mScale;\n}\n\nfunction mHexPath(cx, cy, r) {\n mapCtx.beginPath();\n for (var i = 0; i < 6; i++) {\n var angle = Math.PI / 180 * (60 * i);\n var x = cx + r * Math.cos(angle), y = cy + r * Math.sin(angle);\n i === 0 ? mapCtx.moveTo(x, y) : mapCtx.lineTo(x, y);\n }\n mapCtx.closePath();\n}\n\nfunction mShadeV(hex, amt) {\n if (!hex || hex.length < 7) return hex;\n var n = parseInt(hex.replace('#', ''), 16);\n var r = Math.min(255, (n >> 16) + amt), g = Math.min(255, ((n >> 8) & 0xff) + amt), b = Math.min(255, (n & 0xff) + amt);\n return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');\n}\n\nfunction mGetSearchMatches() {\n if (!mSearchQuery) return new Set();\n var q = mSearchQuery.toLowerCase(), m = new Set();\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i];\n if (a.name.toLowerCase().includes(q) || (a.domain && a.domain.toLowerCase().includes(q)) ||\n (a.subDomain && a.subDomain.toLowerCase().includes(q))) m.add(a.id);\n }\n return m;\n}\n\nfunction mDrawPill(x, y, text, color, fontSize) {\n if (!text) return;\n mapCtx.save();\n mapCtx.font = '600 ' + fontSize + 'px -apple-system,sans-serif';\n var tw = mapCtx.measureText(text).width;\n var ph = fontSize + 8, pw = tw + 20;\n mapCtx.beginPath();\n if (mapCtx.roundRect) mapCtx.roundRect(x - pw / 2, y - ph / 2, pw, ph, ph / 2);\n else mapCtx.rect(x - pw / 2, y - ph / 2, pw, ph);\n mapCtx.fillStyle = isDark ? 'rgba(30,41,59,0.9)' : 'rgba(255,255,255,0.92)';\n mapCtx.shadowColor = 'rgba(0,0,0,0.15)'; mapCtx.shadowBlur = 6;\n mapCtx.fill(); mapCtx.shadowBlur = 0;\n mapCtx.fillStyle = isDark ? '#e2e8f0' : '#0f172a';\n mapCtx.textAlign = 'center'; mapCtx.textBaseline = 'middle';\n mapCtx.fillText(text, x, y);\n mapCtx.restore();\n}\n\nfunction drawMap() {\n mapCtx.clearRect(0, 0, mW, mH);\n var bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-base').trim();\n mapCtx.fillStyle = bg || (isDark ? '#0f172a' : '#f8fafc');\n mapCtx.fillRect(0, 0, mW, mH);\n if (MAP_EMPTY) return;\n\n var size = MAP_HEX * mScale;\n var matchedIds = mGetSearchMatches();\n var hasSearch = mSearchQuery.length > 0;\n\n // Connections\n mapCtx.save();\n mapCtx.strokeStyle = isDark ? 'rgba(148,163,184,0.35)' : 'rgba(100,116,139,0.25)';\n mapCtx.lineWidth = 1.5; mapCtx.setLineDash([4, 4]);\n for (var ci = 0; ci < mLocalConns.length; ci++) {\n var conn = mLocalConns[ci];\n var src = mapAssetIndex.get(conn.sourceAssetId), tgt = mapAssetIndex.get(conn.targetAssetId);\n if (!src || !tgt) continue;\n var sp = mW2s(mHtp_x(src.q, src.r), mHtp_y(src.q, src.r));\n var tp = mW2s(mHtp_x(tgt.q, tgt.r), mHtp_y(tgt.q, tgt.r));\n mapCtx.beginPath(); mapCtx.moveTo(sp.x, sp.y); mapCtx.lineTo(tp.x, tp.y); mapCtx.stroke();\n }\n mapCtx.setLineDash([]); mapCtx.restore();\n\n // Hexagons per cluster\n for (var cli = 0; cli < MAP.clusters.length; cli++) {\n var cluster = MAP.clusters[cli];\n var baseColor = cluster.color;\n var clusterAssets = cluster.assetIds.map(function(id) { return mapAssetIndex.get(id); }).filter(Boolean);\n var isClusterMatch = !hasSearch || clusterAssets.some(function(a) { return matchedIds.has(a.id); });\n var clusterDim = hasSearch && !isClusterMatch;\n\n for (var ai = 0; ai < clusterAssets.length; ai++) {\n var asset = clusterAssets[ai];\n var wx = mHtp_x(asset.q, asset.r), wy = mHtp_y(asset.q, asset.r);\n var s = mW2s(wx, wy), cx = s.x, cy = s.y;\n if (cx + size < 0 || cx - size > mW || cy + size < 0 || cy - size > mH) continue;\n\n var shade = ai % 3 === 0 ? 18 : ai % 3 === 1 ? 8 : 0;\n var fillColor = mShadeV(baseColor, shade);\n if (mShowQuality && asset.qualityScore !== null && asset.qualityScore !== undefined) {\n if (asset.qualityScore < 40) fillColor = '#ef4444';\n else if (asset.qualityScore < 70) fillColor = '#f97316';\n }\n\n var alpha = clusterDim ? 0.18 : 1;\n var isHov = asset.id === mHoveredId, isSel = asset.id === mSelectedId, isCF = asset.id === mConnectFirst;\n\n mapCtx.save(); mapCtx.globalAlpha = alpha;\n mHexPath(cx, cy, size * 0.92);\n if (isDark && (isHov || isSel || isCF)) { mapCtx.shadowColor = fillColor; mapCtx.shadowBlur = isSel ? 16 : 8; }\n mapCtx.fillStyle = fillColor; mapCtx.fill();\n if (isSel || isCF) { mapCtx.strokeStyle = isCF ? '#f59e0b' : '#fff'; mapCtx.lineWidth = 2.5; mapCtx.stroke(); }\n else if (isHov) { mapCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)'; mapCtx.lineWidth = 1.5; mapCtx.stroke(); }\n else { mapCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.4)'; mapCtx.lineWidth = 1; mapCtx.stroke(); }\n mapCtx.restore();\n\n if (mShowQuality && asset.qualityScore !== null && asset.qualityScore !== undefined && size > 8 && asset.qualityScore < 70) {\n mapCtx.beginPath(); mapCtx.arc(cx + size * 0.4, cy - size * 0.4, Math.max(3, size * 0.14), 0, Math.PI * 2);\n mapCtx.fillStyle = asset.qualityScore < 40 ? '#ef4444' : '#f97316'; mapCtx.fill();\n }\n\n var showAssetLabel = mShowLabels && !clusterDim && (mShowAllLabels || (mDetailLevel >= 4) || (mDetailLevel === 3 && mScale >= 0.8));\n if (showAssetLabel && size > 6) {\n var label = asset.name.length > 12 ? asset.name.substring(0, 11) + '...' : asset.name;\n mapCtx.save();\n mapCtx.font = Math.max(8, Math.min(11, size * 0.38)) + 'px -apple-system,sans-serif';\n mapCtx.fillStyle = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.9)';\n mapCtx.textAlign = 'center'; mapCtx.textBaseline = 'middle';\n mapCtx.fillText(label, cx, cy); mapCtx.restore();\n }\n }\n }\n\n // Cluster labels\n if (mShowLabels && mDetailLevel >= 1) {\n for (var cli2 = 0; cli2 < MAP.clusters.length; cli2++) {\n var cl = MAP.clusters[cli2];\n if (cl.assetIds.length === 0) continue;\n if (hasSearch && !cl.assetIds.some(function(id) { return matchedIds.has(id); })) continue;\n var sc = mW2s(cl.centroid.x, cl.centroid.y);\n mDrawPill(sc.x, sc.y - size * 1.2, cl.label, cl.color, 14);\n }\n }\n\n // Sub-domain labels\n if (mShowLabels && mDetailLevel >= 2) {\n var subGroups = new Map();\n for (var si = 0; si < MAP.assets.length; si++) {\n var sa = MAP.assets[si];\n if (!sa.subDomain) continue;\n var key = sa.domain + '|' + sa.subDomain;\n if (!subGroups.has(key)) subGroups.set(key, []);\n subGroups.get(key).push(sa);\n }\n subGroups.forEach(function(group) {\n var sx = 0, sy = 0;\n for (var gi = 0; gi < group.length; gi++) { sx += mHtp_x(group[gi].q, group[gi].r); sy += mHtp_y(group[gi].q, group[gi].r); }\n var cxs = sx / group.length, cys = sy / group.length;\n var spt = mW2s(cxs, cys);\n mDrawPill(spt.x, spt.y + size * 1.5, group[0].subDomain, '#64748b', 11);\n });\n }\n}\n\n// ── Map hit test ─────────────────────────────────────────────────────────────\nfunction mGetAssetAt(sx, sy) {\n var w = mS2w(sx, sy);\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i], wx = mHtp_x(a.q, a.r), wy = mHtp_y(a.q, a.r);\n var dx = Math.abs(w.x - wx), dy = Math.abs(w.y - wy);\n if (dx > MAP_HEX || dy > MAP_HEX) continue;\n if (dx * dx + dy * dy < MAP_HEX * MAP_HEX) return a;\n }\n return null;\n}\n\n// ── Map pan / zoom ───────────────────────────────────────────────────────────\nvar mDragging = false, mLastMX = 0, mLastMY = 0;\nmapWrap.addEventListener('mousedown', function(e) {\n if (e.button !== 0) return;\n mDragging = true; mLastMX = e.clientX; mLastMY = e.clientY;\n mapWrap.classList.add('dragging');\n});\nwindow.addEventListener('mouseup', function() { mDragging = false; mapWrap.classList.remove('dragging'); });\nwindow.addEventListener('mousemove', function(e) {\n if (currentTab !== 'map') return;\n if (mDragging) {\n mvx += e.clientX - mLastMX; mvy += e.clientY - mLastMY;\n mLastMX = e.clientX; mLastMY = e.clientY; drawMap(); return;\n }\n var rect = mapWrap.getBoundingClientRect();\n var sx = e.clientX - rect.left, sy = e.clientY - rect.top;\n var asset = mGetAssetAt(sx, sy);\n var newId = asset ? asset.id : null;\n if (newId !== mHoveredId) { mHoveredId = newId; drawMap(); }\n var tt = document.getElementById('map-tooltip');\n if (asset) {\n document.getElementById('mtt-name').textContent = asset.name;\n document.getElementById('mtt-domain').textContent = asset.domain + (asset.subDomain ? ' > ' + asset.subDomain : '');\n document.getElementById('mtt-quality').textContent = asset.qualityScore !== null ? 'Quality: ' + asset.qualityScore + '/100' : '';\n tt.style.display = 'block'; tt.style.left = (e.clientX + 12) + 'px'; tt.style.top = (e.clientY - 8) + 'px';\n } else { tt.style.display = 'none'; }\n});\n\nmapWrap.addEventListener('click', function(e) {\n var rect = mapWrap.getBoundingClientRect();\n var sx = e.clientX - rect.left, sy = e.clientY - rect.top;\n var asset = mGetAssetAt(sx, sy);\n if (mConnectMode) {\n if (!asset) return;\n if (!mConnectFirst) { mConnectFirst = asset.id; drawMap(); }\n else if (mConnectFirst !== asset.id) {\n mLocalConns.push({ id: crypto.randomUUID(), sourceAssetId: mConnectFirst, targetAssetId: asset.id, type: 'connection' });\n mConnectFirst = null; drawMap();\n }\n return;\n }\n if (asset) { mSelectedId = asset.id; mShowDetail(asset); }\n else { mSelectedId = null; document.getElementById('map-detail').classList.remove('open'); }\n drawMap();\n});\n\nmapWrap.addEventListener('wheel', function(e) {\n e.preventDefault();\n var rect = mapWrap.getBoundingClientRect();\n mApplyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - rect.left, e.clientY - rect.top);\n}, { passive: false });\n\nfunction mApplyZoom(factor, sx, sy) {\n var ns = Math.max(0.05, Math.min(8, mScale * factor));\n var wx = (sx - mvx) / mScale, wy = (sy - mvy) / mScale;\n mScale = ns; mvx = sx - wx * mScale; mvy = sy - wy * mScale;\n document.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\n drawMap();\n}\n\ndocument.getElementById('mz-in').addEventListener('click', function() { mApplyZoom(1.25, mW / 2, mH / 2); });\ndocument.getElementById('mz-out').addEventListener('click', function() { mApplyZoom(1 / 1.25, mW / 2, mH / 2); });\n\n// ── Map detail panel ─────────────────────────────────────────────────────────\nfunction mEsc(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction mRenderQ(s) {\n var c = s >= 70 ? '#22c55e' : s >= 40 ? '#f97316' : '#ef4444';\n return s + '/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:' + s + '%;background:' + c + '\"></div></div>';\n}\nfunction mShowDetail(asset) {\n document.getElementById('md-name').textContent = asset.name;\n var body = document.getElementById('md-body');\n var rows = [['Domain', asset.domain], ['Sub-domain', asset.subDomain],\n ['Quality', asset.qualityScore !== null ? mRenderQ(asset.qualityScore) : null]\n ].concat(Object.entries(asset.metadata || {}).slice(0, 8).map(function(kv) { return [kv[0], String(kv[1])]; }))\n .filter(function(r) { return r[1] !== null && r[1] !== undefined && r[1] !== ''; });\n body.innerHTML = rows.map(function(r) {\n return '<div class=\"meta-row\"><div class=\"meta-label\">' + mEsc(String(r[0])) + '</div><div class=\"meta-value\">' + r[1] + '</div></div>';\n }).join('');\n var related = mLocalConns.filter(function(cn) { return cn.sourceAssetId === asset.id || cn.targetAssetId === asset.id; });\n if (related.length > 0) {\n body.innerHTML += '<div class=\"meta-row\"><div class=\"meta-label\">Connections (' + related.length + ')</div><div>' +\n related.map(function(cn) {\n var oid = cn.sourceAssetId === asset.id ? cn.targetAssetId : cn.sourceAssetId;\n var o = mapAssetIndex.get(oid);\n return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">' + (o ? mEsc(o.name) : oid) + '</div>';\n }).join('') + '</div></div>';\n }\n document.getElementById('map-detail').classList.add('open');\n}\ndocument.getElementById('md-close').addEventListener('click', function() {\n document.getElementById('map-detail').classList.remove('open'); mSelectedId = null; drawMap();\n});\n\n// ── Map toolbar ──────────────────────────────────────────────────────────────\ndocument.getElementById('btn-labels').addEventListener('click', function() {\n mShowLabels = !mShowLabels; this.classList.toggle('active', mShowLabels); drawMap();\n});\ndocument.getElementById('btn-all-labels').addEventListener('click', function() {\n mShowAllLabels = !mShowAllLabels; this.classList.toggle('active', mShowAllLabels);\n if (mShowAllLabels && !mShowLabels) { mShowLabels = true; document.getElementById('btn-labels').classList.add('active'); }\n drawMap();\n});\ndocument.getElementById('btn-quality').addEventListener('click', function() {\n mShowQuality = !mShowQuality; this.classList.toggle('active', mShowQuality); drawMap();\n});\ndocument.getElementById('btn-connect').addEventListener('click', function() {\n mConnectMode = !mConnectMode; mConnectFirst = null;\n this.classList.toggle('active', mConnectMode);\n mapWrap.classList.toggle('connecting', mConnectMode);\n document.getElementById('map-connect-hint').style.display = mConnectMode ? 'block' : 'none'; drawMap();\n});\n\n// Map keyboard\nmapWrap.addEventListener('keydown', function(e) {\n if (e.key === 'ArrowLeft') { mvx += 40; drawMap(); }\n else if (e.key === 'ArrowRight') { mvx -= 40; drawMap(); }\n else if (e.key === 'ArrowUp') { mvy += 40; drawMap(); }\n else if (e.key === 'ArrowDown') { mvy -= 40; drawMap(); }\n else if (e.key === '+' || e.key === '=') mApplyZoom(1.2, mW / 2, mH / 2);\n else if (e.key === '-') mApplyZoom(1 / 1.2, mW / 2, mH / 2);\n else if (e.key === 'Escape') {\n mSelectedId = null; document.getElementById('map-detail').classList.remove('open');\n if (mConnectMode) { mConnectMode = false; mConnectFirst = null; mapWrap.classList.remove('connecting'); document.getElementById('map-connect-hint').style.display = 'none'; document.getElementById('btn-connect').classList.remove('active'); }\n drawMap();\n }\n});\n\n// Map init\nresizeMap(); mapFitToView();\ndocument.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\ndrawMap();\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// TOPOLOGY VIEW (lazy init)\n// ═══════════════════════════════════════════════════════════════════════════════\nvar TOPO = ${graphData};\n\nvar TYPE_COLORS = {\n host:'#4a9eff',database_server:'#ff6b6b',database:'#ff8c42',\n web_service:'#6bcb77',api_endpoint:'#4d96ff',cache_server:'#ffd93d',\n message_broker:'#c77dff',queue:'#e0aaff',topic:'#9d4edd',\n container:'#48cae4',pod:'#00b4d8',k8s_cluster:'#0077b6',\n config_file:'#adb5bd',saas_tool:'#c084fc',table:'#f97316',unknown:'#6c757d'\n};\nvar LAYER_COLORS = { saas:'#c084fc',web:'#6bcb77',data:'#ff6b6b',messaging:'#c77dff',infra:'#4a9eff',config:'#adb5bd',other:'#6c757d' };\nvar LAYER_NAMES = { saas:'SaaS Tools',web:'Web / API',data:'Data Layer',messaging:'Messaging',infra:'Infrastructure',config:'Config',other:'Other' };\n\nvar topoSelectedId = null;\n\nfunction setTopoSearch(q) {\n var el = document.getElementById('topo-search');\n if (el) { el.value = q; buildTopoList(q); }\n}\n\nfunction buildTopoList(filter) {\n var listEl = document.getElementById('topo-list');\n var q = (filter || '').toLowerCase();\n listEl.innerHTML = '';\n var sorted = TOPO.nodes.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });\n for (var i = 0; i < sorted.length; i++) {\n var d = sorted[i];\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n var item = document.createElement('div');\n item.className = 'topo-item' + (d.id === topoSelectedId ? ' active' : '');\n item.dataset.id = d.id;\n var color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = '<span class=\"topo-dot\" style=\"background:' + color + '\"></span>' +\n '<span class=\"topo-name\" title=\"' + d.id + '\">' + d.name + '</span>' +\n '<span class=\"topo-type\">' + d.type.replace(/_/g, ' ') + '</span>';\n (function(dd) { item.onclick = function() { selectTopoNode(dd); focusTopoNode(dd); }; })(d);\n listEl.appendChild(item);\n }\n}\n\ndocument.getElementById('topo-search').addEventListener('input', function(e) { buildTopoList(e.target.value); });\n\nvar topoSidebar = document.getElementById('topo-sidebar');\n\nfunction selectTopoNode(d) {\n topoSelectedId = d.id;\n buildTopoList(document.getElementById('topo-search').value);\n showTopoNode(d);\n if (typeof d3 !== 'undefined') d3.selectAll('.node-hex').classed('selected', function(nd) { return nd.id === d.id; });\n}\n\nfunction showTopoNode(d) {\n var c = TYPE_COLORS[d.type] || '#aaa';\n var confPct = Math.round(d.confidence * 100);\n var tags = (d.tags || []).map(function(t) { return '<span class=\"tag\">' + t + '</span>'; }).join('');\n var metaRows = Object.entries(d.metadata || {})\n .filter(function(kv) { return kv[1] !== null && kv[1] !== undefined && String(kv[1]).length > 0; })\n .map(function(kv) { return '<tr><td>' + kv[0] + '</td><td>' + JSON.stringify(kv[1]) + '</td></tr>'; }).join('');\n var related = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) === d.id || (l.target.id || l.target) === d.id;\n });\n var edgeItems = related.map(function(l) {\n var isOut = (l.source.id || l.source) === d.id;\n var other = isOut ? (l.target.id || l.target) : (l.source.id || l.source);\n return '<div class=\"edge-item\">' + (isOut ? '\\\\u2192' : '\\\\u2190') + ' <span>' + other + '</span> <small>[' + l.relationship + ']</small></div>';\n }).join('');\n\n topoSidebar.innerHTML =\n '<h2>' + d.name + '</h2>' +\n '<table class=\"meta-table\">' +\n '<tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">' + d.id + '</td></tr>' +\n '<tr><td>Type</td><td><span style=\"color:' + c + '\">' + d.type + '</span></td></tr>' +\n '<tr><td>Layer</td><td>' + d.layer + '</td></tr>' +\n '<tr><td>Confidence</td><td>' + confPct + '% <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:' + confPct + '%;background:' + c + '\"></div></div></td></tr>' +\n '<tr><td>Via</td><td>' + (d.discoveredVia || '\\\\u2014') + '</td></tr>' +\n '<tr><td>Timestamp</td><td>' + (d.discoveredAt ? d.discoveredAt.substring(0, 19).replace('T', ' ') : '\\\\u2014') + '</td></tr>' +\n (tags ? '<tr><td>Tags</td><td>' + tags + '</td></tr>' : '') +\n metaRows + '</table>' +\n (related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>' + edgeItems + '</div>' : '') +\n '<div style=\"margin-top:14px\"><button class=\"export-btn\" style=\"width:100%\" onclick=\"deleteTopoNode(\\\\'' + d.id.replace(/'/g, \"\\\\\\\\'\") + '\\\\')\">Delete node</button></div>';\n}\n\nfunction deleteTopoNode(id) {\n var idx = TOPO.nodes.findIndex(function(n) { return n.id === id; });\n if (idx === -1) return;\n TOPO.nodes.splice(idx, 1);\n TOPO.links = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) !== id && (l.target.id || l.target) !== id;\n });\n topoSelectedId = null;\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n if (typeof rebuildTopoGraph === 'function') rebuildTopoGraph();\n buildTopoList(document.getElementById('topo-search').value);\n}\n\nfunction initTopology() {\n if (typeof d3 === 'undefined') return;\n\n var svgEl = d3.select('#topo-graph svg');\n var graphDiv = document.getElementById('topo-graph');\n var gW = function() { return graphDiv.clientWidth; };\n var gH = function() { return graphDiv.clientHeight; };\n var g = svgEl.append('g');\n\n svgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\n var currentZoom = 1;\n var zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', function(e) {\n g.attr('transform', e.transform); currentZoom = e.transform.k; updateTopoLOD(currentZoom);\n });\n svgEl.call(zoomBehavior);\n\n // Layer filters\n var layers = Array.from(new Set(TOPO.nodes.map(function(d) { return d.layer; })));\n var layerVisible = {};\n layers.forEach(function(l) { layerVisible[l] = true; });\n\n var toolbarEl = document.getElementById('topo-toolbar');\n layers.forEach(function(layer) {\n var btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = '<span class=\"filter-dot\" style=\"background:' + (LAYER_COLORS[layer] || '#666') + '\"></span>' + (LAYER_NAMES[layer] || layer);\n btn.onclick = function() { layerVisible[layer] = !layerVisible[layer]; btn.classList.toggle('off', !layerVisible[layer]); updateTopoVisibility(); };\n toolbarEl.appendChild(btn);\n });\n\n // JGF export button\n var jgfBtn = document.createElement('button');\n jgfBtn.className = 'export-btn'; jgfBtn.textContent = '\\\\u2193 JGF'; jgfBtn.title = 'Export JSON Graph Format';\n jgfBtn.onclick = function() {\n var jgf = { graph: { directed: true, type: 'cartography', label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(TOPO.nodes.map(function(n) { return [n.id, { label: n.name, metadata: { type: n.type, layer: n.layer, confidence: n.confidence, discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt, tags: n.tags } }]; })),\n edges: TOPO.links.map(function(l) { return { source: l.source.id || l.source, target: l.target.id || l.target, relation: l.relationship, metadata: { confidence: l.confidence, evidence: l.evidence } }; })\n }};\n var blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n var url = URL.createObjectURL(blob);\n var a = document.createElement('a'); a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n };\n toolbarEl.appendChild(jgfBtn);\n\n // Hex helpers\n var T_HEX = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\n function tHexSize(d) { return T_HEX[d.type] || T_HEX.default; }\n function tHexPath(size) {\n var pts = [];\n for (var i = 0; i < 6; i++) {\n var angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(function(p) { return p.join(','); }).join('L') + 'Z';\n }\n\n // Cluster force\n function clusterForce(alpha) {\n var centroids = {}, counts = {};\n TOPO.nodes.forEach(function(d) {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0; centroids[d.layer].y += d.y || 0; counts[d.layer]++;\n });\n for (var l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n var strength = alpha * 0.15;\n TOPO.nodes.forEach(function(d) {\n var cn = centroids[d.layer];\n if (cn) { d.vx += (cn.x - d.x) * strength; d.vy += (cn.y - d.y) * strength; }\n });\n }\n\n // Hulls\n var hullGroup = g.append('g').attr('class', 'hulls');\n var hullPaths = {}, hullLabels = {};\n layers.forEach(function(layer) {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n });\n\n function updateHulls() {\n layers.forEach(function(layer) {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n var pts = TOPO.nodes.filter(function(d) { return d.layer === layer && layerVisible[d.layer]; }).map(function(d) { return [d.x, d.y]; });\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n var hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n var cx = d3.mean(hull, function(p) { return p[0]; });\n var cy = d3.mean(hull, function(p) { return p[1]; });\n var padded = hull.map(function(p) {\n var dx = p[0] - cx, dy = p[1] - cy;\n var len = Math.sqrt(dx * dx + dy * dy) || 1;\n return [p[0] + dx / len * 40, p[1] + dy / len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, function(p) { return Math.abs(p[1] - cy); }) - 30);\n });\n }\n\n // Graph\n var linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\n var linkGroup = g.append('g');\n var nodeGroup = g.append('g');\n\n function focusTopoNode(d) {\n if (!d.x || !d.y) return;\n var w = gW(), h = gH();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n }\n window.focusTopoNode = focusTopoNode;\n\n function rebuildTopoGraph() {\n if (sim) sim.stop();\n\n linkSel = linkGroup.selectAll('line').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkSel.exit().remove();\n var linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', function(d) { return d.confidence < 0.6 ? '#2a2e35' : '#3d434b'; })\n .attr('stroke-dasharray', function(d) { return d.confidence < 0.6 ? '4 3' : null; })\n .attr('stroke-width', function(d) { return d.confidence < 0.6 ? 0.8 : 1.2; })\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(function(d) { return d.relationship + ' (' + Math.round(d.confidence * 100) + '%)\\\\n' + (d.evidence || ''); });\n\n linkLabelSel = linkGroup.selectAll('text').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel).text(function(d) { return d.relationship; });\n\n nodeSel = nodeGroup.selectAll('g').data(TOPO.nodes, function(d) { return d.id; });\n nodeSel.exit().remove();\n var nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', function(e, d) { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', function(e, d) { d.fx = e.x; d.fy = e.y; })\n .on('end', function(e, d) { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', function(e, d) { e.stopPropagation(); selectTopoNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', function(d) { return tHexPath(tHexSize(d)); })\n .attr('fill', function(d) { return TYPE_COLORS[d.type] || '#aaa'; })\n .attr('stroke', function(d) { var c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', function(d) { return 0.6 + d.confidence * 0.4; })\n .classed('selected', function(d) { return d.id === topoSelectedId; });\n nodeSel.select('title').text(function(d) { return d.name + ' (' + d.type + ')\\\\nconf: ' + Math.round(d.confidence * 100) + '%'; });\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', function(d) { return tHexSize(d) + 13; })\n .text(function(d) { return d.name.length > 20 ? d.name.substring(0, 18) + '\\\\u2026' : d.name; });\n\n sim = d3.forceSimulation(TOPO.nodes)\n .force('link', d3.forceLink(TOPO.links).id(function(d) { return d.id; }).distance(function(d) { return d.relationship === 'contains' ? 50 : 100; }).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(gW() / 2, gH() / 2))\n .force('collision', d3.forceCollide().radius(function(d) { return tHexSize(d) + 10; }))\n .force('cluster', clusterForce)\n .on('tick', function() {\n updateHulls();\n linkSel.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; })\n .attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; });\n linkLabelSel.attr('x', function(d) { return (d.source.x + d.target.x) / 2; })\n .attr('y', function(d) { return (d.source.y + d.target.y) / 2 - 4; });\n nodeSel.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });\n });\n }\n window.rebuildTopoGraph = rebuildTopoGraph;\n\n function updateTopoLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n }\n\n function updateTopoVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', function(d) { return layerVisible[d.layer] ? null : 'none'; });\n linkSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n }\n\n rebuildTopoGraph();\n buildTopoList();\n updateTopoLOD(1);\n\n svgEl.on('click', function() {\n topoSelectedId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildTopoList(document.getElementById('topo-search').value);\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n });\n}\n\n// Init topology node list (non-D3 part)\nbuildTopoList();\n<\\/script>\n</body>\n</html>`;\n}\n\n// ── JGF Export ────────────────────────────────────────────────────────────────\n\nexport function exportJGF(nodes: NodeRow[], edges: EdgeRow[]): string {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(nodes.map(n => [n.id, {\n label: n.name,\n metadata: {\n type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n },\n }])),\n edges: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relation: e.relationship,\n metadata: { confidence: e.confidence, evidence: e.evidence },\n })),\n },\n };\n return JSON.stringify(jgf, null, 2);\n}\n\n// ── Cost (FinOps) export (3.3) ────────────────────────────────────────────────\n\n/**\n * CSV-escape a field: quote when it contains a comma/quote/newline, and neutralize\n * spreadsheet formula injection by prefixing a leading `=`/`+`/`-`/`@` with a quote\n * (a hostile `owner`/`domain` from an imported CSV must not execute in Excel/Sheets).\n */\nfunction csvField(v: string | number): string {\n let s = String(v);\n if (/^[=+\\-@]/.test(s)) s = `'${s}`;\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n}\n\n/**\n * Cost rolled up by domain and owner as CSV — the FinOps export. One block per\n * scope; rows are bucketed by `(currency, period)` so mixed currencies are never\n * summed into one figure.\n */\nexport function exportCostCSV(summary: GraphSummary): string {\n const rows = ['scope,key,currency,period,total,nodes'];\n for (const c of summary.costByDomain) {\n rows.push(['domain', c.domain, c.currency, c.period, c.total, c.nodes].map(csvField).join(','));\n }\n for (const c of summary.costByOwner) {\n rows.push(['owner', c.owner, c.currency, c.period, c.total, c.nodes].map(csvField).join(','));\n }\n return rows.join('\\n') + '\\n';\n}\n\n/** The same cost rollup as JSON for programmatic consumers. */\nexport function exportCostSummary(summary: GraphSummary): string {\n return JSON.stringify({\n costByDomain: summary.costByDomain,\n costByOwner: summary.costByOwner,\n costCoverage: summary.costCoverage,\n }, null, 2);\n}\n\n// ── Compliance report export (3.4) ────────────────────────────────────────────\n\nconst SEVERITY_RANK: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\n/**\n * Render a `ComplianceReport` as `json`, `markdown`, or `mermaid`. The Mermaid form\n * reuses the diff exporter's severity-coloured `classDef` pattern: one node per gap\n * coloured by severity, with a `\"✓ No compliance gaps\"` empty state.\n */\nexport function exportComplianceReport(report: ComplianceReport, format: 'json' | 'markdown' | 'mermaid'): string {\n if (format === 'json') return JSON.stringify(report, null, 2);\n\n if (format === 'markdown') {\n const scoreStr = report.score === null ? 'n/a' : `${report.score}/100`;\n const out: string[] = [\n `# Compliance — ${report.rulesetName} v${report.rulesetVersion}`,\n ``,\n `**Status:** ${report.status.toUpperCase()} · **Score:** ${scoreStr}`,\n ``,\n `| Controls | Count |`, `|----------|-------|`,\n `| Passed | ${report.totals.passed} |`,\n `| Failed | ${report.totals.failed} |`,\n `| Not applicable | ${report.totals.notApplicable} |`,\n `| Total | ${report.totals.rules} |`,\n ``,\n `| Severity | Failed | Passed |`, `|----------|--------|--------|`,\n ...(['critical', 'high', 'medium', 'low'] as const).map(\n (s) => `| ${s} | ${report.bySeverity[s].failed} | ${report.bySeverity[s].passed} |`,\n ),\n ];\n if (report.gaps.length === 0) {\n out.push(``, `✓ No compliance gaps.`);\n } else {\n out.push(``, `## Gaps`);\n for (const g of [...report.gaps].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])) {\n out.push(``, `### [${g.severity}] ${g.control} — ${g.title}`, ...g.nodeIds.map((id) => `- \\`${id}\\``));\n }\n }\n return out.join('\\n');\n }\n\n // mermaid\n const lines: string[] = [\n 'graph TB',\n ' classDef critical fill:#7f1d1d,stroke:#ef4444,color:#fff;',\n ' classDef high fill:#7c2d12,stroke:#f97316,color:#fff;',\n ' classDef medium fill:#713f12,stroke:#eab308,color:#fff;',\n ' classDef low fill:#1e3a5f,stroke:#3b82f6,color:#fff;',\n ];\n if (report.gaps.length === 0) {\n lines.push(' ok[\"✓ No compliance gaps\"]');\n return lines.join('\\n');\n }\n // Strip Mermaid-breaking chars (quote/bracket/newline) defensively, even though\n // titles come from trusted bundled rulesets. Order by severity for parity with md.\n const mmSafe = (s: string): string => s.replace(/[\"\\]\\r\\n]/g, \"'\");\n let i = 0;\n for (const g of [...report.gaps].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])) {\n const gid = `g${i++}`;\n lines.push(` ${gid}[\"${mmSafe(g.control)}: ${mmSafe(g.title)} (${g.nodeIds.length})\"]:::${g.severity}`);\n }\n return lines.join('\\n');\n}\n\n// ── exportAll ─────────────────────────────────────────────────────────────────\n\nexport function exportAll(\n db: CartographyDB,\n sessionId: string,\n outputDir: string,\n formats: string[] = ['mermaid', 'json', 'yaml', 'html', 'map', 'discovery'],\n): void {\n mkdirSync(outputDir, { recursive: true });\n\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n\n // Always export JGF to well-known path (overwritten each run)\n const jgfPath = join(outputDir, 'cartography-graph.jgf.json');\n writeFileSync(jgfPath, exportJGF(nodes, edges));\n\n if (formats.includes('mermaid')) {\n writeFileSync(join(outputDir, 'topology.mermaid'), generateTopologyMermaid(nodes, edges));\n writeFileSync(join(outputDir, 'dependencies.mermaid'), generateDependencyMermaid(nodes, edges));\n }\n\n if (formats.includes('json')) {\n writeFileSync(join(outputDir, 'catalog.json'), exportJSON(db, sessionId));\n }\n\n if (formats.includes('yaml')) {\n writeFileSync(join(outputDir, 'catalog-info.yaml'), exportBackstageYAML(nodes, edges));\n }\n\n if (formats.includes('html') || formats.includes('map') || formats.includes('discovery')) {\n writeFileSync(join(outputDir, 'discovery.html'), exportDiscoveryApp(nodes, edges));\n }\n\n if (formats.includes('cost')) {\n const summary = db.getGraphSummary(sessionId);\n writeFileSync(join(outputDir, 'cost-by-domain.csv'), exportCostCSV(summary));\n writeFileSync(join(outputDir, 'cost-summary.json'), exportCostSummary(summary));\n }\n}\n","/**\n * Hex Grid Engine — flat-top axial coordinate system.\n * Reference: https://www.redblobgames.com/grids/hexagons/\n */\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AxialCoord {\n q: number;\n r: number;\n}\n\nexport interface PixelCoord {\n x: number;\n y: number;\n}\n\n// ── Geometry ─────────────────────────────────────────────────────────────────\n\n/**\n * Convert axial hex coordinates to pixel coordinates (flat-top).\n */\nexport function hexToPixel(q: number, r: number, size: number): PixelCoord {\n const x = size * (3 / 2 * q);\n const y = size * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r);\n return { x, y };\n}\n\n/**\n * Convert pixel coordinates to nearest axial hex (flat-top).\n */\nexport function pixelToHex(x: number, y: number, size: number): AxialCoord {\n const q = (2 / 3 * x) / size;\n const r = (-1 / 3 * x + Math.sqrt(3) / 3 * y) / size;\n return hexRound(q, r);\n}\n\n/**\n * Round floating-point axial coordinates to the nearest hex.\n */\nexport function hexRound(q: number, r: number): AxialCoord {\n const s = -q - r;\n let rq = Math.round(q);\n let rr = Math.round(r);\n const rs = Math.round(s);\n const dq = Math.abs(rq - q);\n const dr = Math.abs(rr - r);\n const ds = Math.abs(rs - s);\n if (dq > dr && dq > ds) {\n rq = -rr - rs;\n } else if (dr > ds) {\n rr = -rq - rs;\n }\n // Normalize -0 to 0\n return { q: rq || 0, r: rr || 0 };\n}\n\n/**\n * Return the 6 pixel corners of a flat-top hexagon.\n */\nexport function hexCorners(cx: number, cy: number, size: number): PixelCoord[] {\n const corners: PixelCoord[] = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 180) * (60 * i); // flat-top: start at 0°\n corners.push({\n x: cx + size * Math.cos(angle),\n y: cy + size * Math.sin(angle),\n });\n }\n return corners;\n}\n\n// ── Neighbors & Distance ─────────────────────────────────────────────────────\n\nconst HEX_DIRECTIONS: AxialCoord[] = [\n { q: 1, r: 0 }, { q: 1, r: -1 }, { q: 0, r: -1 },\n { q: -1, r: 0 }, { q: -1, r: 1 }, { q: 0, r: 1 },\n];\n\nexport function hexNeighbors(q: number, r: number): AxialCoord[] {\n return HEX_DIRECTIONS.map(d => ({ q: q + d.q, r: r + d.r }));\n}\n\nexport function hexDistance(a: AxialCoord, b: AxialCoord): number {\n return (Math.abs(a.q - b.q) + Math.abs(a.q + a.r - b.q - b.r) + Math.abs(a.r - b.r)) / 2;\n}\n\n/**\n * Generate all hex coordinates on a given ring around center.\n */\nexport function hexRing(center: AxialCoord, radius: number): AxialCoord[] {\n if (radius === 0) return [{ q: center.q, r: center.r }];\n const results: AxialCoord[] = [];\n let hex = {\n q: center.q + HEX_DIRECTIONS[4].q * radius,\n r: center.r + HEX_DIRECTIONS[4].r * radius,\n };\n for (let side = 0; side < 6; side++) {\n for (let step = 0; step < radius; step++) {\n results.push({ q: hex.q, r: hex.r });\n hex = {\n q: hex.q + HEX_DIRECTIONS[side].q,\n r: hex.r + HEX_DIRECTIONS[side].r,\n };\n }\n }\n return results;\n}\n\n/**\n * Generate a spiral sequence of hex positions (ring 0, ring 1, ring 2, …).\n * Returns exactly `count` positions.\n */\nexport function hexSpiral(center: AxialCoord, count: number): AxialCoord[] {\n const positions: AxialCoord[] = [];\n let ring = 0;\n while (positions.length < count) {\n const ringPositions = hexRing(center, ring);\n for (const pos of ringPositions) {\n if (positions.length >= count) break;\n positions.push(pos);\n }\n ring++;\n }\n return positions;\n}\n","/**\n * Domain-based clustering and hex grid positioning.\n * Groups data assets by domain, assigns organic hex positions via spiral fill,\n * computes cluster centroids and assigns colors from the domain palette.\n */\n\nimport { hexSpiral, hexToPixel, hexDistance, type AxialCoord } from './hex.js';\nimport type { DataAsset, Cluster } from './types.js';\nimport { DOMAIN_COLORS, DOMAIN_PALETTE } from './types.js';\n\n// ── Color Assignment ──────────────────────────────────────────────────────────\n\n/**\n * Assign a deterministic color from the palette to a domain name.\n * Uses the predefined DOMAIN_COLORS map first, then falls back to the palette.\n */\nexport function assignColor(domain: string, allDomains: string[]): string {\n if (DOMAIN_COLORS[domain]) return DOMAIN_COLORS[domain];\n const idx = allDomains.indexOf(domain);\n return DOMAIN_PALETTE[idx % DOMAIN_PALETTE.length];\n}\n\n/**\n * Assign colors to all domains in the dataset.\n */\nexport function assignColors(domains: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n for (const d of domains) {\n result[d] = assignColor(d, domains);\n }\n return result;\n}\n\n/**\n * Generate a slightly lighter shade of a hex color string.\n */\nexport function shadeVariant(hex: string, amount: number): string {\n const num = parseInt(hex.replace('#', ''), 16);\n const r = Math.min(255, (num >> 16) + amount);\n const g = Math.min(255, ((num >> 8) & 0xff) + amount);\n const b = Math.min(255, (num & 0xff) + amount);\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;\n}\n\n// ── Grouping ──────────────────────────────────────────────────────────────────\n\n/**\n * Group assets by their `domain` field.\n */\nexport function groupByDomain(assets: DataAsset[]): Map<string, DataAsset[]> {\n const map = new Map<string, DataAsset[]>();\n for (const a of assets) {\n const d = a.domain || 'Other';\n if (!map.has(d)) map.set(d, []);\n map.get(d)!.push(a);\n }\n return map;\n}\n\n// ── Cluster Layout ────────────────────────────────────────────────────────────\n\nconst CLUSTER_GAP = 3; // min hex distance between cluster borders\n\n/**\n * Arrange domain clusters on the hex grid without overlap.\n * Places largest clusters first at the origin, subsequent clusters spiral outward.\n */\nexport function layoutClusters(\n groups: Map<string, DataAsset[]>,\n hexSize: number,\n): { clusters: Cluster[]; assets: DataAsset[] } {\n const allDomains = Array.from(groups.keys());\n const colors = assignColors(allDomains);\n\n // Sort domains by size descending (largest first → center)\n const sorted = Array.from(groups.entries())\n .sort((a, b) => b[1].length - a[1].length);\n\n const occupied = new Set<string>();\n const key = (q: number, r: number) => `${q},${r}`;\n\n const clusters: Cluster[] = [];\n const allAssets: DataAsset[] = [];\n\n for (const [domain, domainAssets] of sorted) {\n // Find a free origin for this cluster\n const origin = clusters.length === 0\n ? { q: 0, r: 0 }\n : findFreeOrigin(occupied, domainAssets.length, CLUSTER_GAP);\n\n // Pack assets in a spiral around the origin\n const positions = hexSpiral(origin, domainAssets.length);\n\n const assetIds: string[] = [];\n for (let i = 0; i < domainAssets.length; i++) {\n const asset = domainAssets[i];\n asset.position = positions[i];\n assetIds.push(asset.id);\n occupied.add(key(positions[i].q, positions[i].r));\n allAssets.push(asset);\n }\n\n const centroid = computeCentroid(positions, hexSize);\n\n clusters.push({\n id: `cluster:${domain}`,\n label: domain,\n domain,\n color: colors[domain],\n assetIds,\n centroid,\n });\n }\n\n return { clusters, assets: allAssets };\n}\n\n/**\n * Find a cluster origin that doesn't overlap any occupied hexes.\n */\nfunction findFreeOrigin(\n occupied: Set<string>,\n count: number,\n gap: number,\n): AxialCoord {\n const key = (q: number, r: number) => `${q},${r}`;\n\n // Pre-parse occupied coordinates to avoid repeated string splitting\n const parsedOccupied: AxialCoord[] = [];\n for (const oKey of occupied) {\n const [oq, or] = oKey.split(',').map(Number);\n parsedOccupied.push({ q: oq, r: or });\n }\n\n for (let searchRadius = 1; searchRadius < 100; searchRadius++) {\n const candidates = hexSpiral({ q: 0, r: 0 }, 1 + 6 * searchRadius * (searchRadius + 1) / 2);\n\n for (const candidate of candidates) {\n const testPositions = hexSpiral(candidate, count);\n let fits = true;\n\n for (const tp of testPositions) {\n if (occupied.has(key(tp.q, tp.r))) { fits = false; break; }\n\n for (const oc of parsedOccupied) {\n if (hexDistance(tp, oc) < gap) {\n fits = false;\n break;\n }\n }\n if (!fits) break;\n }\n\n if (fits) return candidate;\n }\n }\n\n return { q: occupied.size * 5, r: 0 };\n}\n\n// ── Centroid ──────────────────────────────────────────────────────────────────\n\nexport function computeCentroid(positions: AxialCoord[], hexSize: number): { x: number; y: number } {\n if (positions.length === 0) return { x: 0, y: 0 };\n let sx = 0, sy = 0;\n for (const { q, r } of positions) {\n const { x, y } = hexToPixel(q, r, hexSize);\n sx += x;\n sy += y;\n }\n return { x: sx / positions.length, y: sy / positions.length };\n}\n\n// ── Cluster Bounds ────────────────────────────────────────────────────────────\n\nexport function computeClusterBounds(\n assets: DataAsset[],\n hexSize: number,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n if (assets.length === 0) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const a of assets) {\n const { x, y } = hexToPixel(a.position.q, a.position.r, hexSize);\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n return { minX, minY, maxX, maxY };\n}\n","/**\n * Node-to-Asset Mapping.\n * Converts existing DiscoveryNode/Edge data into the CartographyMap data model.\n */\n\nimport type { NodeRow, EdgeRow, DataAsset, Connection, CartographyMapData } from './types.js';\nimport { layoutClusters, groupByDomain } from './cluster.js';\n\n// ── Domain Mapping ───────────────────────────────────────────────────────────\n\nconst TYPE_TO_DOMAIN: Record<string, string> = {\n database_server: 'Data Layer',\n database: 'Data Layer',\n table: 'Data Layer',\n cache_server: 'Data Layer',\n web_service: 'Web / API',\n api_endpoint: 'Web / API',\n message_broker: 'Messaging',\n queue: 'Messaging',\n topic: 'Messaging',\n host: 'Infrastructure',\n container: 'Infrastructure',\n pod: 'Infrastructure',\n k8s_cluster: 'Infrastructure',\n config_file: 'Infrastructure',\n saas_tool: 'SaaS Tools',\n};\n\n/**\n * Determine the domain for a node.\n * Priority: explicit node.domain > metadata.category > tag-based > type-based > \"Other\"\n */\nfunction resolveDomain(node: NodeRow): string {\n // 1. Explicit domain field\n if (node.domain) return node.domain;\n\n // 2. Metadata category\n const meta = node.metadata as Record<string, unknown>;\n if (typeof meta['category'] === 'string' && meta['category'].length > 0) {\n return meta['category'];\n }\n\n // 3. Tags — use first tag that looks like a domain\n for (const tag of node.tags ?? []) {\n if (tag.length > 2 && tag[0] === tag[0].toUpperCase()) {\n return tag;\n }\n }\n\n // 4. Type-based mapping\n return TYPE_TO_DOMAIN[node.type] ?? 'Other';\n}\n\n// ── Conversion ───────────────────────────────────────────────────────────────\n\n/**\n * Convert NodeRow[] to DataAsset[].\n */\nexport function nodesToAssets(nodes: NodeRow[]): DataAsset[] {\n return nodes.map(n => ({\n id: n.id,\n name: n.name,\n domain: resolveDomain(n),\n subDomain: n.subDomain,\n qualityScore: n.qualityScore ?? Math.round(n.confidence * 100),\n metadata: n.metadata ?? {},\n position: { q: 0, r: 0 }, // will be assigned by layoutClusters\n }));\n}\n\n/**\n * Convert EdgeRow[] to Connection[].\n */\nexport function edgesToConnections(edges: EdgeRow[]): Connection[] {\n return edges.map(e => ({\n id: e.id,\n sourceAssetId: e.sourceId,\n targetAssetId: e.targetId,\n type: e.relationship,\n }));\n}\n\n// ── Full Pipeline ─────────────────────────────────────────────────────────────\n\nconst HEX_SIZE = 24;\n\n/**\n * Build a complete CartographyMapData from raw nodes and edges.\n */\nexport function buildMapData(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): CartographyMapData {\n const rawAssets = nodesToAssets(nodes);\n const connections = edgesToConnections(edges);\n\n if (rawAssets.length === 0) {\n return {\n assets: [],\n clusters: [],\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n }\n\n const groups = groupByDomain(rawAssets);\n const { clusters, assets } = layoutClusters(groups, HEX_SIZE);\n\n return {\n assets,\n clusters,\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n}\n","/**\n * Plain-text compliance report formatter (3.4). Colour-free so it lives outside\n * `cli.ts` (which owns the colour helpers and is excluded from coverage).\n */\n\nimport type { ComplianceReport } from './types.js';\n\nconst NODE_CAP = 50;\n\n/** Render a `ComplianceReport` as a plain-text summary with `✓`/`✗` markers. */\nexport function formatComplianceText(report: ComplianceReport): string {\n const scoreStr = report.score === null ? 'n/a' : `${report.score}/100`;\n const lines: string[] = [\n `Compliance: ${report.rulesetName} v${report.rulesetVersion} — ${report.status.toUpperCase()} (score ${scoreStr})`,\n `Controls: ${report.totals.passed} passed, ${report.totals.failed} failed, ${report.totals.notApplicable} n/a (of ${report.totals.rules})`,\n '',\n 'By severity (failed/passed):',\n ...(['critical', 'high', 'medium', 'low'] as const).map(\n (s) => ` - ${s}: ${report.bySeverity[s].failed} failed / ${report.bySeverity[s].passed} passed`,\n ),\n ];\n\n if (report.gaps.length === 0) {\n lines.push('', '✓ No compliance gaps.');\n return lines.join('\\n');\n }\n\n lines.push('', `Gaps (${report.gaps.length}):`);\n for (const g of report.gaps) {\n lines.push(` ✗ [${g.severity}] ${g.control} — ${g.title}`);\n const shown = g.nodeIds.slice(0, NODE_CAP);\n for (const id of shown) lines.push(` ${id}`);\n if (g.nodeIds.length > NODE_CAP) lines.push(` … +${g.nodeIds.length - NODE_CAP} more`);\n }\n return lines.join('\\n');\n}\n","/**\n * Config-file layer (2.5).\n *\n * Reads and validates a JSON `cartography.config.json` file and resolves it into\n * a fully-populated {@link CartographyConfig}. The reader is deliberately general:\n * WS 2.11 (central-org sync) consumes this same module and extends the file schema\n * (`ConfigFileSchema`) with its own block, so all file → config merging logic lives\n * here in one place.\n *\n * Security: untrusted file input is parsed with `JSON.parse` (no `eval`, no dynamic\n * `require`) and validated with a `.strict()` Zod schema that rejects unknown keys.\n * File contents are never executed; values flow only into the existing sanitized\n * config/discovery sinks.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CartographyConfig, CentralDbConfig } from './types.js';\nimport { ConfigFileSchema, centralDbFromEnv, defaultConfig } from './types.js';\n\n/** Raised when a config file cannot be read, parsed, or validated. */\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\n/**\n * Read and validate a JSON config file at `path`, returning a fully-resolved\n * {@link CartographyConfig}. Merges the file's `schedule`/`entryPoints`/`dbPath`/\n * `organization` into `defaultConfig` so every existing config invariant (e.g.\n * `agentModel === models.lead`) is preserved.\n *\n * Precedence for the shared `entryPoints`/`dbPath`: a value inside the `schedule`\n * block wins over the same file-level key (the schedule block is the more specific\n * intent for a scheduled run).\n *\n * @throws {ConfigError} when the file is missing/unreadable, the JSON is malformed,\n * or the content fails schema validation (including unknown keys via `.strict()`).\n */\nexport function loadConfig(path: string): CartographyConfig {\n const file = readConfigFile(path);\n\n const overrides: Partial<CartographyConfig> = {};\n if (file.organization) overrides.organization = file.organization;\n\n const entryPoints = file.schedule?.entryPoints ?? file.entryPoints;\n if (entryPoints) overrides.entryPoints = [...entryPoints];\n\n const dbPath = file.schedule?.dbPath ?? file.dbPath;\n if (dbPath) overrides.dbPath = dbPath;\n\n if (file.schedule) overrides.schedule = file.schedule;\n\n // 2.11 central-DB sync. Precedence (low→high): file `centralDb` < env\n // (`CARTOGRAPHY_CENTRAL_*`) < anything `defaultConfig` later layers. We merge the\n // file block under env here (env wins per field) and pass the result as the\n // explicit `centralDb` override; `defaultConfig` then validates it once. A file\n // `url` + env `token` compose into a valid block; an invalid one is dropped there.\n if (file.centralDb) {\n // `defaultConfig` re-validates the assembled block; pass the (possibly partial)\n // merge as a centralDb override. The cast is sound because resolveCentralDb\n // safeParses before it lands on the resolved config.\n const merged: Partial<CentralDbConfig> = { ...file.centralDb, ...centralDbFromEnv() };\n overrides.centralDb = merged as CentralDbConfig;\n }\n\n return defaultConfig(overrides);\n}\n\n/**\n * Lower-level reader: parse + validate a config file into its typed shape without\n * resolving it against `defaultConfig`. Useful for callers (e.g. WS 2.11) that need\n * the raw file shape rather than a merged runtime config.\n *\n * @throws {ConfigError} on missing file, malformed JSON, or schema-validation failure.\n */\nexport function readConfigFile(path: string): import('./types.js').ConfigFile {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n throw new ConfigError(\n `Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch (err) {\n throw new ConfigError(\n `Invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const parsed = ConfigFileSchema.safeParse(json);\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('; ');\n throw new ConfigError(`Invalid config in ${path}: ${detail}`);\n }\n return parsed.data;\n}\n","/**\n * Scheduled discovery (2.5).\n *\n * A thin, dependency-free cron driver over the existing read-only discovery and\n * drift machinery. Two pure pieces — {@link parseCron} and {@link nextRun} — own\n * a minimal 5-field cron grammar (UTC, no NPM dependency); {@link runOnce} runs a\n * single deterministic local scan and returns the topology drift relative to the\n * prior run. `runOnce` never invokes the Claude/agent loop and needs no API key.\n *\n * Reuse, not reimplementation: discovery is `runLocalDiscovery` and diffing is the\n * pure `diffTopology` engine (surfaced here via the 2.1 incremental `mode:'update'`\n * rescan, which already returns the delta). This module adds scheduling + per-run\n * persistence around them.\n */\n\nimport type { CartographyConfig } from './types.js';\nimport type { TopologyDelta } from './diff.js';\nimport type { CartographyDB } from './db.js';\nimport { runLocalDiscovery } from './discovery/local.js';\nimport { diffTopology } from './diff.js';\nimport { logInfo } from './logger.js';\n\n/** Parsed allowed-value sets for each cron field (all UTC). */\nexport interface CronFields {\n /** 0–59 */\n minute: Set<number>;\n /** 0–23 */\n hour: Set<number>;\n /** 1–31 */\n dom: Set<number>;\n /** 1–12 */\n month: Set<number>;\n /** 0–6 (Sunday = 0) */\n dow: Set<number>;\n}\n\ninterface FieldSpec {\n name: keyof CronFields;\n min: number;\n max: number;\n}\n\nconst FIELD_SPECS: readonly FieldSpec[] = [\n { name: 'minute', min: 0, max: 59 },\n { name: 'hour', min: 0, max: 23 },\n { name: 'dom', min: 1, max: 31 },\n { name: 'month', min: 1, max: 12 },\n { name: 'dow', min: 0, max: 7 }, // 7 and 0 both mean Sunday; normalized to 0 below\n];\n\n/** Parse one cron field into the set of integers it matches within [min, max]. */\nfunction parseField(raw: string, spec: FieldSpec): Set<number> {\n const out = new Set<number>();\n const add = (n: number): void => {\n if (!Number.isInteger(n) || n < spec.min || n > spec.max) {\n throw new RangeError(`Invalid value \"${n}\" in cron field \"${spec.name}\" (allowed ${spec.min}-${spec.max})`);\n }\n // Day-of-week 7 normalizes to 0 (both Sunday).\n out.add(spec.name === 'dow' && n === 7 ? 0 : n);\n };\n\n for (const part of raw.split(',')) {\n if (part === '') {\n throw new RangeError(`Empty term in cron field \"${spec.name}\"`);\n }\n // Split an optional step: \"<range>/<step>\".\n const [rangePart, stepPart, ...rest] = part.split('/');\n if (rest.length > 0) {\n throw new RangeError(`Malformed step in cron field \"${spec.name}\": \"${part}\"`);\n }\n let step = 1;\n if (stepPart !== undefined) {\n step = Number(stepPart);\n if (!Number.isInteger(step) || step < 1) {\n throw new RangeError(`Invalid step \"${stepPart}\" in cron field \"${spec.name}\"`);\n }\n }\n\n let lo: number;\n let hi: number;\n if (rangePart === '*') {\n lo = spec.min;\n hi = spec.max;\n } else if (rangePart.includes('-')) {\n const [a, b, ...extra] = rangePart.split('-');\n if (extra.length > 0) {\n throw new RangeError(`Malformed range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n lo = Number(a);\n hi = Number(b);\n if (!Number.isInteger(lo) || !Number.isInteger(hi)) {\n throw new RangeError(`Non-numeric range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n if (lo > hi) {\n throw new RangeError(`Descending range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n } else {\n const n = Number(rangePart);\n if (!Number.isInteger(n)) {\n throw new RangeError(`Non-numeric value in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n // A bare value with a step (\"5/10\") steps from the value to the field max.\n lo = n;\n hi = stepPart !== undefined ? spec.max : n;\n }\n\n for (let v = lo; v <= hi; v += step) add(v);\n }\n\n if (out.size === 0) {\n throw new RangeError(`Cron field \"${spec.name}\" matched no values`);\n }\n return out;\n}\n\n/**\n * Parse a 5-field cron expression (`minute hour dom month dow`, UTC) into its\n * matching value sets. Grammar per field: star, star-slash-n, `a`, `a-b`,\n * `a-b`-slash-n, `a`-slash-n, and comma lists of those. Day-of-week `7`\n * normalizes to `0` (Sunday).\n *\n * @throws {RangeError} when the expression is not exactly 5 fields, or any field\n * is out of range / non-numeric / malformed.\n */\nexport function parseCron(expr: string): CronFields {\n const fields = expr.trim().split(/\\s+/);\n if (fields.length !== 5) {\n throw new RangeError(`Cron expression must have 5 fields (got ${fields.length}): \"${expr}\"`);\n }\n const [minute, hour, dom, month, dow] = FIELD_SPECS.map((spec, i) => parseField(fields[i]!, spec));\n return { minute: minute!, hour: hour!, dom: dom!, month: month!, dow: dow! };\n}\n\n/** True when `date` (UTC) satisfies all cron fields, honoring dom/dow OR-semantics. */\nfunction matches(fields: CronFields, date: Date): boolean {\n if (!fields.minute.has(date.getUTCMinutes())) return false;\n if (!fields.hour.has(date.getUTCHours())) return false;\n if (!fields.month.has(date.getUTCMonth() + 1)) return false;\n\n const domRestricted = fields.dom.size !== 31;\n const dowRestricted = fields.dow.size !== 7;\n const domOk = fields.dom.has(date.getUTCDate());\n const dowOk = fields.dow.has(date.getUTCDay());\n\n // Standard cron OR-semantics: when both dom and dow are restricted, either may\n // match; when only one is restricted, only that one constrains.\n if (domRestricted && dowRestricted) return domOk || dowOk;\n if (domRestricted) return domOk;\n if (dowRestricted) return dowOk;\n return true;\n}\n\n/** Upper bound on the forward search: ~4 years of minutes (covers Feb-29 schedules). */\nconst MAX_SEARCH_MINUTES = 4 * 366 * 24 * 60;\n\n/**\n * The earliest scheduled instant strictly after `after` (UTC, second/ms truncated).\n * Deterministic and pure: same `expr` + `after` always yields the same Date.\n *\n * @throws {RangeError} when `expr` is invalid, or no match is found within ~4 years.\n */\nexport function nextRun(expr: string, after: Date): Date {\n const fields = parseCron(expr);\n // Truncate to the minute and step forward at least one minute.\n const cursor = new Date(after.getTime());\n cursor.setUTCSeconds(0, 0);\n cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);\n\n for (let i = 0; i < MAX_SEARCH_MINUTES; i++) {\n if (matches(fields, cursor)) return new Date(cursor.getTime());\n cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);\n }\n throw new RangeError(`No cron match for \"${expr}\" within ~4 years after ${after.toISOString()}`);\n}\n\nexport interface ScheduledRunResult {\n /** The session this run scanned (reused in place across runs). */\n sessionId: string;\n /** The prior session used as the diff base, or `undefined` on the first run. */\n baseSessionId?: string;\n /** Topology drift this run observed. */\n delta: TopologyDelta;\n /** Node count after the scan. */\n nodes: number;\n /** Edge count after the scan. */\n edges: number;\n /** Ids of the scanners that ran. */\n scanners: string[];\n}\n\n/**\n * Run one deterministic local-discovery pass and return its topology drift.\n *\n * Reuses the most recent prior `discover` session for this config's tenant and\n * rescans it in place via the 2.1 incremental `mode:'update'` path (same session\n * id, prunes vanished entities, stamps `last_scanned_at`), which already returns\n * the delta from the pure `diffTopology` engine. When no prior session exists, it\n * creates a fresh session, replace-scans it, and reports the whole topology as\n * `added` (diffed against an empty base).\n *\n * Read-only and API-key-free. Does **not** persist the drift run — the caller does\n * (so tests can inspect the delta first). All progress goes to stderr.\n */\nexport async function runOnce(cfg: CartographyConfig, db: CartographyDB): Promise<ScheduledRunResult> {\n const prior = db.getLatestSession('discover');\n\n if (prior) {\n // Incremental in-place rescan (2.1): the delta is computed and returned by\n // runLocalDiscovery itself, so no second diff pass is needed.\n const r = await runLocalDiscovery(db, prior.id, {\n hint: cfg.entryPoints.join(','),\n plugins: cfg.plugins,\n mode: 'update',\n onProgress: (line) => logInfo(`scan: ${line}`),\n });\n const delta = r.delta ?? diffTopology({ nodes: [], edges: [] }, { nodes: [], edges: [] });\n logInfo('scheduled run complete', { sessionId: prior.id, base: prior.id, ...delta.summary });\n return { sessionId: prior.id, baseSessionId: prior.id, delta, nodes: r.nodes, edges: r.edges, scanners: r.scanners };\n }\n\n // First run: no prior topology — create a session, scan it, diff against empty.\n const sessionId = db.createSession('discover', cfg);\n try {\n const r = await runLocalDiscovery(db, sessionId, {\n hint: cfg.entryPoints.join(','),\n plugins: cfg.plugins,\n mode: 'replace',\n onProgress: (line) => logInfo(`scan: ${line}`),\n });\n const current = { nodes: db.getNodes(sessionId), edges: db.getEdges(sessionId) };\n const delta = diffTopology({ nodes: [], edges: [] }, current);\n logInfo('scheduled run complete', { sessionId, base: null, ...delta.summary });\n return { sessionId, baseSessionId: undefined, delta, nodes: r.nodes, edges: r.edges, scanners: r.scanners };\n } finally {\n db.endSession(sessionId);\n }\n}\n","/**\n * Stable content hashing for the central-DB share queue (2.11).\n *\n * The hash is computed over the *policy-transformed* payload (what `previewShare`\n * produces — already anonymized/dropped), so two scans that yield the same outgoing\n * bytes map to the same `pending_shares` row (idempotent enqueue) and the same\n * server-side dedup key. It is deterministic via `stableStringify` (recursively\n * key-sorted JSON), so key ordering never perturbs the hash.\n *\n * Zero new dependencies — `node:crypto` + the existing `stableStringify`.\n */\n\nimport { createHash } from 'node:crypto';\nimport { stableStringify } from '../diff.js';\n\n/** A node or edge projected to its outgoing (already-transformed) shape. */\nexport type ShareKind = 'node' | 'edge';\n\n/**\n * sha256 (hex) over the canonical JSON of `{ kind, payload }`. `payload` is the\n * transformed projection — for `anonymized`/`none` items it is the anonymized form,\n * never the raw record — so the hash is stable across scans and identical to the\n * value the central side can dedup on.\n */\nexport function shareHash(kind: ShareKind, payload: unknown): string {\n return createHash('sha256').update(stableStringify({ kind, payload })).digest('hex');\n}\n","/**\n * Share classifier (2.11) — pure, DB-agnostic.\n *\n * Buckets the items of a {@link SharePreview} (the 2.10 policy-transformed payload)\n * into `share` / `withhold` / `pending` against the persisted {@link SharingPolicy}\n * and the set of already-shared content hashes:\n *\n * - A node whose transformed payload is `null` (effective level `none`, incl. the\n * PERSONAL-host hard floor) is **withheld** — it never leaves.\n * - A node already pushed (its `shareHash` ∈ `sharedHashes`) is dropped (no bucket).\n * - A surviving node covered by the policy ({@link isRemembered}) is **share** — the\n * employee's policy is the standing consent, so it auto-approves with no re-prompt.\n * - A surviving node *not* covered by any policy rule/default is **pending** — new /\n * unmatched, queued for explicit review. Nothing in this bucket ever leaves until\n * the employee approves it (the load-bearing privacy invariant).\n *\n * Edges follow their endpoints: an edge is shareable only when both endpoints\n * survive (already guaranteed by `previewShare`, which drops dangling edges) and\n * both endpoint nodes land in `share`. Otherwise the edge is withheld.\n *\n * The transform/anonymization itself is **not** reimplemented here — it is consumed\n * from `previewShare`. This module only routes the already-transformed items.\n */\n\nimport type { SharingPolicy } from '../types.js';\nimport type { SharePreview } from '../sharing.js';\nimport { isRemembered } from '../sharing.js';\nimport { shareHash } from './hash.js';\n\n/** One classified, ready-to-queue item carrying its outgoing (transformed) payload. */\nexport interface ClassifiedItem {\n contentHash: string;\n kind: 'node' | 'edge';\n /** Original node id (for `node` items); the remapped source id for `edge` items. */\n nodeId?: string;\n /** The exact transformed bytes that would leave the machine. */\n payload: unknown;\n}\n\nexport interface ClassifyResult {\n /** Covered by policy → auto-approve (no re-prompt). */\n share: ClassifiedItem[];\n /** Effective level `none` / PERSONAL floor → suppressed; never leaves. */\n withhold: ClassifiedItem[];\n /** New / unmatched → queued for explicit human review. */\n pending: ClassifiedItem[];\n}\n\nexport interface ClassifyInput {\n preview: SharePreview;\n policy: SharingPolicy;\n /** `content_hash` values already pushed (status `shared`) — suppress re-share. */\n sharedHashes: ReadonlySet<string>;\n}\n\n/**\n * Route a {@link SharePreview} into share / withhold / pending buckets. Pure and\n * deterministic: same inputs always yield the same buckets. The payloads carried\n * here are the transformed ones from the preview, so anything routed to `share` is\n * already anonymized per policy.\n */\nexport function classify(input: ClassifyInput): ClassifyResult {\n const { preview, policy, sharedHashes } = input;\n const result: ClassifyResult = { share: [], withhold: [], pending: [] };\n\n // Track which original node ids are cleared to share, so edges can follow.\n const sharedNodeIds = new Set<string>();\n\n for (const entry of preview.nodes) {\n if (entry.payload === null) {\n // Dropped at level `none` (or PERSONAL floor): nothing leaves.\n result.withhold.push({ contentHash: '', kind: 'node', nodeId: entry.node.id, payload: null });\n continue;\n }\n const contentHash = shareHash('node', entry.payload);\n if (sharedHashes.has(contentHash)) continue; // already shared — no bucket\n\n const item: ClassifiedItem = { contentHash, kind: 'node', nodeId: entry.node.id, payload: entry.payload };\n if (isRemembered(policy, entry.node.id)) {\n result.share.push(item);\n sharedNodeIds.add(entry.node.id);\n } else {\n result.pending.push(item);\n }\n }\n\n // Edges already have endpoints remapped (and dangling ones dropped) by previewShare.\n // An edge is shareable only when both of its (original) endpoint nodes are cleared.\n // previewShare exposes only remapped ids on edges, so we re-derive surviving status\n // from the share bucket via the transformed payload: edges whose remapped endpoints\n // correspond to shared nodes go to `share`; the rest are withheld.\n const sharedRemappedIds = new Set<string>();\n for (const entry of preview.nodes) {\n if (entry.payload !== null && sharedNodeIds.has(entry.node.id)) {\n sharedRemappedIds.add(entry.payload.id);\n }\n }\n for (const e of preview.edges) {\n const payload = { sourceId: e.sourceId, targetId: e.targetId, relationship: e.relationship };\n const contentHash = shareHash('edge', payload);\n const bothShared = sharedRemappedIds.has(e.sourceId) && sharedRemappedIds.has(e.targetId);\n if (!bothShared) {\n result.withhold.push({ contentHash: '', kind: 'edge', payload });\n continue;\n }\n if (sharedHashes.has(contentHash)) continue;\n result.share.push({ contentHash, kind: 'edge', payload });\n }\n\n return result;\n}\n","/**\n * Outbound push client (2.11) — Cartograph's first egress path.\n *\n * Pushes consented, policy-transformed deltas to the central ingest endpoint over\n * bearer-auth HTTPS. It is the inverse of the inbound MCP auth in\n * `src/mcp/transports.ts`: instead of validating a bearer token, it *sends* one.\n *\n * Load-bearing privacy invariant: this function only ever sends the items it is\n * handed, which the caller (`sync push`) draws exclusively from `getApprovedShares()`\n * (status `approved`). The hard guards below additionally make a no-config / insecure\n * / empty send impossible. The bearer token is NEVER logged and NEVER placed in the\n * payload; error logging routes the URL through `stripSensitive`.\n *\n * Network is Node 20+ global `fetch`, injectable via `fetchImpl` so tests never hit\n * the network. Zero new dependencies.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { CartographyConfig } from '../types.js';\nimport { stripSensitive, redactValue } from '../tools.js';\nimport { stableStringify } from '../diff.js';\n\n/** One item to push. `payload` is the already-policy-transformed (anonymized) projection. */\nexport interface PushItem {\n contentHash: string;\n kind: 'node' | 'edge';\n payload: unknown;\n}\n\nexport interface PushResult {\n /** Items the server acknowledged. */\n sent: number;\n /** Batches POSTed. */\n batches: number;\n /** Items in batches that ultimately failed (left for a later retry). */\n failed: number;\n /** content hashes the server acknowledged (caller flips these to `shared`). */\n sentHashes: string[];\n}\n\nexport interface PushOptions {\n /** Injectable fetch (tests). Defaults to Node global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Items per batch. Defaults to `config.centralDb.batchSize ?? 100`. */\n batchSize?: number;\n /** Max retries per batch on 5xx / network errors. Default 4. */\n maxRetries?: number;\n /** Per-request timeout (ms). Default 15000. */\n timeoutMs?: number;\n /** Preview only — never networks. */\n dryRun?: boolean;\n /** Log sink (stderr by default); the token never reaches it. */\n log?: (line: string) => void;\n /** Sleep hook (tests inject a no-op to skip real backoff). */\n sleep?: (ms: number) => Promise<void>;\n}\n\n/** Wire-format version of the push envelope (the contract WS 2.12 ingests). */\nexport const PUSH_SCHEMA_VERSION = 1 as const;\n\nconst DEFAULT_BATCH = 100;\nconst DEFAULT_RETRIES = 4;\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\nfunction defaultLog(line: string): void {\n process.stderr.write(`[cartography-sync] ${line}\\n`);\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Deterministic per-batch idempotency key: sha256 over the sorted content hashes. */\nfunction batchKey(items: PushItem[]): string {\n const hashes = items.map((i) => i.contentHash).sort();\n return createHash('sha256').update(stableStringify(hashes)).digest('hex');\n}\n\n/**\n * Push approved deltas. Returns counts and the acknowledged content hashes.\n *\n * Hard guards (the no-leak guarantee):\n * - missing `centralDb.url`/`token` → throws (nothing sent, fetch never called).\n * - non-`https:` URL → throws, unless `CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1'`\n * (test-only, documented as unsafe; never the default).\n * - empty `items` → returns zeros without any network call.\n */\nexport async function pushDeltas(\n config: CartographyConfig,\n items: PushItem[],\n opts: PushOptions = {},\n): Promise<PushResult> {\n const central = config.centralDb;\n if (!central?.url || !central.token) {\n throw new Error('sync push: centralDb not configured (set centralDb.url + token)');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(central.url);\n } catch {\n throw new Error('sync push: centralDb.url is not a valid URL');\n }\n const insecureAllowed = process.env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1';\n if (parsed.protocol !== 'https:' && !insecureAllowed) {\n throw new Error(\n `sync push: refusing to send over insecure ${parsed.protocol}// — use https:// ` +\n `(or set CARTOGRAPHY_ALLOW_INSECURE_SYNC=1 for local testing only)`,\n );\n }\n\n const log = opts.log ?? defaultLog;\n const sleep = opts.sleep ?? defaultSleep;\n const fetchImpl = opts.fetchImpl ?? fetch;\n const batchSize = Math.max(1, opts.batchSize ?? central.batchSize ?? DEFAULT_BATCH);\n const maxRetries = Math.max(0, opts.maxRetries ?? DEFAULT_RETRIES);\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const safeUrl = stripSensitive(central.url);\n\n if (items.length === 0) {\n log('nothing to push (0 approved items)');\n return { sent: 0, batches: 0, failed: 0, sentHashes: [] };\n }\n\n // Defensive: strip any lingering credentials from the (already-anonymized) payload.\n const batches: PushItem[][] = [];\n for (let i = 0; i < items.length; i += batchSize) {\n batches.push(items.slice(i, i + batchSize).map((it) => ({ ...it, payload: redactValue(it.payload) })));\n }\n\n let sent = 0;\n let failed = 0;\n const sentHashes: string[] = [];\n\n for (const batch of batches) {\n const key = batchKey(batch);\n const body = JSON.stringify({\n schemaVersion: PUSH_SCHEMA_VERSION,\n ...(central.org ? { org: central.org } : {}),\n items: batch.map((b) => ({ contentHash: b.contentHash, kind: b.kind, payload: b.payload })),\n });\n\n if (opts.dryRun) {\n log(`dry-run: would POST ${batch.length} item(s) to ${safeUrl} (idempotency ${key.slice(0, 12)}…)`);\n sent += batch.length;\n sentHashes.push(...batch.map((b) => b.contentHash));\n continue;\n }\n\n let ok = false;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const startedAt = Date.now();\n try {\n const res = await fetchImpl(central.url, {\n method: 'POST',\n headers: {\n 'authorization': `Bearer ${central.token}`,\n 'content-type': 'application/json',\n 'x-idempotency-key': key,\n },\n body,\n signal: controller.signal,\n });\n const elapsed = Date.now() - startedAt;\n if (res.ok) {\n log(`pushed ${batch.length} item(s) → ${safeUrl} [${res.status}] ${elapsed}ms (attempt ${attempt + 1})`);\n ok = true;\n break;\n }\n // 4xx is a client error — do not retry; surface and stop this batch.\n if (res.status >= 400 && res.status < 500) {\n log(`batch rejected → ${safeUrl} [${res.status}] (no retry)`);\n break;\n }\n // 5xx — retryable.\n log(`batch failed → ${safeUrl} [${res.status}] (attempt ${attempt + 1}/${maxRetries + 1})`);\n } catch (err) {\n // Network/timeout — retryable. Never echo headers or token.\n const msg = err instanceof Error ? err.message : String(err);\n log(`batch error → ${safeUrl}: ${msg.replace(/https?:\\/\\/[^\\s]+/g, (u) => stripSensitive(u))} (attempt ${attempt + 1}/${maxRetries + 1})`);\n } finally {\n clearTimeout(timer);\n }\n if (attempt < maxRetries) {\n // Capped exponential backoff with jitter.\n const base = Math.min(2 ** attempt * 250, 4000);\n await sleep(base + Math.floor(Math.random() * 100));\n }\n }\n\n if (ok) {\n sent += batch.length;\n sentHashes.push(...batch.map((b) => b.contentHash));\n } else {\n failed += batch.length;\n }\n }\n\n return { sent, batches: batches.length, failed, sentHashes };\n}\n","/**\n * Central-DB sync orchestration (2.11) — the post-scan enqueue glue.\n *\n * After a (manual or scheduled) scan, {@link runSyncClassify} resolves the\n * employee's persisted sharing policy (2.10), builds the policy-transformed payload\n * via `previewShare` (already anonymized/dropped — nothing raw for `anonymized`/\n * `none`), classifies it against the already-shared set, and enqueues the result\n * into `pending_shares`:\n *\n * - `share` → enqueued `approved` with `decided_by='rule'` (the policy *is* the\n * standing consent; never re-prompted).\n * - `pending` → enqueued `pending` for explicit review (nothing leaves until approved).\n * - `withhold`→ recorded `withheld` for the audit/`sync status` suppression count.\n *\n * Short-circuits to a no-op when `centralDb` is unconfigured, so an install without\n * sync configured never writes to the queue. All writes run in one transaction.\n */\n\nimport type { CartographyConfig } from '../types.js';\nimport type { CartographyDB } from '../db.js';\nimport { loadOrgKey } from '../orgkey.js';\nimport { previewShare } from '../sharing.js';\nimport { classify } from './classify.js';\n\nexport interface SyncClassifyResult {\n enqueued: number;\n autoShared: number;\n withheld: number;\n}\n\nexport interface SyncClassifyOptions {\n /** Override the org-key path / namespace (tests). Defaults to `config.organization`. */\n orgKey?: Buffer;\n}\n\n/**\n * Classify a session's topology against the persisted policy and enqueue the\n * result. Returns counts for `pending` (`enqueued`), auto-approved (`autoShared`),\n * and suppressed (`withheld`) items. No-op (all zeros) when sync is unconfigured.\n */\nexport function runSyncClassify(\n db: CartographyDB,\n sessionId: string,\n config: CartographyConfig,\n opts: SyncClassifyOptions = {},\n): SyncClassifyResult {\n if (!config.centralDb?.url) return { enqueued: 0, autoShared: 0, withheld: 0 };\n\n const orgKey = opts.orgKey ?? loadOrgKey({ organization: config.organization });\n const policy = db.getSharingPolicy();\n // persistReversal: an actual pre-send transform should persist the reversal map so\n // an admin can later invert anonymized tokens.\n const preview = previewShare(db, sessionId, orgKey, policy, { persistReversal: true });\n const sharedHashes = db.getSharedHashes();\n\n const { share, pending, withhold } = classify({ preview, policy, sharedHashes });\n\n const writeAll = db.rawConnection().transaction(() => {\n for (const item of share) {\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'approved',\n decidedBy: 'rule',\n });\n }\n for (const item of pending) {\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'pending',\n });\n }\n // Withheld items carry no stable content hash (payload may be null); record them\n // only when they have a real hash to key on (edges suppressed for unshared\n // endpoints). Dropped `none` nodes are intentionally not queued — they leave no\n // trace beyond `previewShare.droppedNodeIds`.\n for (const item of withhold) {\n if (!item.contentHash) continue;\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'withheld',\n decidedBy: 'rule',\n });\n }\n });\n writeAll();\n\n return { enqueued: pending.length, autoShared: share.length, withheld: withhold.length };\n}\n\nexport type { ClassifiedItem, ClassifyResult, ClassifyInput } from './classify.js';\nexport { classify } from './classify.js';\nexport { shareHash } from './hash.js';\nexport { pushDeltas } from './push.js';\nexport type { PushItem, PushResult, PushOptions } from './push.js';\n","import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ProviderName } from './types.js';\n\nfunction isOAuthLoggedIn(): boolean {\n // Claude CLI stores OAuth tokens in ~/.claude/.credentials.json\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n const credFile = join(home, '.claude', '.credentials.json');\n if (!existsSync(credFile)) return false;\n try {\n const creds = JSON.parse(readFileSync(credFile, 'utf8')) as Record<string, unknown>;\n const oauth = creds['claudeAiOauth'] as Record<string, unknown> | undefined;\n return typeof oauth?.['accessToken'] === 'string' && oauth['accessToken'].length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Provider-aware preflight. Defaults to `'claude'` so existing zero-arg callers are\n * unaffected. For non-Claude providers, checks only what each backend needs from the\n * environment; deeper reachability (e.g. Ollama host) is deferred to the provider's\n * `ensureAvailable`, which degrades at run with an actionable message.\n */\nexport function checkPrerequisites(provider: ProviderName = 'claude'): void {\n if (provider === 'openai') {\n checkOpenAIPrerequisites();\n return;\n }\n if (provider === 'ollama') {\n // No key/CLI required at preflight; the provider checks host reachability at run.\n process.stderr.write(\n `✓ Ollama provider selected (host: ${process.env.OLLAMA_HOST ?? 'http://127.0.0.1:11434'})\\n`,\n );\n return;\n }\n checkClaudePrerequisites();\n}\n\nfunction checkOpenAIPrerequisites(): void {\n if (!process.env.OPENAI_API_KEY) {\n process.stderr.write(\n '\\n❌ OpenAI provider selected but OPENAI_API_KEY is not set.\\n\\n' +\n ' Set your key:\\n' +\n ' export OPENAI_API_KEY=sk-...\\n\\n' +\n ' Install the SDK if needed:\\n' +\n ' npm install openai\\n\\n' +\n ' Tip: pass a non-Claude model, e.g. --model gpt-4.1\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('OPENAI_API_KEY not set');\n }\n}\n\nfunction checkClaudePrerequisites(): void {\n // Claude CLI present?\n try {\n execSync('claude --version', { stdio: 'pipe' });\n } catch {\n process.stderr.write(\n '\\n❌ Claude CLI not found.\\n' +\n ' Datasynx Cartography requires the Claude CLI as a runtime dependency.\\n\\n' +\n ' Install:\\n' +\n ' npm install -g @anthropic-ai/claude-code\\n' +\n ' # or\\n' +\n ' curl -fsSL https://claude.ai/install.sh | bash\\n\\n' +\n ' Then: claude login\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('Claude CLI not found');\n }\n\n // Check auth: API Key OR OAuth login (claude.ai Subscription)\n const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);\n const hasOAuth = isOAuthLoggedIn();\n\n if (!hasApiKey && !hasOAuth) {\n process.stderr.write(\n '⚠ No authentication found. Please choose one of the following options:\\n\\n' +\n ' Option A — claude.ai Subscription (recommended):\\n' +\n ' claude login\\n\\n' +\n ' Option B — API Key:\\n' +\n ' export ANTHROPIC_API_KEY=sk-ant-...\\n\\n'\n );\n } else if (hasOAuth && !hasApiKey) {\n process.stderr.write('✓ Logged in via claude login (Subscription)\\n');\n }\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAqON,SAAS,mBAAmB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQ1D,UAAU,SAAS,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAW1C,UAAU,0MAA0M,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCA+BnL,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAOtB,SAAS,mBAAmB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAqDrD,OAAO;AAAA,gBACHA,SAAQ;AAAA,kBACN,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aA+VZ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["classify","import_node_fs","import_node_path","import_node_crypto","import_zod","import_zod","import_node_os","import_node_fs","import_node_path","osHostname","hostname","Database","IS_LINUX","baseName","run","import_zod","SEVERITIES","SEVERITIES","Database","globalId","contentHash","collect","degree","globalId","import_node_crypto","import_node_crypto","import_node_fs","import_node_os","import_node_path","host","POSIX_PATH","WIN_PATH","import_zod","host","hostname","matches","import_zod","anchors","import_node_crypto","LOOPBACK_HOSTS","host","http","body","resolve","lexicalSearch","log","import_zod","host","host","parseToml","parseYaml","stringifyToml","stringifyYaml","import_node_fs","import_node_path","import_node_os","import_node_path","matches","import_zod","import_node_fs","import_node_path","import_node_fs","import_node_path","HEX_SIZE","import_node_fs","json","cursor","import_node_crypto","classify","contentHash","import_node_crypto","log","classify","import_node_child_process","import_node_fs","import_node_path"]}