chain-insights 0.37.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -301,11 +301,11 @@ async function printSubscriptionPassGuidance(pass) {
301
301
  function addAmlAddressRiskCommand(parent, networksCommand) {
302
302
  parent.addCommand(createCliCommand("aml-address-risk").description("CIA workflow: screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new commander.Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
303
303
  try {
304
- const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-C6QY_gam.cjs"));
304
+ const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-cRhdWVRY.cjs"));
305
305
  const requestedVersion = opts.toolVersion ?? opts.version;
306
306
  resolveAmlAddressRiskVersion(requestedVersion);
307
307
  await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
308
- const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-C6QY_gam.cjs"));
308
+ const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-cRhdWVRY.cjs"));
309
309
  const result = await runAmlAddressRisk(client, {
310
310
  address: opts.address,
311
311
  network: opts.network,
package/dist/cli.mjs CHANGED
@@ -299,11 +299,11 @@ async function printSubscriptionPassGuidance(pass) {
299
299
  function addAmlAddressRiskCommand(parent, networksCommand) {
300
300
  parent.addCommand(createCliCommand("aml-address-risk").description("CIA workflow: screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
301
301
  try {
302
- const { resolveAmlAddressRiskVersion } = await import("./public-tools-rKIzmn6f.mjs");
302
+ const { resolveAmlAddressRiskVersion } = await import("./public-tools-CYNhQm82.mjs");
303
303
  const requestedVersion = opts.toolVersion ?? opts.version;
304
304
  resolveAmlAddressRiskVersion(requestedVersion);
305
305
  await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
306
- const { runAmlAddressRisk } = await import("./public-tools-rKIzmn6f.mjs");
306
+ const { runAmlAddressRisk } = await import("./public-tools-CYNhQm82.mjs");
307
307
  const result = await runAmlAddressRisk(client, {
308
308
  address: opts.address,
309
309
  network: opts.network,
@@ -769,7 +769,7 @@ async function createProxy() {
769
769
  }],
770
770
  isError: true
771
771
  };
772
- const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-C6QY_gam.cjs"));
772
+ const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-cRhdWVRY.cjs"));
773
773
  const result = await runAmlAddressRisk(remoteClient, {
774
774
  address,
775
775
  network,
@@ -765,7 +765,7 @@ async function createProxy() {
765
765
  }],
766
766
  isError: true
767
767
  };
768
- const { runAmlAddressRisk } = await import("./public-tools-rKIzmn6f.mjs");
768
+ const { runAmlAddressRisk } = await import("./public-tools-CYNhQm82.mjs");
769
769
  const result = await runAmlAddressRisk(remoteClient, {
770
770
  address,
771
771
  network,
@@ -284,7 +284,6 @@ function wrapClientForUsageTracking(client, totals) {
284
284
  }
285
285
  //#endregion
286
286
  //#region src/investigation/public-tools.ts
287
- const GRAPH_QUERY_BATCH_TIMEOUT_SECONDS = 10;
288
287
  const GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS = 3e5;
289
288
  const AML_ADDRESS_RISK_SUPPORTED_VERSIONS = ["v1"];
290
289
  function resolveAmlAddressRiskVersion(requested) {
@@ -343,8 +342,7 @@ async function callGraphBatch(remoteClient, network, queries) {
343
342
  queries: queries.map((query) => ({
344
343
  ...query,
345
344
  query: topologyGraphQuery(query.query)
346
- })),
347
- per_query_timeout_seconds: GRAPH_QUERY_BATCH_TIMEOUT_SECONDS
345
+ }))
348
346
  }
349
347
  }, void 0, {
350
348
  timeout: GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS,
@@ -1169,4 +1167,4 @@ function htmlEscape(value) {
1169
1167
  //#endregion
1170
1168
  export { resolveAmlAddressRiskVersion, runAmlAddressRisk };
1171
1169
 
1172
- //# sourceMappingURL=public-tools-rKIzmn6f.mjs.map
1170
+ //# sourceMappingURL=public-tools-CYNhQm82.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-tools-CYNhQm82.mjs","names":["isRecord","numberValue"],"sources":["../src/viz/graph-normalizer.ts","../src/investigation/risk-level.ts","../src/workspace/output-root.ts","../src/lib/usage-accumulator.ts","../src/investigation/public-tools.ts"],"sourcesContent":["const GRAPH_TYPE_LABELS = new Set([\n 'Address',\n 'Exchange',\n 'Miner',\n 'Validator',\n 'Hotkey',\n 'Subnet',\n 'IPAddress',\n])\nconst FLOW_EDGE_TYPES = new Set(['flows_to', 'transfer', 'transfer_to', 'transfers_to'])\n\ntype GraphRecord = Record<string, unknown>\n\nexport type NormalizedGraphPayload = {\n schema: 'chain-insights.graph.v1'\n nodes: GraphRecord[]\n edges: GraphRecord[]\n flows: unknown[]\n edge_anchors: unknown[]\n [key: string]: unknown\n}\n\nfunction isRecord(value: unknown): value is GraphRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction stringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.map(String).filter(Boolean) : []\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)]\n}\n\nfunction lowerSnake(value: string): string {\n return value\n .trim()\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase()\n}\n\nfunction isGraphTypeLabel(label: string): boolean {\n return GRAPH_TYPE_LABELS.has(label)\n}\n\nfunction displayLabels(node: GraphRecord): string[] {\n return unique(stringArray(node['labels'])).filter((label) => !isGraphTypeLabel(label))\n}\n\nfunction systemLabels(node: GraphRecord): string[] {\n return unique([\n ...stringArray(node['system_labels']),\n ...stringArray(node['graph_labels']),\n ...stringArray(node['raw_labels']),\n ...stringArray(node['labels']).filter(isGraphTypeLabel),\n ])\n}\n\nfunction normalizeRoles(node: GraphRecord): string[] {\n const roles = stringArray(node['roles'])\n const role = typeof node['role'] === 'string' ? node['role'] : ''\n if (role === 'source_exchange') roles.push('exchange')\n else if (role) roles.push(role)\n\n const display = stringArray(node['labels']).map(lowerSnake)\n if (\n systemLabels(node).some((label) => lowerSnake(label) === 'exchange') ||\n display.includes('exchange')\n ) {\n roles.push('exchange')\n }\n\n return unique(roles.map(lowerSnake).filter(Boolean))\n}\n\nfunction normalizeNodeType(node: GraphRecord): string {\n if (typeof node['node_type'] === 'string' && node['node_type'].trim()) {\n return lowerSnake(node['node_type'])\n }\n if (typeof node['address'] === 'string' || typeof node['id'] === 'string') return 'address'\n const labels = systemLabels(node)\n if (labels.length > 0) return lowerSnake(labels[0]!)\n return 'unknown'\n}\n\nfunction normalizeNode(node: unknown): GraphRecord {\n if (!isRecord(node)) return {}\n\n const normalized: GraphRecord = {}\n for (const [key, value] of Object.entries(node)) {\n if (\n [\n 'address_subtypes',\n 'entity_kind',\n 'graph_labels',\n 'labels',\n 'node_type',\n 'pattern_flags',\n 'raw_labels',\n 'role',\n 'roles',\n 'system_labels',\n 'type',\n ].includes(key)\n ) {\n continue\n }\n normalized[key] = value\n }\n\n const id =\n typeof node['id'] === 'string'\n ? node['id']\n : typeof node['address'] === 'string'\n ? node['address']\n : undefined\n if (id) normalized['id'] = id\n\n const address = typeof node['address'] === 'string' ? node['address'] : id\n if (address) normalized['address'] = address\n\n normalized['node_type'] = normalizeNodeType(node)\n normalized['labels'] = displayLabels(node)\n\n const addressSubtypes = unique(stringArray(node['address_subtypes']))\n if (addressSubtypes.length > 0) normalized['address_subtypes'] = addressSubtypes\n\n const roles = normalizeRoles(node)\n if (roles.length > 0) normalized['roles'] = roles\n\n if (\n !Array.isArray(node['flags']) &&\n Array.isArray(node['pattern_flags']) &&\n node['pattern_flags'].length > 0\n ) {\n normalized['flags'] = node['pattern_flags'].map(String)\n }\n\n return normalized\n}\n\nfunction normalizeEdgeType(edge: GraphRecord): string {\n const rawType =\n typeof edge['edge_type'] === 'string' && edge['edge_type'].trim()\n ? edge['edge_type']\n : typeof edge['type'] === 'string' && edge['type'].trim()\n ? edge['type']\n : typeof edge['relationship_type'] === 'string' && edge['relationship_type'].trim()\n ? edge['relationship_type']\n : 'related_to'\n const edgeType = lowerSnake(rawType)\n return FLOW_EDGE_TYPES.has(edgeType) ? 'flows_to' : edgeType\n}\n\nfunction normalizeEdge(edge: unknown): GraphRecord {\n if (!isRecord(edge)) return {}\n\n const normalized: GraphRecord = {}\n for (const [key, value] of Object.entries(edge)) {\n if (['edge_type', 'from_address', 'relationship_type', 'to_address', 'type'].includes(key))\n continue\n normalized[key] = value\n }\n\n if (typeof normalized['source'] !== 'string' && typeof edge['from_address'] === 'string') {\n normalized['source'] = edge['from_address']\n }\n if (typeof normalized['target'] !== 'string' && typeof edge['to_address'] === 'string') {\n normalized['target'] = edge['to_address']\n }\n normalized['edge_type'] = normalizeEdgeType(edge)\n\n return normalized\n}\n\nexport function normalizeGraphPayload(payload: unknown): NormalizedGraphPayload {\n if (!isRecord(payload) || payload['schema'] !== 'chain-insights.graph.v1') {\n throw new Error('Unsupported graph payload schema')\n }\n\n return {\n ...payload,\n schema: 'chain-insights.graph.v1',\n nodes: Array.isArray(payload['nodes']) ? payload['nodes'].map(normalizeNode) : [],\n edges: Array.isArray(payload['edges']) ? payload['edges'].map(normalizeEdge) : [],\n flows: Array.isArray(payload['flows']) ? payload['flows'] : [],\n edge_anchors: Array.isArray(payload['edge_anchors']) ? payload['edge_anchors'] : [],\n }\n}\n","/**\n * Risk-level normalization shared by the AML tools and graph payload builders.\n *\n * The Chain Insights Graph serves the ML verdict verbatim, which historically\n * mixed cases (HIGH vs high) and — since the calibrated-scoring release —\n * includes an explicit abstention band: risk_level = UNSCORED means the model\n * had too little labeled graph context to stand behind a band. UNSCORED is a\n * \"no stance\" state and must never be rendered or reasoned about as low risk.\n */\n\nexport const RISK_SEVERITIES = ['low', 'medium', 'high', 'critical'] as const\nexport const UNSCORED_RISK_LEVEL = 'unscored'\n\n/** Lowercased, trimmed risk level; undefined for empty/unrecognized values. */\nexport function normalizeRiskLevel(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const normalized = value.trim().toLowerCase()\n if (!normalized) return undefined\n if (normalized === UNSCORED_RISK_LEVEL) return UNSCORED_RISK_LEVEL\n if ((RISK_SEVERITIES as readonly string[]).includes(normalized)) return normalized\n return undefined\n}\n\n/** True when the ML verdict explicitly abstained (no stance, not low). */\nexport function isUnscoredRiskLevel(value: unknown): boolean {\n return normalizeRiskLevel(value) === UNSCORED_RISK_LEVEL\n}\n\n/**\n * Severity ordering for comparisons: low(0) < medium(1) < high(2) <\n * critical(3). Unknown/unscored values rank -1 — they never win a\n * severity comparison, they abstain from it.\n */\nexport function riskSeverityRank(value: unknown): number {\n const normalized = normalizeRiskLevel(value)\n if (!normalized || normalized === UNSCORED_RISK_LEVEL) return -1\n return (RISK_SEVERITIES as readonly string[]).indexOf(normalized)\n}\n","import path from 'node:path'\nimport { findActiveWorkspace } from './active.js'\n\nexport const NO_WORKSPACE_ERROR = 'No Chain Insights workspace found. Run: cia init .'\n\nexport interface WorkspaceOutputPaths {\n root: string\n metadataDir: string\n schemaDir: string\n runtimeDir: string\n artifactsRoot: string\n entitiesRoot: string\n sessionsRoot: string\n publishedRoot: string\n reportsRoot: string\n reportGraphsRoot: string\n reportTablesRoot: string\n logsRoot: string\n}\n\nexport function requireWorkspaceRoot(startDir = process.cwd()): string {\n const workspace = findActiveWorkspace(startDir)\n if (!workspace) throw new Error(NO_WORKSPACE_ERROR)\n return workspace.root\n}\n\nexport function workspaceOutputPaths(workspaceRoot = requireWorkspaceRoot()): WorkspaceOutputPaths {\n const root = path.resolve(workspaceRoot)\n return {\n root,\n metadataDir: path.join(root, '.chain-insights'),\n schemaDir: path.join(root, '.chain-insights', 'schema'),\n runtimeDir: path.join(root, '.chain-insights', 'runtime'),\n artifactsRoot: path.join(root, 'artifacts'),\n entitiesRoot: path.join(root, 'entities'),\n sessionsRoot: path.join(root, 'sessions'),\n publishedRoot: path.join(root, 'published'),\n reportsRoot: path.join(root, 'reports'),\n reportGraphsRoot: path.join(root, 'reports', 'graphs'),\n reportTablesRoot: path.join(root, 'reports', 'tables'),\n logsRoot: path.join(root, '.chain-insights', 'runtime', 'logs'),\n }\n}\n","/**\n * Per-workflow billable-units accounting for the aml_* tools (internal feature:\n * usage-units-propagation). The `aml_*` workflow tools make MANY internal\n * graph_query_batch round trips to answer one high-level question; this\n * module totals what those round trips billed so the workflow response can\n * report a single `usage` block instead of exposing nothing at all.\n *\n * Capture point: aml_address_risk (and internal graph_query_batch helper\n * calls feeding its enrichment) wraps its `remoteClient` once via\n * `wrapClientForUsageTracking` before making any internal calls, then reads\n * the shared `UsageTotals` back out when building its response. Every\n * internal helper (callGraphBatch, probeSeedAddresses, ...) keeps calling\n * `client.callTool(...)` exactly as before -- the wrapper observes\n * graph_query_batch/graph_query responses in transit and never changes their\n * return value.\n *\n * Defensive by design: a backend that does not yet emit billing fields (or\n * an unparsable response) contributes 0 units but the call still counts\n * toward `query_count` -- this must never throw and never block a workflow.\n */\n\ninterface CallToolLike {\n callTool(...args: unknown[]): Promise<unknown>\n}\n\nexport interface UsageTotals {\n billable_units: number\n query_count: number\n truncated_queries: number\n}\n\nexport function createUsageAccumulator(): UsageTotals {\n return { billable_units: 0, query_count: 0, truncated_queries: 0 }\n}\n\n// Snapshot as a plain object for embedding in a response -- callers should\n// not hand out the live mutable accumulator.\nexport function usageBlock(totals: UsageTotals): UsageTotals {\n return {\n billable_units: totals.billable_units,\n query_count: totals.query_count,\n truncated_queries: totals.truncated_queries,\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction numberValue(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\nfunction textOfToolResult(result: unknown): string {\n if (!isRecord(result)) return ''\n const content = result['content']\n if (!Array.isArray(content)) return ''\n return content\n .filter(\n (item): item is { type: 'text'; text: string } =>\n isRecord(item) && item['type'] === 'text' && typeof item['text'] === 'string'\n )\n .map((item) => item.text)\n .join('\\n')\n}\n\n// facts.query.* is the single-query shape (graph_query). facts.batch.* /\n// facts.queries[] is the batch shape (graph_query_batch). Both are read\n// defensively -- missing/malformed fields never throw, they just contribute\n// nothing to the total.\nfunction accumulateFacts(totals: UsageTotals, facts: unknown): void {\n if (!isRecord(facts)) return\n\n const query = facts['query']\n if (isRecord(query)) {\n const units = numberValue(query['billable_units'])\n if (units !== undefined) totals.billable_units += units\n if (query['truncated'] === true) totals.truncated_queries += 1\n }\n\n const batch = facts['batch']\n const batchUnits = isRecord(batch) ? numberValue(batch['billable_units']) : undefined\n const queries = Array.isArray(facts['queries'])\n ? (facts['queries'] as unknown[]).filter(isRecord)\n : undefined\n\n if (batchUnits !== undefined) {\n totals.billable_units += batchUnits\n } else if (queries) {\n // No batch-level total (older/mocked backend): fall back to summing\n // whatever per-query billable_units are present. Entries with no field\n // contribute 0, same as the single-query defensive path.\n for (const entry of queries) {\n const units = numberValue(entry['billable_units'])\n if (units !== undefined) totals.billable_units += units\n }\n }\n\n if (queries) {\n for (const entry of queries) {\n if (entry['truncated'] === true) totals.truncated_queries += 1\n }\n } else if (isRecord(batch) && batch['truncated'] === true) {\n totals.truncated_queries += 1\n }\n}\n\n// One graph_query_batch round trip is one query_count unit per Cypher\n// statement it carried (facts.queries.length) -- that is what \"one internal\n// graph query\" means for this tool. A round trip whose response could not be\n// attributed to individual statements (parse failure, missing facts.queries)\n// still represents one internal graph query having been issued, so it counts\n// as 1 rather than 0.\nfunction recordGraphQueryBatchResult(totals: UsageTotals, rawResult: unknown): void {\n let facts: unknown\n try {\n const text = textOfToolResult(rawResult).trim()\n if (text) facts = (JSON.parse(text) as { facts?: unknown }).facts\n else if (\n isRecord(rawResult) &&\n isRecord((rawResult as Record<string, unknown>)['structuredContent'])\n ) {\n facts = (\n (rawResult as Record<string, unknown>)['structuredContent'] as Record<string, unknown>\n )['facts']\n }\n } catch {\n facts = undefined\n }\n\n const queries =\n isRecord(facts) && Array.isArray(facts['queries']) ? (facts['queries'] as unknown[]) : undefined\n totals.query_count += queries ? queries.length : 1\n accumulateFacts(totals, facts)\n}\n\n// One graph_query round trip is exactly one internal graph query.\nfunction recordGraphQueryResult(totals: UsageTotals, rawResult: unknown): void {\n totals.query_count += 1\n let facts: unknown\n try {\n if (\n isRecord(rawResult) &&\n isRecord((rawResult as Record<string, unknown>)['structuredContent'])\n ) {\n facts = (\n (rawResult as Record<string, unknown>)['structuredContent'] as Record<string, unknown>\n )['facts']\n } else {\n const text = textOfToolResult(rawResult).trim()\n if (text) facts = (JSON.parse(text) as { facts?: unknown }).facts\n }\n } catch {\n facts = undefined\n }\n accumulateFacts(totals, facts)\n}\n\n// Wraps a Client-shaped object (real MCP SDK Client or a test double) so\n// every graph_query_batch/graph_query call it makes is observed and totaled\n// into `totals`, without changing what the call returns. Any other method\n// or property passes through untouched.\nexport function wrapClientForUsageTracking<T extends CallToolLike>(\n client: T,\n totals: UsageTotals\n): T {\n return new Proxy(client as object, {\n get(target, prop, receiver) {\n if (prop === 'callTool') {\n return async (...args: unknown[]) => {\n const result = await (target as CallToolLike).callTool(...args)\n const request = args[0]\n const toolName =\n isRecord(request) && typeof request['name'] === 'string' ? request['name'] : undefined\n if (toolName === 'graph_query_batch') {\n recordGraphQueryBatchResult(totals, result)\n } else if (toolName === 'graph_query') {\n recordGraphQueryResult(totals, result)\n }\n return result\n }\n }\n const value = Reflect.get(target, prop, target)\n return typeof value === 'function' ? value.bind(target) : value\n },\n }) as T\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport type { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport type { ContentBlock } from '@modelcontextprotocol/sdk/types.js'\nimport { normalizeGraphPayload } from '../viz/graph-normalizer.js'\nimport { isUnscoredRiskLevel, normalizeRiskLevel, riskSeverityRank } from './risk-level.js'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport {\n createUsageAccumulator,\n usageBlock,\n wrapClientForUsageTracking,\n} from '../lib/usage-accumulator.js'\n\nexport { buildTruthProposal, resolveTruthIngressTlsPaths } from './truth-proposal.js'\nexport type { TruthProposalCommand } from './truth-proposal.js'\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n isError?: boolean\n}\n\ninterface ParsedGraphBatch {\n facts?: {\n queries?: Array<{\n id?: string\n ok?: boolean\n results?: Array<Record<string, unknown>>\n error?: string\n }>\n }\n}\n\ntype QueryFailure = {\n id: string\n error: string\n}\n\nconst GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS = 5 * 60 * 1000\n\nexport interface AddressRiskOptions {\n address: string\n network: string\n compareAddress?: string\n writeArtifacts?: boolean\n}\n\nconst AML_ADDRESS_RISK_SUPPORTED_VERSIONS = ['v1'] as const\nexport type AmlAddressRiskVersion = (typeof AML_ADDRESS_RISK_SUPPORTED_VERSIONS)[number]\nexport const LATEST_AML_ADDRESS_RISK_VERSION: AmlAddressRiskVersion = 'v1'\n\nexport function resolveAmlAddressRiskVersion(requested?: string): AmlAddressRiskVersion {\n const normalized = requested?.trim().toLowerCase()\n if (!normalized) return LATEST_AML_ADDRESS_RISK_VERSION\n if (\n AML_ADDRESS_RISK_SUPPORTED_VERSIONS.includes(\n normalized as (typeof AML_ADDRESS_RISK_SUPPORTED_VERSIONS)[number]\n )\n ) {\n return normalized as AmlAddressRiskVersion\n }\n throw new Error(\n `Unsupported aml_address_risk version \"${requested?.trim() ?? ''}\". Supported versions: ${AML_ADDRESS_RISK_SUPPORTED_VERSIONS.join(', ')}.`\n )\n}\n\nfunction escapeCypherString(value: string): string {\n return value.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')\n}\n\nfunction textFromToolResult(result: RemoteToolResult): string {\n return (result.content ?? [])\n .filter((item): item is Extract<ContentBlock, { type: 'text' }> => item.type === 'text')\n .map((item) => item.text)\n .join('\\n')\n}\n\nfunction parseGraphBatchResult(result: RemoteToolResult): ParsedGraphBatch {\n const text = textFromToolResult(result).trim()\n if (!text) throw new Error('graph_query_batch returned no text content')\n const parsed = JSON.parse(text) as ParsedGraphBatch\n if (!parsed.facts?.queries)\n throw new Error('graph_query_batch response did not include facts.queries')\n return parsed\n}\n\nfunction topologyGraphQuery(query: string): string {\n const trimmed = query.trim()\n if (/^USE\\s+/i.test(trimmed)) return trimmed\n return `USE topology ${trimmed}`\n}\n\nfunction collectQueryFailure(\n failures: QueryFailure[],\n id: string,\n error: string | undefined\n): void {\n failures.push({ id, error: error || 'unknown error' })\n}\n\nfunction optionalResultsFor(\n batch: ParsedGraphBatch,\n id: string,\n failures: QueryFailure[]\n): Array<Record<string, unknown>> {\n const query = batch.facts?.queries?.find((entry) => entry.id === id)\n if (!query) return []\n if (query.ok === false) {\n collectQueryFailure(failures, id, query.error)\n return []\n }\n return query.results ?? []\n}\n\nfunction optionalResultsWithPrefix(\n batch: ParsedGraphBatch,\n prefix: string,\n failures: QueryFailure[]\n): Array<Record<string, unknown>> {\n return (batch.facts?.queries ?? [])\n .filter((entry) => entry.id?.startsWith(prefix))\n .flatMap((entry) => {\n if (entry.ok === false) {\n collectQueryFailure(failures, entry.id ?? prefix, entry.error)\n return []\n }\n return entry.results ?? []\n })\n}\n\nasync function callGraphBatch(\n remoteClient: Client,\n network: string,\n queries: Array<{ id: string; query: string }>\n): Promise<ParsedGraphBatch> {\n const result = (await remoteClient.callTool(\n {\n name: 'graph_query_batch',\n arguments: {\n network,\n queries: queries.map((query) => ({\n ...query,\n query: topologyGraphQuery(query.query),\n })),\n },\n },\n undefined,\n {\n timeout: GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS,\n }\n )) as RemoteToolResult\n if (result.isError) throw new Error(textFromToolResult(result) || 'graph_query_batch failed')\n return parseGraphBatchResult(result)\n}\n\nfunction addressProfileQuery(address: string): { id: string; query: string } {\n // risk_score/risk_level are always projected: topology is now the only ML\n // risk source (the retired facts_risk_scores_view never had a risk_level\n // column, so UNSCORED abstention was invisible until this move).\n const riskFields = ', a.risk_score AS live_risk_score, a.risk_level AS live_risk_level'\n // Per-label risk is the overlay graphsync materializes on the node, one\n // entry per current label row. DozerDB properties cannot hold a list of\n // maps, so it is three parallel lists: label_risk_labels, label_risk_levels\n // and label_risk_updated_timestamps. deriveLabelRows zips them back.\n // Missing/empty is \"no label-risk signal,\" never an error.\n const labelRiskFields =\n ', a.label_risk_labels AS label_risk_labels, a.label_risk_levels AS label_risk_levels, a.label_risk_updated_timestamps AS label_risk_updated_timestamps'\n return {\n id: 'address_profile',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})`,\n `RETURN a.address AS address, a.network AS network, a.labels AS display_labels, a.labels AS system_labels, a.is_exchange AS is_exchange${riskFields}${labelRiskFields}`,\n 'LIMIT 1',\n ].join(' '),\n }\n}\n\n// Lifetime address features are materialized on the one USE topology node.\nfunction addressFeatureQuery(address: string): { id: string; query: string } {\n return {\n id: 'address_feature',\n query: [\n 'USE topology',\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})`,\n 'RETURN a.degree_in AS degree_in, a.degree_out AS degree_out, a.degree_total AS degree_total, a.tx_in_count AS tx_in_count, a.tx_out_count AS tx_out_count, a.tx_total_count AS tx_total_count, a.total_volume_usd AS total_volume_usd, a.total_in_usd AS total_in_usd, a.total_out_usd AS total_out_usd, a.net_flow_usd AS net_flow_usd, a.first_activity_timestamp AS first_activity_timestamp, a.last_activity_timestamp AS last_activity_timestamp, a.activity_span_days AS activity_span_days',\n 'LIMIT 1',\n ].join(' '),\n }\n}\n\nfunction flowEdgeMap(variableName: string): string {\n return `{amount_usd_sum: ${variableName}.amount_usd_sum, tx_count: ${variableName}.tx_count, first_seen_timestamp: ${variableName}.first_seen_timestamp, last_seen_timestamp: ${variableName}.last_seen_timestamp}`\n}\n\nfunction pathNodeMap(variableName: string): string {\n // risk_score/risk_level are always projected on topology nodes.\n const riskFields = `, risk_score: ${variableName}.risk_score, risk_level: ${variableName}.risk_level`\n return `{address: ${variableName}.address, network: ${variableName}.network, labels: ${variableName}.labels, system_labels: ${variableName}.labels, is_exchange: ${variableName}.is_exchange${riskFields}}`\n}\n\n// money-trail enrichment: incident MONEY_TRAIL edges on the subject address\n// (either direction), and the TRAIL_ENDS_AT fan-out of whichever seed\n// generated the trail -- both are optional-results reads, so a graph with no\n// money-trail layer (or a failed query) never fails aml_address_risk itself.\nexport function moneyTrailIncidentQuery(address: string): string {\n return [\n 'USE topology',\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:MONEY_TRAIL]-()`,\n 'RETURN r.edge_class AS edge_class, r.value AS value, r.min_hop AS min_hop, r.seed_count AS seed_count, r.primary_seed AS primary_seed, r.generation AS generation, r.network AS network, r.first_ts AS first_ts, r.last_ts AS last_ts',\n 'LIMIT 200',\n ].join(' ')\n}\n\nexport function moneyTrailEndsQuery(seed: string): string {\n return [\n 'USE topology',\n `MATCH (s:Address {address: \"${escapeCypherString(seed)}\"})-[r:TRAIL_ENDS_AT]->(t:Address)`,\n 'RETURN t.address AS address, r.fact_type AS fact_type, r.direction AS direction, r.terminal_role AS terminal_role, r.hop AS hop, r.value AS value, r.generation AS generation',\n 'LIMIT 200',\n ].join(' ')\n}\n\n// Task 2 (trace fast path): a resolved trace target IS a money-trail seed\n// when it has outgoing TRAIL_ENDS_AT edges -- same shape as\n// moneyTrailEndsQuery, reused here under a probe-specific name so the\n// trace-tool call site reads as \"check whether this address is a seed\"\n// rather than \"fetch the ends fan of a known seed\" (they happen to be the\n// same read).\nexport function moneyTrailSeedProbeQuery(address: string): string {\n return moneyTrailEndsQuery(address)\n}\n\n// Precomputed MONEY_TRAIL corridor from a confirmed seed: bounded to 6 hops\n// (min_hop is a property baked onto the edge by the walk engine, not a\n// variable-length path depth) and excludes peripheral-only continuation --\n// peripheral is the \"touched funds\" floor (see MONEY_TRAIL_CLASS_RANK), not\n// a real trail hop, so it never extends the fast-path corridor.\nexport function moneyTrailCorridorQuery(seed: string): string {\n return [\n 'USE topology',\n `MATCH (s:Address {address: \"${escapeCypherString(seed)}\"})-[r:MONEY_TRAIL]->(t:Address)`,\n 'WHERE r.min_hop <= 6 AND r.edge_class <> \"peripheral\"',\n 'RETURN t.address AS address, r.edge_class AS edge_class, r.value AS value, r.min_hop AS min_hop, r.seed_count AS seed_count, r.primary_seed AS primary_seed, r.generation AS generation, r.network AS network',\n 'ORDER BY r.min_hop ASC',\n 'LIMIT 200',\n ].join(' ')\n}\n\nexport interface MoneyTrailBlock {\n on_trail: true\n class: string\n min_hop: number\n primary_seed: string\n generation: number\n nearest_trail_end?: { address: string; fact_type: string; value: string }\n}\n\nconst MONEY_TRAIL_CLASS_RANK: Record<string, number> = { transport: 3, holding: 2, peripheral: 1 }\n\nexport function buildMoneyTrailBlock(\n incidentRows: Array<Record<string, unknown>>,\n endRows: Array<Record<string, unknown>>\n): MoneyTrailBlock | undefined {\n if (incidentRows.length === 0) return undefined\n\n const winningClassRank = Math.max(\n ...incidentRows.map((row) => MONEY_TRAIL_CLASS_RANK[firstString(row['edge_class']) ?? ''] ?? 0)\n )\n const winningClass =\n Object.entries(MONEY_TRAIL_CLASS_RANK).find(([, rank]) => rank === winningClassRank)?.[0] ??\n firstString(incidentRows[0]?.['edge_class']) ??\n 'peripheral'\n const rowsOfWinningClass = incidentRows.filter(\n (row) => firstString(row['edge_class']) === winningClass\n )\n const bestRow = rowsOfWinningClass.reduce((best, row) => {\n const rowHop = numberValue(row['min_hop']) ?? Number.POSITIVE_INFINITY\n const bestHop = numberValue(best['min_hop']) ?? Number.POSITIVE_INFINITY\n return rowHop < bestHop ? row : best\n }, rowsOfWinningClass[0]!)\n\n const nearestEnd = endRows.reduce<Record<string, unknown> | undefined>((best, row) => {\n const rowValue = numberValue(row['value']) ?? Number.NEGATIVE_INFINITY\n const bestValue = best\n ? (numberValue(best['value']) ?? Number.NEGATIVE_INFINITY)\n : Number.NEGATIVE_INFINITY\n return !best || rowValue > bestValue ? row : best\n }, undefined)\n\n return {\n on_trail: true,\n class: winningClass,\n min_hop: numberValue(bestRow['min_hop']) ?? 0,\n primary_seed: firstString(bestRow['primary_seed']) ?? '',\n generation: numberValue(bestRow['generation']) ?? 0,\n ...(nearestEnd\n ? {\n nearest_trail_end: {\n address: firstString(nearestEnd['address']) ?? '',\n fact_type: firstString(nearestEnd['fact_type']) ?? '',\n value: String(nearestEnd['value'] ?? ''),\n },\n }\n : {}),\n }\n}\n\nexport function moneyTrailSummarySentence(block: MoneyTrailBlock): string {\n return block.class === 'peripheral'\n ? `This address touched money-trail funds (${block.class}, min hop ${block.min_hop}).`\n : `This address sits on a money trail (${block.class}, min hop ${block.min_hop}).`\n}\n\n// Pool guard (schema skill, \"Pool trace rule\"): a FLOWS_TO walk never starts\n// at a :Pool and never passes through one, and each guard is its own AND term\n// (inside an OR it would guard nothing). Fixed-hop form of\n// skills/chain-insights-cypher: `WHERE NOT src:Pool AND NOT mid:Pool`.\nfunction poolGuardTerms(nodeVariables: string[]): string {\n return nodeVariables.map((nodeVariable) => `NOT ${nodeVariable}:Pool`).join(' AND ')\n}\n\nfunction exchangeOutflowQueries(address: string): Array<{ id: string; query: string }> {\n return Array.from({ length: 3 }, (_, index) => exchangeOutflowQueryAtDepth(address, index + 1))\n}\n\nfunction exchangeOutflowQueryAtDepth(\n address: string,\n depth: number\n): { id: string; query: string } {\n const intermediateVariables = Array.from(\n { length: Math.max(depth - 1, 0) },\n (_, index) => `n${index + 1}`\n )\n const nodeVariables = ['a', ...intermediateVariables, 'exchange']\n const edgeVariables = Array.from({ length: depth }, (_, index) => `r${index + 1}`)\n const relationshipChain = edgeVariables\n .map((edgeVariable, index) => {\n const targetVariable =\n index === edgeVariables.length - 1 ? 'exchange' : intermediateVariables[index]!\n return `-[${edgeVariable}:FLOWS_TO]->(${targetVariable}:Address)`\n })\n .join('')\n const intermediatePredicates = intermediateVariables.map(\n (nodeVariable) => `${nodeVariable}.is_exchange IS NULL`\n )\n // Pool trace rule: the walk starts at the screened address and passes\n // through the intermediates, so none of them may be a :Pool. The exchange\n // end is the target, which may be one.\n const poolGuards = poolGuardTerms(['a', ...intermediateVariables])\n const depositVariable = nodeVariables[nodeVariables.length - 2]!\n const terminalEdgeVariable = edgeVariables[edgeVariables.length - 1]!\n return {\n id: `exchange_outflows_${depth}`,\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})${relationshipChain}`,\n `WHERE a.address <> exchange.address AND exchange.is_exchange IS NOT NULL${intermediatePredicates.length > 0 ? ` AND ${intermediatePredicates.join(' AND ')}` : ''} AND ${poolGuards}`,\n `RETURN \"outflow\" AS direction, exchange.address AS exchange_address, exchange.labels AS exchange_display_labels, exchange.labels AS exchange_system_labels, ${depositVariable}.address AS deposit_address, ${depth} AS hops, ${terminalEdgeVariable}.amount_usd_sum AS amount_usd_sum, ${terminalEdgeVariable}.tx_count AS tx_count, ${terminalEdgeVariable}.first_seen_timestamp AS first_seen_timestamp, ${terminalEdgeVariable}.last_seen_timestamp AS last_seen_timestamp, [${nodeVariables.map((nodeVariable) => `${nodeVariable}.address`).join(', ')}] AS addresses, [${nodeVariables.map((nodeVariable) => pathNodeMap(nodeVariable)).join(', ')}] AS path_nodes, [${edgeVariables.map(flowEdgeMap).join(', ')}] AS edge_props`,\n 'ORDER BY hops ASC',\n 'LIMIT 200',\n ].join(' '),\n }\n}\n\nfunction exchangeInflowQueries(address: string): Array<{ id: string; query: string }> {\n return Array.from({ length: 3 }, (_, index) => exchangeInflowQueryAtDepth(address, index + 1))\n}\n\nfunction exchangeInflowQueryAtDepth(address: string, depth: number): { id: string; query: string } {\n const intermediateVariables = Array.from(\n { length: Math.max(depth - 1, 0) },\n (_, index) => `n${index + 1}`\n )\n const nodeVariables = ['exchange', ...intermediateVariables, 'a']\n const edgeVariables = Array.from({ length: depth }, (_, index) => `r${index + 1}`)\n const relationshipChain = edgeVariables\n .map((edgeVariable, index) => {\n const targetVariable =\n index === edgeVariables.length - 1 ? 'a' : intermediateVariables[index]!\n return `-[${edgeVariable}:FLOWS_TO]->(${targetVariable}:Address)`\n })\n .join('')\n const intermediatePredicates = intermediateVariables.map(\n (nodeVariable) => `${nodeVariable}.is_exchange IS NULL`\n )\n // Pool trace rule: the trace starts at the screened address `a` and walks\n // back through the intermediates, so none of them may be a :Pool. The\n // exchange end is the target, which may be one.\n const poolGuards = poolGuardTerms(['a', ...intermediateVariables])\n const withdrawalVariable = nodeVariables[1]!\n const terminalEdgeVariable = edgeVariables[edgeVariables.length - 1]!\n return {\n id: `exchange_inflows_${depth}`,\n query: [\n `MATCH (exchange:Address)${relationshipChain}`,\n `WHERE a.address = \"${escapeCypherString(address)}\" AND a.address <> exchange.address AND exchange.is_exchange IS NOT NULL${intermediatePredicates.length > 0 ? ` AND ${intermediatePredicates.join(' AND ')}` : ''} AND ${poolGuards}`,\n `RETURN \"inflow\" AS direction, exchange.address AS exchange_address, exchange.labels AS exchange_display_labels, exchange.labels AS exchange_system_labels, ${withdrawalVariable}.address AS withdrawal_address, ${depth} AS hops, ${terminalEdgeVariable}.amount_usd_sum AS amount_usd_sum, ${terminalEdgeVariable}.tx_count AS tx_count, ${terminalEdgeVariable}.first_seen_timestamp AS first_seen_timestamp, ${terminalEdgeVariable}.last_seen_timestamp AS last_seen_timestamp, [${nodeVariables.map((nodeVariable) => `${nodeVariable}.address`).join(', ')}] AS addresses, [${nodeVariables.map((nodeVariable) => pathNodeMap(nodeVariable)).join(', ')}] AS path_nodes, [${edgeVariables.map(flowEdgeMap).join(', ')}] AS edge_props`,\n 'ORDER BY hops ASC',\n 'LIMIT 200',\n ].join(' '),\n }\n}\n\nfunction connectionProbeQuery(\n address: string,\n compareAddress: string\n): { id: string; query: string } {\n return {\n id: 'connection_probe',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:FLOWS_TO]-(b:Address {address: \"${escapeCypherString(compareAddress)}\"})`,\n 'RETURN [a.address, b.address] AS addresses, 1 AS hops',\n 'LIMIT 5',\n ].join(' '),\n }\n}\n\n// Address-grain has no separate identity-resolution lookup: existence of the\n// compare_address is checked directly against :Address, distinct from the\n// primary address_profile query so both existence checks can run in one batch.\nfunction compareAddressExistsQuery(address: string): { id: string; query: string } {\n return {\n id: 'compare_address_exists',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})`,\n 'RETURN a.address AS address',\n 'LIMIT 1',\n ].join(' '),\n }\n}\n\n// Pool trace rule: the via_linked read is a two-hop walk through `owned`, so\n// `a` and `owned` carry the guard and the target `b` may be a pool. The direct\n// read is a counterparty listing of one address, not a trace, and stays\n// unguarded (ruling: a pool's own counterparty list is a listing).\n// AC11: FLOWS_TO reachability UNIONed over one -[:LINKED]- hop, so an\n// investigator surveying an address's exposure also sees exposure carried by\n// an address that is only ownership-LINKED to it (LINKED is undirected).\nfunction linkedExposureQueries(address: string): Array<{ id: string; query: string }> {\n return [\n {\n id: 'linked_exposure_direct',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:FLOWS_TO]-(b:Address)`,\n 'WHERE a.address <> b.address',\n `RETURN a.address AS subject_address, b.address AS counterparty_address, b.network AS counterparty_network, \"direct\" AS exposure_basis, r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count`,\n 'LIMIT 200',\n ].join(' '),\n },\n {\n id: 'linked_exposure_via_linked',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[l:LINKED]-(owned:Address)-[r:FLOWS_TO]-(b:Address)`,\n 'WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address',\n `RETURN a.address AS subject_address, b.address AS counterparty_address, b.network AS counterparty_network, \"via_linked\" AS exposure_basis, owned.address AS linked_via_address, l.basis AS link_basis, l.confidence AS link_confidence, r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count`,\n 'LIMIT 200',\n ].join(' '),\n },\n ]\n}\n\n// AC5: LINKED ownership-overlay probe. On the single public robinhood graph,\n// :Address.network is a node PROPERTY (robinhood), never a second query\n// network: LINKED is the ownership edge within that one space, served on the\n// topology graph only, with no network switch.\nfunction crossSpaceLinkedQuery(address: string): { id: string; query: string } {\n return {\n id: 'cross_space_linked',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[l:LINKED]-(b:Address)`,\n 'WHERE a.network <> b.network',\n 'RETURN a.address AS address, a.network AS network, b.address AS linked_address, b.network AS linked_network, l.basis AS basis, l.confidence AS confidence, l.source_event AS source_event, l.declared_owner AS declared_owner',\n 'LIMIT 50',\n ].join(' '),\n }\n}\n\n// ── Pairwise route evidence ──\n// Undirected shortest route between two KNOWN identity endpoints. The\n// topology graph speaks ISO GQL, so both routes use one shortest path.\n// Exchange intermediates on a returned route are DISCLOSED in the evidence,\n// never silently filtered out.\n//\n// Pool trace rule (schema skill, \"Pool trace rule\"): a route never starts at\n// a :Pool and never passes through one, and it may end at one. It follows\n// FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient. The\n// guard sits inside the path pattern, so the search finds the shortest route\n// that avoids pools instead of dropping a shortest route that crosses one.\n//\n// The text is the guarded route contract (ruled 2026-09-28), served word for\n// word by skills/chain-insights-cypher, and graphrag-mcp rewrites exactly this\n// shape onto DozerDB's fast shortest-path search (data-pipeline\n// route_rewrite.go). Change the addresses and the RETURN only. The quantifier\n// bound covers the guarded middle; the last hop is the unguarded target end,\n// so a route is 1 to CONNECTION_ROUTE_DEPTH_BOUND + 1 hops. The `{0,...}`\n// lower bound keeps DozerDB off the slow undirected search.\n\nexport const CONNECTION_ROUTE_DEPTH_BOUND = 4\n// A route is the guarded middle plus the last hop to the target end.\nexport const CONNECTION_ROUTE_MAX_HOPS = CONNECTION_ROUTE_DEPTH_BOUND + 1\n\nexport function shouldIncludeRouteQueries(compareAddress: string | undefined): boolean {\n // GQL shortest-path traversal always fires when a compare address is given.\n return Boolean(compareAddress)\n}\n\nexport function connectionRouteQueries(\n address: string,\n compareAddress: string\n): Array<{ id: string; query: string }> {\n const routeQuery = (fromAddress: string, toAddress: string): string =>\n [\n `MATCH p = SHORTEST 1 (a:Address {address: \"${escapeCypherString(fromAddress)}\"} WHERE NOT a:Pool)`,\n ` (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,${CONNECTION_ROUTE_DEPTH_BOUND}}`,\n ` ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: \"${escapeCypherString(toAddress)}\"}) RETURN p LIMIT 1`,\n ].join('')\n return [\n { id: 'connection_route_outbound', query: routeQuery(address, compareAddress) },\n { id: 'connection_route_inbound', query: routeQuery(compareAddress, address) },\n ]\n}\n\nexport interface RouteEvidenceSide {\n hops: number\n identities: string[]\n exchange_intermediates: string[]\n amount_usd_sum_total: number\n}\n\nfunction collectOrdered(\n value: unknown,\n matches: (candidate: Record<string, unknown>) => boolean\n): Array<Record<string, unknown>> {\n const collected: Array<Record<string, unknown>> = []\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const entry of node) walk(entry)\n return\n }\n if (node && typeof node === 'object') {\n const candidate = node as Record<string, unknown>\n if (matches(candidate)) collected.push(candidate)\n for (const nested of Object.values(candidate)) walk(nested)\n }\n }\n walk(value)\n return collected\n}\n\n// is_exchange on topology nodes is a MARKER, not a boolean: it usually\n// carries the exchange name (e.g. \"binance\"). Treat explicit falsy\n// encodings as not-exchange (a bare non-null check would falsely disclose\n// nodes serialized as false/\"false\"/0), and any other non-empty value as\n// exchange. isExchangeFlag() is NOT reused here — it is boolean-only and\n// would miss name-valued markers.\nexport function isExchangeMarker(value: unknown): boolean {\n if (value === null || value === undefined || value === false) return false\n if (typeof value === 'number') return value !== 0\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase()\n return (\n normalized !== '' && normalized !== 'false' && normalized !== '0' && normalized !== 'null'\n )\n }\n return value === true\n}\n\n// Shape-tolerant hydrated-path reader: walks the returned path value for\n// ordered address nodes and FLOWS_TO edge amounts without pinning the\n// backend's exact path envelope.\nexport function routeFromPathValue(value: unknown): RouteEvidenceSide | null {\n if (!value || typeof value !== 'object') return null\n const nodes = collectOrdered(value, (candidate) => typeof candidate['address'] === 'string')\n if (nodes.length < 2) return null\n const edges = collectOrdered(\n value,\n (candidate) => typeof candidate['amount_usd_sum'] === 'number' && !('address' in candidate)\n )\n const identities = nodes.map((node) => String(node['address']))\n const exchangeIntermediates = nodes\n .slice(1, -1)\n .filter((node) => isExchangeMarker(node['is_exchange']))\n .map((node) => String(node['address']))\n return {\n // Hop count is a structural property of the node sequence — never\n // derived from edge-object detection, which keys on numeric\n // amount_usd_sum and would under-report on partially hydrated edges.\n hops: nodes.length - 1,\n identities,\n exchange_intermediates: exchangeIntermediates,\n amount_usd_sum_total: edges.reduce((total, edge) => total + Number(edge['amount_usd_sum']), 0),\n }\n}\n\nexport interface RouteEvidence {\n search_strategy: 'any_shortest'\n route_rank_basis: 'hop_count'\n depth_bound: number\n route_found: boolean\n outbound: RouteEvidenceSide | null\n inbound: RouteEvidenceSide | null\n}\n\nexport function buildRouteEvidence(\n outboundRows: Array<Record<string, unknown>>,\n inboundRows: Array<Record<string, unknown>>\n): RouteEvidence {\n const sideFrom = (rows: Array<Record<string, unknown>>): RouteEvidenceSide | null => {\n const first = rows[0]\n if (!first) return null\n const pathValue = 'p' in first ? first['p'] : Object.values(first)[0]\n return routeFromPathValue(pathValue)\n }\n const outbound = sideFrom(outboundRows)\n const inbound = sideFrom(inboundRows)\n return {\n search_strategy: 'any_shortest',\n route_rank_basis: 'hop_count',\n depth_bound: CONNECTION_ROUTE_MAX_HOPS,\n route_found: outbound !== null || inbound !== null,\n outbound,\n inbound,\n }\n}\n\nfunction formatExchangeRows(rows: Array<Record<string, unknown>>): string[] {\n return rows.map((row) => {\n const direction = String(row['direction'] ?? 'flow')\n const exchange = String(row['exchange_address'] ?? '')\n const amount = row['amount_usd_sum'] ?? ''\n const hops = row['hops'] ?? ''\n return `- ${direction}: ${exchange} (${hops} hop(s), amount_usd_sum ${amount})`\n })\n}\n\nfunction numberValue(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\nfunction firstNumber(...values: unknown[]): number | undefined {\n for (const value of values) {\n const parsed = numberValue(value)\n if (parsed !== undefined) return parsed\n }\n return undefined\n}\n\nfunction firstString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value === 'string' && value.trim()) return value.trim()\n }\n return undefined\n}\n\nfunction riskLevelFromScore(score: number): string {\n if (score >= 0.85) return 'critical'\n if (score >= 0.7) return 'high'\n if (score >= 0.4) return 'medium'\n return 'low'\n}\n\nfunction riskRecommendation(level: string): string {\n if (level === 'critical' || level === 'high') return 'Escalate for manual review.'\n if (level === 'medium') return 'Review exchange exposure and counterparties before clearing.'\n return 'No stored risk signal found; continue with normal monitoring.'\n}\n\nfunction riskDrivers(\n profile: Record<string, unknown>,\n labelRows: Array<Record<string, unknown>>,\n exchangeRows: Array<Record<string, unknown>>\n): string[] {\n const drivers: string[] = []\n\n const riskLabels = labelRows\n .map((row) => firstString(row['label']))\n .filter((label): label is string => Boolean(label))\n if (riskLabels.length > 0) drivers.push(`Labels: ${[...new Set(riskLabels)].join('; ')}`)\n\n const outflowCount = exchangeRows.filter((row) => row['direction'] === 'outflow').length\n const inflowCount = exchangeRows.filter((row) => row['direction'] === 'inflow').length\n if (outflowCount > 0)\n drivers.push(`Forward bounded search reached ${outflowCount} exchange path(s).`)\n if (inflowCount > 0)\n drivers.push(`Backward bounded search found ${inflowCount} source exchange path(s).`)\n\n return [...new Set(drivers)]\n}\n\n// Derives the deterministic labelRows subset from the profile row's three\n// label_risk_* lists (topology), reproducing the retired\n// addressLabelRiskQuery's `ORDER BY label.updated_timestamp DESC LIMIT 10`\n// subset byte-for-byte: same sort, same cap. The lists are parallel: entry i\n// of each one describes the same label row. Missing/empty lists -> empty\n// array, same as \"no labels\" behaved under the old facts read.\nfunction deriveLabelRows(profile: Record<string, unknown>): Array<Record<string, unknown>> {\n // Dozer/Neo4j properties cannot store lists of maps. Sync therefore\n // materializes the three map fields as parallel primitive arrays.\n const labels = Array.isArray(profile['label_risk_labels'])\n ? (profile['label_risk_labels'] as unknown[]).map((value) => String(value))\n : []\n const riskLevels = Array.isArray(profile['label_risk_levels'])\n ? (profile['label_risk_levels'] as unknown[]).map((value) => String(value))\n : []\n const timestamps = Array.isArray(profile['label_risk_updated_timestamps'])\n ? (profile['label_risk_updated_timestamps'] as unknown[]).map((value) => numberValue(value))\n : []\n const rows = labels.map((label, index) => ({\n label,\n risk_level: riskLevels[index] ?? '',\n updated_timestamp: timestamps[index] ?? 0,\n }))\n return [...rows]\n .sort(\n (a, b) =>\n (numberValue(b['updated_timestamp']) ?? 0) - (numberValue(a['updated_timestamp']) ?? 0)\n )\n .slice(0, 10)\n}\n\nconst RISK_LEVEL_ORDER = ['critical', 'high', 'medium', 'low'] as const\n\nfunction strongestLabelRiskLevel(labelRows: Array<Record<string, unknown>>): string | undefined {\n const levels = labelRows\n .map((row) => firstString(row['risk_level'])?.toLowerCase())\n .filter((level): level is string =>\n Boolean(level && (RISK_LEVEL_ORDER as readonly string[]).includes(level))\n )\n if (levels.length === 0) return undefined\n return RISK_LEVEL_ORDER.find((candidate) => levels.includes(candidate))\n}\n\nfunction riskScoreSources(\n profile: Record<string, unknown>,\n labelRows: Array<Record<string, unknown>>\n): Array<Record<string, unknown>> {\n const sources: Array<Record<string, unknown>> = []\n if (numberValue(profile['live_risk_score']) !== undefined) {\n sources.push({\n family: 'ml_risk_score',\n layer: 'topology',\n source: 'address_node',\n fields: ['risk_score', 'risk_level'],\n })\n }\n if (labelRows.length > 0) {\n sources.push({\n family: 'label_risk',\n layer: 'topology',\n source: 'address_node',\n labels: labelRows.map((row) => ({\n label: row['label'],\n risk_level: row['risk_level'],\n })),\n })\n }\n return sources\n}\n\nfunction terminalEdgeProperties(row: Record<string, unknown>): Record<string, unknown> | undefined {\n const edgeProps = Array.isArray(row['edge_props'])\n ? (row['edge_props'] as Array<Record<string, unknown>>)\n : []\n return edgeProps[edgeProps.length - 1]\n}\n\nfunction enrichExchangeRows(rows: Array<Record<string, unknown>>): Array<Record<string, unknown>> {\n return rows.map((row) => {\n const terminal = terminalEdgeProperties(row)\n if (!terminal) return row\n return {\n ...row,\n amount_usd_sum: row['amount_usd_sum'] ?? terminal['amount_usd_sum'],\n tx_count: row['tx_count'] ?? terminal['tx_count'],\n first_seen_timestamp: row['first_seen_timestamp'] ?? terminal['first_seen_timestamp'],\n last_seen_timestamp: row['last_seen_timestamp'] ?? terminal['last_seen_timestamp'],\n }\n })\n}\n\nconst FALLBACK_SHARED_TX_COUNT = 1000\nconst FALLBACK_SHARED_USD_SUM = 5_000_000\nconst FALLBACK_SHARED_DAMPING = 0.1\nconst FALLBACK_VALUE_SATURATION_USD = 1_000_000\nconst FALLBACK_BASE_SCORE = 0.1\nconst FALLBACK_MAX_SCORE = 0.6\n\n/**\n * Score exchange exposure when no ML risk score exists. Topology-grain:\n * log-scaled USD volume across exchange rows, with shared/omnibus edges\n * (high tx_count or USD throughput) dampened, bounded in (0, 0.6] so a\n * fallback can never impersonate a high ML score band.\n */\nexport function exchangeExposureFallbackScore(\n exchangeRows: Array<Record<string, unknown>>\n): number {\n if (exchangeRows.length === 0) return 0\n let weightedUsd = 0\n for (const row of exchangeRows) {\n const usd = numberValue(row['amount_usd_sum']) ?? 0\n const txCount = numberValue(row['tx_count']) ?? 0\n const shared = txCount >= FALLBACK_SHARED_TX_COUNT || usd >= FALLBACK_SHARED_USD_SUM\n weightedUsd += shared ? usd * FALLBACK_SHARED_DAMPING : usd\n }\n const capped = Math.min(weightedUsd, FALLBACK_VALUE_SATURATION_USD)\n const factor = Math.log10(1 + capped) / Math.log10(1 + FALLBACK_VALUE_SATURATION_USD)\n return Math.min(\n FALLBACK_MAX_SCORE,\n FALLBACK_BASE_SCORE + (FALLBACK_MAX_SCORE - FALLBACK_BASE_SCORE) * factor\n )\n}\n\nexport function riskAssessment(\n profile: Record<string, unknown>,\n labelRows: Array<Record<string, unknown>>,\n exchangeRows: Array<Record<string, unknown>>\n): Record<string, unknown> {\n const mlRiskScore = firstNumber(profile['live_risk_score'])\n // UNSCORED is the model's explicit abstention (calibrated-scoring release):\n // the score exists for transparency but carries no stance — deriving a\n // severity band from it would silently launder an abstention into a\n // confident verdict (typically \"low\"). Fall back to labels/exchange\n // exposure and say so.\n const mlAbstained = isUnscoredRiskLevel(profile['live_risk_level'])\n const labelRiskLevel = strongestLabelRiskLevel(labelRows)\n const usableMlScore = mlAbstained ? undefined : mlRiskScore\n const score = usableMlScore ?? exchangeExposureFallbackScore(exchangeRows)\n let level =\n labelRiskLevel ??\n (mlAbstained && exchangeRows.length === 0 ? 'unscored' : riskLevelFromScore(score))\n const drivers = riskDrivers(profile, labelRows, exchangeRows)\n if (mlAbstained)\n drivers.push(\n 'ml_abstained: ML verdict is UNSCORED (insufficient labeled graph context); level derived from labels/exchange exposure only'\n )\n // Labels are curated truth and stay first — but a lower-severity label\n // must never SUPPRESS a more severe usable ML band. Failing toward\n // \"looks safe\" is the one direction an AML triage tool may not fail.\n const usableMlBand = usableMlScore !== undefined ? riskLevelFromScore(usableMlScore) : undefined\n if (\n labelRiskLevel &&\n usableMlBand &&\n riskSeverityRank(usableMlBand) > riskSeverityRank(labelRiskLevel)\n ) {\n level = usableMlBand\n drivers.push(\n `ml_label_divergence: usable ML band ${usableMlBand} exceeds strongest label level ${labelRiskLevel}; reporting the more severe band — review the label`\n )\n }\n return {\n level,\n score,\n ...(mlRiskScore !== undefined ? { ml_risk_score: mlRiskScore } : {}),\n ...(mlAbstained ? { ml_verdict: 'unscored' } : {}),\n confidence:\n usableMlScore !== undefined || labelRiskLevel\n ? 'high'\n : exchangeRows.length > 0\n ? 'medium'\n : 'low',\n recommendation:\n level === 'unscored'\n ? 'Model abstained and no label/exchange signal found; gather more context before clearing.'\n : riskRecommendation(level),\n drivers,\n sources: riskScoreSources(profile, labelRows),\n }\n}\n\nfunction formatRiskScore(score: unknown): string {\n const parsed = numberValue(score)\n if (parsed === undefined) return String(score ?? 'unknown')\n return Number.isInteger(parsed) ? parsed.toString() : parsed.toFixed(2)\n}\n\nfunction stringArrayValue(value: unknown): string[] | undefined {\n if (Array.isArray(value)) return value.map(String)\n if (typeof value === 'string' && value.trim()) return [value]\n return undefined\n}\n\nfunction restoreSystemLabels(\n graph: Record<string, unknown>,\n rawNodes: Array<Record<string, unknown>>\n): Record<string, unknown> {\n if (!Array.isArray(graph['nodes'])) return graph\n const labelsByAddress = new Map(\n rawNodes\n .map(\n (node) =>\n [\n typeof node['address'] === 'string'\n ? node['address']\n : typeof node['id'] === 'string'\n ? node['id']\n : '',\n stringArrayValue(node['system_labels']),\n ] as const\n )\n .filter(\n (entry): entry is readonly [string, string[]] =>\n Boolean(entry[0]) && Array.isArray(entry[1]) && entry[1].length > 0\n )\n )\n return {\n ...graph,\n nodes: graph['nodes'].map((node) => {\n if (typeof node !== 'object' || node === null || Array.isArray(node)) return node\n const record = node as Record<string, unknown>\n const address =\n typeof record['address'] === 'string'\n ? record['address']\n : typeof record['id'] === 'string'\n ? record['id']\n : ''\n const systemLabels = labelsByAddress.get(address)\n return systemLabels ? { ...record, system_labels: systemLabels } : record\n }),\n }\n}\n\nfunction buildRiskGraph(\n address: string,\n profile: Record<string, unknown>,\n rows: Array<Record<string, unknown>>,\n network: string\n): Record<string, unknown> {\n const nodes = new Map<string, Record<string, unknown>>()\n nodes.set(address, {\n id: address,\n address,\n node_type: 'address',\n labels: stringArrayValue(profile['display_labels']) ?? [],\n ...(stringArrayValue(profile['system_labels'])\n ? { system_labels: stringArrayValue(profile['system_labels']) }\n : {}),\n ...(stringArrayValue(profile['member_addresses'])?.length\n ? { member_addresses: stringArrayValue(profile['member_addresses']) }\n : {}),\n ...(numberValue(profile['live_risk_score']) !== undefined\n ? { risk_score: numberValue(profile['live_risk_score']) }\n : {}),\n ...(firstString(profile['live_risk_level'])\n ? { risk_level: firstString(profile['live_risk_level']) }\n : {}),\n roles: ['subject'],\n })\n const edges: Array<Record<string, unknown>> = []\n const mergeNode = (entry: string, metadata?: Record<string, unknown>) => {\n const existing = nodes.get(entry) ?? {\n id: entry,\n address: entry,\n node_type: 'address',\n labels: [],\n }\n const labels = stringArrayValue(metadata?.['labels']) ?? existing['labels']\n const systemLabels = stringArrayValue(metadata?.['system_labels']) ?? existing['system_labels']\n const memberAddresses =\n stringArrayValue(metadata?.['member_addresses']) ?? existing['member_addresses']\n const riskScore = numberValue(metadata?.['risk_score']) ?? existing['risk_score']\n const riskLevel =\n normalizeRiskLevel(firstString(metadata?.['risk_level'])) ?? existing['risk_level']\n nodes.set(entry, {\n ...existing,\n labels,\n ...(systemLabels ? { system_labels: systemLabels } : {}),\n ...(Array.isArray(memberAddresses) && memberAddresses.length > 0\n ? { member_addresses: memberAddresses }\n : {}),\n ...(riskScore !== undefined ? { risk_score: riskScore } : {}),\n ...(riskLevel ? { risk_level: riskLevel } : {}),\n })\n }\n for (const row of rows) {\n const rawPath = Array.isArray(row['path']) ? row['path'] : row['addresses']\n const path = Array.isArray(rawPath) ? rawPath.map(String) : []\n const pathNodes = Array.isArray(row['path_nodes'])\n ? (row['path_nodes'] as Array<Record<string, unknown>>)\n : []\n for (let index = 0; index < path.length; index += 1) {\n const entry = path[index]!\n mergeNode(entry, pathNodes[index])\n }\n const exchange = typeof row['exchange_address'] === 'string' ? row['exchange_address'] : ''\n if (exchange) {\n const displayLabels = stringArrayValue(row['exchange_display_labels']) ?? []\n const systemLabels =\n stringArrayValue(row['exchange_system_labels']) ??\n stringArrayValue(row['exchange_labels']) ??\n []\n nodes.set(exchange, {\n id: exchange,\n address: exchange,\n node_type: 'address',\n labels: displayLabels,\n ...(systemLabels.length > 0 ? { system_labels: systemLabels } : {}),\n roles: ['exchange'],\n })\n }\n for (let index = 0; index < path.length - 1; index += 1) {\n const edgeProps = Array.isArray(row['edge_props'])\n ? (row['edge_props'] as Array<Record<string, unknown>>)\n : []\n const edge = edgeProps[index] ?? row\n edges.push({\n source: path[index],\n target: path[index + 1],\n edge_type: 'flows_to',\n usd_amount: edge['amount_usd_sum'] ?? 0,\n amount_usd_sum: edge['amount_usd_sum'] ?? 0,\n tx_count: edge['tx_count'] ?? 0,\n direction: row['direction'],\n })\n }\n }\n const rawNodes = [...nodes.values()]\n return restoreSystemLabels(\n normalizeGraphPayload({\n schema: 'chain-insights.graph.v1',\n nodes: rawNodes,\n edges,\n flows: [],\n edge_anchors: [],\n metadata: { address, network, generated_at: new Date().toISOString() },\n }),\n rawNodes\n )\n}\n\nexport async function addressRisk(\n remoteClient: Client,\n options: AddressRiskOptions\n): Promise<{\n summaryText: string\n structuredContent: Record<string, unknown>\n graphData: Record<string, unknown>\n}> {\n const address = options.address.trim()\n const network = options.network.trim()\n const compareInput = options.compareAddress?.trim() ?? ''\n if (!address) throw new Error('address is required')\n if (!network) throw new Error('network is required')\n\n // Every internal graph_query_batch round trip this workflow makes is\n // observed here and totaled into a usage block on the response (never\n // blocks/throws on a backend that doesn't emit billing fields yet).\n const usage = createUsageAccumulator()\n const trackedClient = wrapClientForUsageTracking(remoteClient, usage)\n\n // Address-grain has no separate resolution step for the SUBJECT address:\n // the input address IS the graph node key. Its existence is inferred\n // post-hoc from the address_profile row (below) instead of the pre-revert\n // pre-flight lookup -- deliberate: it saves a round trip on the hot path.\n // The COMPARE address keeps a pre-flight existence probe, because the\n // connection probe and shortest-path route queries must be SUPPRESSED (not\n // ignored) when the compare address does not exist -- pre-revert behavior\n // never issued route probes for an unresolved compare input.\n let compareUnresolved = false\n if (compareInput) {\n const compareBatch = await callGraphBatch(trackedClient, network, [\n compareAddressExistsQuery(compareInput),\n ])\n const compareRows = optionalResultsFor(compareBatch, 'compare_address_exists', [])\n compareUnresolved = !firstString(compareRows[0]?.['address'])\n }\n const compareAddress = compareInput && !compareUnresolved ? compareInput : ''\n\n const queries = [\n addressProfileQuery(address),\n addressFeatureQuery(address),\n { id: 'money_trail_incident', query: moneyTrailIncidentQuery(address) },\n ...exchangeOutflowQueries(address),\n ...exchangeInflowQueries(address),\n ...(compareAddress\n ? [connectionProbeQuery(address, compareAddress)]\n : [\n {\n id: 'connection_probe',\n query:\n 'MATCH (n:Address {address: \"__chain_insights_noop__\"}) RETURN n.address AS noop LIMIT 0',\n },\n ]),\n // Route evidence is additive: ISO GQL shortest-path traversal always\n // fires for a compare address whose existence probe succeeded (the\n // 1-hop probe above always runs).\n ...(shouldIncludeRouteQueries(compareAddress)\n ? connectionRouteQueries(address, compareAddress)\n : []),\n ]\n const batch = await callGraphBatch(trackedClient, network, queries)\n const partialQueryFailures: QueryFailure[] = []\n // Deliberate post-hoc existence inference (address grain): an empty\n // address_profile result means the subject :Address does not exist ->\n // report unresolved; this replaces the pre-revert identity pre-flight.\n const addressProfileRows = optionalResultsFor(batch, 'address_profile', partialQueryFailures)\n if (addressProfileRows.length === 0) {\n return {\n summaryText: `Address risk for ${network}:${address}\\n\\nUnresolved: no Address found for \"${address}\". The address may not have on-chain activity in this network.`,\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: 'aml_address_risk',\n facts: {\n subject: { network, addresses: [address] },\n unresolved: [address],\n usage: usageBlock(usage),\n },\n },\n graphData: {\n schema: 'chain-insights.graph.v1',\n nodes: [],\n edges: [],\n flows: [],\n edge_anchors: [],\n metadata: { address, network, generated_at: new Date().toISOString() },\n },\n }\n }\n const profile: Record<string, unknown> = {\n address,\n ...(addressProfileRows[0] ?? {}),\n ...(optionalResultsFor(batch, 'address_feature', partialQueryFailures)[0] ?? {}),\n }\n const labelRows = deriveLabelRows(profile)\n const outflows = enrichExchangeRows(\n optionalResultsWithPrefix(batch, 'exchange_outflows_', partialQueryFailures)\n )\n const inflows = enrichExchangeRows(\n optionalResultsWithPrefix(batch, 'exchange_inflows_', partialQueryFailures)\n )\n const connections = compareAddress\n ? optionalResultsFor(batch, 'connection_probe', partialQueryFailures)\n : []\n const routeEvidence = shouldIncludeRouteQueries(compareAddress)\n ? buildRouteEvidence(\n optionalResultsFor(batch, 'connection_route_outbound', partialQueryFailures),\n optionalResultsFor(batch, 'connection_route_inbound', partialQueryFailures)\n )\n : undefined\n const exchangeRows = [...outflows, ...inflows]\n // A hop-depth exchange_outflows_N/exchange_inflows_N query can fail\n // independently (e.g. the archive-tier query-memory limit on a deep\n // multi-hop search) while other hop depths succeed with zero rows. An\n // empty exchangeRows result must then read as \"search incomplete,\" not\n // \"no exchange exposure\" -- the two are not the same claim, and an AML\n // tool that silently reports a false-clean verdict on a partial search\n // failure is a real risk (found during MoA review, 2026-07-05).\n const exchangeSearchFailures = partialQueryFailures.filter(\n (failure) =>\n failure.id.startsWith('exchange_outflows_') || failure.id.startsWith('exchange_inflows_')\n )\n const exchangeSearchComplete = exchangeSearchFailures.length === 0\n // money-trail enrichment (optional): a preliminary block resolves the\n // primary_seed off the incident-only rows, then a second batch call fans\n // out TRAIL_ENDS_AT for that seed so the block can pick the highest-value\n // terminal fact. Both reads are optional-results -- a graph with no\n // money-trail layer never fails the tool.\n const moneyTrailIncidentRows = optionalResultsFor(\n batch,\n 'money_trail_incident',\n partialQueryFailures\n )\n const preliminaryMoneyTrail = buildMoneyTrailBlock(moneyTrailIncidentRows, [])\n let moneyTrailEndRows: Array<Record<string, unknown>> = []\n if (preliminaryMoneyTrail?.primary_seed) {\n try {\n const endsBatch = await callGraphBatch(trackedClient, network, [\n { id: 'money_trail_ends', query: moneyTrailEndsQuery(preliminaryMoneyTrail.primary_seed) },\n ])\n moneyTrailEndRows = optionalResultsFor(endsBatch, 'money_trail_ends', partialQueryFailures)\n } catch (error) {\n // Degrade, don't fail: a transport/parse error on the second batch call\n // (unlike a query-level ok:false, which optionalResultsFor already\n // absorbs) must not blow up the whole tool -- the incident rows are\n // already in hand, so the block still reports on_trail without\n // nearest_trail_end.\n collectQueryFailure(\n partialQueryFailures,\n 'money_trail_ends',\n error instanceof Error ? error.message : String(error)\n )\n }\n }\n const moneyTrail = buildMoneyTrailBlock(moneyTrailIncidentRows, moneyTrailEndRows)\n const graphData = buildRiskGraph(address, profile, exchangeRows, network)\n const risk = riskAssessment(profile, labelRows, exchangeRows)\n const liveRiskScore = numberValue(profile['live_risk_score'])\n const liveRiskLevel = firstString(profile['live_risk_level'])\n const liveNodeVerdict =\n liveRiskScore !== undefined || liveRiskLevel\n ? {\n ...(liveRiskScore !== undefined ? { risk_score: liveRiskScore } : {}),\n ...(liveRiskLevel ? { risk_level: liveRiskLevel } : {}),\n source: 'topology_node',\n }\n : undefined\n\n const lines = [\n `Address risk for ${network}:${address}`,\n '',\n `Risk: ${risk['level']} (${formatRiskScore(risk['score'])})`,\n `Confidence: ${risk['confidence']}`,\n `Recommendation: ${risk['recommendation']}`,\n ...(liveNodeVerdict\n ? [`Live node triage: ${liveRiskLevel ?? 'unknown'} (${formatRiskScore(liveRiskScore)})`]\n : []),\n `Graph degree: in ${profile['degree_in'] ?? 'unknown'}, out ${profile['degree_out'] ?? 'unknown'}.`,\n '',\n 'Exchange behavior',\n ...(exchangeRows.length > 0\n ? [\n formatExchangeRows(exchangeRows).join('\\n'),\n ...(exchangeSearchComplete\n ? []\n : [\n `(incomplete: ${exchangeSearchFailures.length} other hop-depth quer${exchangeSearchFailures.length === 1 ? 'y' : 'ies'} failed -- there may be more exchange exposure than shown here)`,\n ]),\n ]\n : [\n exchangeSearchComplete\n ? '- No exchange inflow/outflow paths found in bounded search.'\n : `- Exchange search incomplete: ${exchangeSearchFailures.length} hop-depth quer${exchangeSearchFailures.length === 1 ? 'y' : 'ies'} failed before returning a result. This is NOT a clean finding -- retry or narrow the search (see Partial query failures below).`,\n ]),\n ]\n if (Array.isArray(risk['drivers']) && risk['drivers'].length > 0) {\n lines.push('', 'Risk drivers', risk['drivers'].map((driver) => `- ${driver}`).join('\\n'))\n }\n if (moneyTrail) {\n lines.push('', moneyTrailSummarySentence(moneyTrail))\n }\n if (compareAddress) {\n lines.push(\n '',\n `Connection compare target: ${compareAddress}`,\n connections.length > 0\n ? `Connection paths found: ${connections.length}`\n : 'Connection paths found: 0'\n )\n }\n if (compareUnresolved) {\n lines.push(\n '',\n `Unresolved compare_address: no Address found for \"${compareInput}\"; comparison skipped.`\n )\n }\n if (partialQueryFailures.length > 0) {\n lines.push(\n '',\n 'Partial query failures',\n partialQueryFailures.map((failure) => `- ${failure.id}: ${failure.error}`).join('\\n')\n )\n }\n const summaryText = lines.join('\\n')\n const structuredContent = {\n schema: 'chain-insights.result.v1',\n tool: 'aml_address_risk',\n facts: {\n subject: {\n network,\n addresses: compareAddress ? [address, compareAddress] : [address],\n },\n risk: {\n ...risk,\n ...(liveNodeVerdict ? { live_node: liveNodeVerdict } : {}),\n },\n exchange_behavior: {\n outflows,\n inflows,\n search_status: exchangeSearchComplete ? 'complete' : 'incomplete',\n ...(exchangeSearchComplete\n ? {}\n : { failed_query_ids: exchangeSearchFailures.map((failure) => failure.id) }),\n },\n connection: compareAddress\n ? {\n compare_address: compareAddress,\n paths: connections,\n ...(routeEvidence ? { route_evidence: routeEvidence } : {}),\n }\n : undefined,\n money_trail: moneyTrail,\n unresolved: compareUnresolved ? [compareInput] : undefined,\n partial_query_errors: partialQueryFailures.length > 0 ? partialQueryFailures : undefined,\n usage: usageBlock(usage),\n },\n }\n const artifacts = options.writeArtifacts\n ? await writeAddressRiskArtifacts(\n network,\n address,\n compareAddress,\n graphData,\n exchangeRows,\n summaryText\n )\n : statelessArtifacts()\n const evidence = artifactEvidence(artifacts)\n\n return {\n summaryText,\n structuredContent: {\n ...structuredContent,\n artifacts,\n evidence: [\n ...evidence,\n {\n evidence_type: 'tool_summary',\n summary: `aml_address_risk ${address} completed for ${network}`,\n },\n ],\n },\n graphData,\n }\n}\n\n/**\n * Routes the public AML tool contract to the requested implementation. An\n * omitted version always follows the latest supported contract.\n */\nexport async function runAmlAddressRisk(\n remoteClient: Client,\n options: AddressRiskOptions,\n requestedVersion?: string\n) {\n const version = resolveAmlAddressRiskVersion(requestedVersion)\n switch (version) {\n case 'v1': {\n const result = await addressRisk(remoteClient, options)\n return {\n ...result,\n structuredContent: {\n ...result.structuredContent,\n tool_version: version,\n },\n }\n }\n }\n}\n\nfunction toCsvValue(value: unknown): string {\n return value === undefined || value === null ? '' : String(value)\n}\n\nfunction subjectNodeForExchangeRow(row: Record<string, unknown>, fallbackAddress: string): string {\n return String(\n row['direction'] === 'inflow'\n ? (row['withdrawal_address'] ?? row['deposit_address'] ?? fallbackAddress)\n : (row['deposit_address'] ?? row['withdrawal_address'] ?? fallbackAddress)\n )\n}\n\nfunction buildAddressRiskTableHtml(\n tool: string,\n network: string,\n rows: Array<Record<string, unknown>>,\n subject: string\n): string {\n const headers = [\n 'direction',\n 'exchange_address',\n 'subject_path_node',\n 'hops',\n 'amount_usd_sum',\n 'tx_count',\n ] as const\n const body = rows\n .map((row) => {\n const exchangeAddress = String(row['exchange_address'] ?? '')\n const subjectNode = subjectNodeForExchangeRow(row, subject)\n const rowValues = [\n row['direction'] ?? '',\n exchangeAddress,\n subjectNode,\n row['hops'] ?? '',\n row['amount_usd_sum'] ?? '',\n row['tx_count'] ?? '',\n ]\n return `<tr>${rowValues.map((value) => `<td>${htmlEscape(toCsvValue(value))}</td>`).join('')}</tr>`\n })\n .join('\\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\">\n<title>${htmlEscape(tool)} Risk Table</title>\n<style>\n :root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui; background: #0b0d12; color: #f4f2ea; }\n body { margin: 0; background: #0b0d12; color: #f4f2ea; }\n main { padding: 24px; }\n h1 { font-size: 20px; margin: 0 0 8px; font-weight: 650; }\n table { border-collapse: collapse; width: 100%; min-width: 900px; }\n th, td { border-bottom: 1px solid rgba(255,255,255,.08); padding: 8px 10px; text-align: left; }\n th { position: sticky; top: 0; background: #161a24; color: #f2dda6; font-weight: 600; }\n</style>\n</head>\n<body>\n<main>\n <h1>${htmlEscape(tool)} Table</h1>\n <div>Network: <strong>${htmlEscape(network)}</strong></div>\n <div>Generated: <strong>${htmlEscape(new Date().toISOString())}</strong></div>\n <table>\n <thead><tr>${headers.map((header) => `<th>${htmlEscape(header)}</th>`).join('')}</tr></thead>\n <tbody>${body}</tbody>\n </table>\n</main>\n</body>\n</html>\n`\n}\n\nasync function writeAddressRiskArtifacts(\n network: string,\n address: string,\n compareAddress: string | undefined,\n graphData: Record<string, unknown>,\n exchangeRows: Array<Record<string, unknown>>,\n summaryText: string\n): Promise<Record<string, string>> {\n const paths = workspaceOutputPaths()\n await Promise.all([\n mkdir(paths.reportsRoot, { recursive: true }),\n mkdir(paths.reportGraphsRoot, { recursive: true }),\n mkdir(paths.reportTablesRoot, { recursive: true }),\n ])\n const safeNetwork = network.replace(/[^A-Za-z0-9._-]+/g, '_')\n const safeAddress = address.replace(/[^A-Za-z0-9._-]+/g, '_')\n const slug = `${new Date()\n .toISOString()\n .replace(/[-:]/g, '')\n .replace(/\\.\\d{3}Z$/, 'Z')}_aml_address_risk_${safeNetwork}_${safeAddress}`\n const graphPath = path.join(paths.reportGraphsRoot, `${slug}.graph.json`)\n const tableJsonPath = path.join(paths.reportTablesRoot, `${slug}.compact-evidence.json`)\n const csvPath = path.join(paths.reportTablesRoot, `${slug}.flows.csv`)\n const tableHtmlPath = path.join(paths.reportsRoot, `${slug}.table.html`)\n const reportPath = path.join(paths.reportsRoot, `${slug}.aml-address-report.md`)\n const graphHtmlPath = path.join(paths.reportsRoot, `${slug}.graph.html`)\n const { generateInlineGraphHtml } = await import('../viz/html-generator.js')\n const header = [\n 'direction',\n 'exchange_address',\n 'subject_path_node',\n 'hops',\n 'amount_usd_sum',\n 'tx_count',\n ]\n const csv =\n [\n header.join(','),\n ...exchangeRows.map((row) => {\n const exchangeAddress = String(row['exchange_address'] ?? '')\n const subjectPathNode = subjectNodeForExchangeRow(row, address)\n return [\n row['direction'] ?? '',\n exchangeAddress,\n subjectPathNode,\n row['hops'] ?? '',\n row['amount_usd_sum'] ?? '',\n row['tx_count'] ?? '',\n ]\n .map((value) => JSON.stringify(String(value)))\n .join(',')\n }),\n ].join('\\n') + '\\n'\n const evidence = {\n schema: 'chain-insights.trace.v1',\n tool: 'aml_address_risk',\n network,\n input: {\n address,\n ...(compareAddress ? { compare_address: compareAddress } : {}),\n },\n profile: {\n exchange_rows: exchangeRows.map((row) => ({\n direction: row['direction'],\n exchange_address: row['exchange_address'],\n subject_node: subjectNodeForExchangeRow(row, address),\n hops: row['hops'],\n amount_usd_sum: row['amount_usd_sum'],\n tx_count: row['tx_count'],\n })),\n report_summary: summaryText,\n },\n }\n await writeFile(graphPath, JSON.stringify(graphData, null, 2) + '\\n', { mode: 0o600 })\n await writeFile(tableJsonPath, JSON.stringify(evidence, null, 2) + '\\n', { mode: 0o600 })\n await writeFile(csvPath, csv, { mode: 0o600 })\n await writeFile(\n tableHtmlPath,\n buildAddressRiskTableHtml('aml_address_risk', network, exchangeRows, address),\n { mode: 0o600 }\n )\n await writeFile(graphHtmlPath, generateInlineGraphHtml(graphData), { mode: 0o600 })\n await writeFile(\n reportPath,\n [\n `# Address Risk Report (${network}:${address})`,\n `- Graph JSON: ${graphPath}`,\n `- Table JSON: ${tableJsonPath}`,\n `- CSV: ${csvPath}`,\n `- Report HTML: ${tableHtmlPath}`,\n `- Graph HTML: ${graphHtmlPath}`,\n '',\n summaryText,\n ].join('\\n'),\n { mode: 0o600 }\n )\n\n return {\n graph_json: graphPath,\n graph_html: graphHtmlPath,\n table_json: tableJsonPath,\n flows_csv: csvPath,\n table_html: tableHtmlPath,\n report_md: reportPath,\n }\n}\n\nfunction statelessArtifacts(): Record<string, unknown> {\n return {\n artifacts_written: false,\n artifact_mode: 'stateless',\n }\n}\n\nfunction artifactEvidence(artifacts: Record<string, unknown>): Array<Record<string, unknown>> {\n return Object.entries(artifacts)\n .filter(\n (entry): entry is [string, string] =>\n entry[0] !== 'artifact_mode' && typeof entry[1] === 'string' && entry[1].length > 0\n )\n .map(([kind, filePath]) => ({\n evidence_type: 'artifact_pointer',\n path: filePath,\n summary: `${kind} artifact`,\n }))\n}\n\nfunction htmlEscape(value: unknown): string {\n return String(value ?? '')\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;')\n}\n\n// Deterministic query-builder surface for the committed corpus and\n// query-with-a-documented-call-contract tests. Reflects the retained\n// aml_address_risk/graph query builders after the aml_trace_* cut.\n\n// pairAnchorQuery resolves a pair's first/last transaction id through the\n// facts lane (openspec dozerdb-flows-to-slim-schema: the topology edge\n// carries no tx anchors). order 'ASC' returns the first transfer between the\n// pair, 'DESC' the latest. Bounded: LIMIT 1 on an indexed endpoint pattern.\nexport function pairAnchorQuery(from: string, to: string, order: 'ASC' | 'DESC' = 'ASC'): string {\n return [\n 'USE facts',\n `MATCH (a:Address {address: \"${escapeCypherString(from)}\"})-[t:TRANSFER]->(b:Address {address: \"${escapeCypherString(to)}\"})`,\n 'RETURN t.tx_id AS tx_id, t.block_timestamp AS block_timestamp',\n `ORDER BY t.block_timestamp ${order} LIMIT 1`,\n ].join(' ')\n}\n\n// inlineAverageFlowQuery is the documented recipe for the retired\n// avg_tx_size_usd edge property: compute the average at read time from the\n// two fields the slim edge carries.\nexport function inlineAverageFlowQuery(address: string, limit = 25): { query: string } {\n return {\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:FLOWS_TO]->(b:Address)`,\n 'RETURN b.address AS neighbor, r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count,',\n ' r.amount_usd_sum / toFloat(r.tx_count) AS avg_tx_size_usd',\n `ORDER BY r.amount_usd_sum DESC LIMIT ${limit}`,\n ].join(' '),\n }\n}\n\nexport const queryBuilderContract = {\n topologyGraphQuery,\n pairAnchorQuery,\n inlineAverageFlowQuery,\n addressProfileQuery,\n compareAddressExistsQuery,\n addressFeatureQuery,\n exchangeOutflowQueries,\n exchangeInflowQueries,\n connectionProbeQuery,\n connectionRouteQueries,\n linkedExposureQueries,\n crossSpaceLinkedQuery,\n}\n"],"mappings":"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"}
@@ -286,7 +286,6 @@ function wrapClientForUsageTracking(client, totals) {
286
286
  }
287
287
  //#endregion
288
288
  //#region src/investigation/public-tools.ts
289
- const GRAPH_QUERY_BATCH_TIMEOUT_SECONDS = 10;
290
289
  const GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS = 3e5;
291
290
  const AML_ADDRESS_RISK_SUPPORTED_VERSIONS = ["v1"];
292
291
  function resolveAmlAddressRiskVersion(requested) {
@@ -345,8 +344,7 @@ async function callGraphBatch(remoteClient, network, queries) {
345
344
  queries: queries.map((query) => ({
346
345
  ...query,
347
346
  query: topologyGraphQuery(query.query)
348
- })),
349
- per_query_timeout_seconds: GRAPH_QUERY_BATCH_TIMEOUT_SECONDS
347
+ }))
350
348
  }
351
349
  }, void 0, {
352
350
  timeout: GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "description": "AML investigation CLI and MCP proxy for blockchain risk screening and Chain Insights Graph access",
5
5
  "homepage": "https://chain-insights.ai",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"public-tools-rKIzmn6f.mjs","names":["isRecord","numberValue"],"sources":["../src/viz/graph-normalizer.ts","../src/investigation/risk-level.ts","../src/workspace/output-root.ts","../src/lib/usage-accumulator.ts","../src/investigation/public-tools.ts"],"sourcesContent":["const GRAPH_TYPE_LABELS = new Set([\n 'Address',\n 'Exchange',\n 'Miner',\n 'Validator',\n 'Hotkey',\n 'Subnet',\n 'IPAddress',\n])\nconst FLOW_EDGE_TYPES = new Set(['flows_to', 'transfer', 'transfer_to', 'transfers_to'])\n\ntype GraphRecord = Record<string, unknown>\n\nexport type NormalizedGraphPayload = {\n schema: 'chain-insights.graph.v1'\n nodes: GraphRecord[]\n edges: GraphRecord[]\n flows: unknown[]\n edge_anchors: unknown[]\n [key: string]: unknown\n}\n\nfunction isRecord(value: unknown): value is GraphRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction stringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.map(String).filter(Boolean) : []\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)]\n}\n\nfunction lowerSnake(value: string): string {\n return value\n .trim()\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase()\n}\n\nfunction isGraphTypeLabel(label: string): boolean {\n return GRAPH_TYPE_LABELS.has(label)\n}\n\nfunction displayLabels(node: GraphRecord): string[] {\n return unique(stringArray(node['labels'])).filter((label) => !isGraphTypeLabel(label))\n}\n\nfunction systemLabels(node: GraphRecord): string[] {\n return unique([\n ...stringArray(node['system_labels']),\n ...stringArray(node['graph_labels']),\n ...stringArray(node['raw_labels']),\n ...stringArray(node['labels']).filter(isGraphTypeLabel),\n ])\n}\n\nfunction normalizeRoles(node: GraphRecord): string[] {\n const roles = stringArray(node['roles'])\n const role = typeof node['role'] === 'string' ? node['role'] : ''\n if (role === 'source_exchange') roles.push('exchange')\n else if (role) roles.push(role)\n\n const display = stringArray(node['labels']).map(lowerSnake)\n if (\n systemLabels(node).some((label) => lowerSnake(label) === 'exchange') ||\n display.includes('exchange')\n ) {\n roles.push('exchange')\n }\n\n return unique(roles.map(lowerSnake).filter(Boolean))\n}\n\nfunction normalizeNodeType(node: GraphRecord): string {\n if (typeof node['node_type'] === 'string' && node['node_type'].trim()) {\n return lowerSnake(node['node_type'])\n }\n if (typeof node['address'] === 'string' || typeof node['id'] === 'string') return 'address'\n const labels = systemLabels(node)\n if (labels.length > 0) return lowerSnake(labels[0]!)\n return 'unknown'\n}\n\nfunction normalizeNode(node: unknown): GraphRecord {\n if (!isRecord(node)) return {}\n\n const normalized: GraphRecord = {}\n for (const [key, value] of Object.entries(node)) {\n if (\n [\n 'address_subtypes',\n 'entity_kind',\n 'graph_labels',\n 'labels',\n 'node_type',\n 'pattern_flags',\n 'raw_labels',\n 'role',\n 'roles',\n 'system_labels',\n 'type',\n ].includes(key)\n ) {\n continue\n }\n normalized[key] = value\n }\n\n const id =\n typeof node['id'] === 'string'\n ? node['id']\n : typeof node['address'] === 'string'\n ? node['address']\n : undefined\n if (id) normalized['id'] = id\n\n const address = typeof node['address'] === 'string' ? node['address'] : id\n if (address) normalized['address'] = address\n\n normalized['node_type'] = normalizeNodeType(node)\n normalized['labels'] = displayLabels(node)\n\n const addressSubtypes = unique(stringArray(node['address_subtypes']))\n if (addressSubtypes.length > 0) normalized['address_subtypes'] = addressSubtypes\n\n const roles = normalizeRoles(node)\n if (roles.length > 0) normalized['roles'] = roles\n\n if (\n !Array.isArray(node['flags']) &&\n Array.isArray(node['pattern_flags']) &&\n node['pattern_flags'].length > 0\n ) {\n normalized['flags'] = node['pattern_flags'].map(String)\n }\n\n return normalized\n}\n\nfunction normalizeEdgeType(edge: GraphRecord): string {\n const rawType =\n typeof edge['edge_type'] === 'string' && edge['edge_type'].trim()\n ? edge['edge_type']\n : typeof edge['type'] === 'string' && edge['type'].trim()\n ? edge['type']\n : typeof edge['relationship_type'] === 'string' && edge['relationship_type'].trim()\n ? edge['relationship_type']\n : 'related_to'\n const edgeType = lowerSnake(rawType)\n return FLOW_EDGE_TYPES.has(edgeType) ? 'flows_to' : edgeType\n}\n\nfunction normalizeEdge(edge: unknown): GraphRecord {\n if (!isRecord(edge)) return {}\n\n const normalized: GraphRecord = {}\n for (const [key, value] of Object.entries(edge)) {\n if (['edge_type', 'from_address', 'relationship_type', 'to_address', 'type'].includes(key))\n continue\n normalized[key] = value\n }\n\n if (typeof normalized['source'] !== 'string' && typeof edge['from_address'] === 'string') {\n normalized['source'] = edge['from_address']\n }\n if (typeof normalized['target'] !== 'string' && typeof edge['to_address'] === 'string') {\n normalized['target'] = edge['to_address']\n }\n normalized['edge_type'] = normalizeEdgeType(edge)\n\n return normalized\n}\n\nexport function normalizeGraphPayload(payload: unknown): NormalizedGraphPayload {\n if (!isRecord(payload) || payload['schema'] !== 'chain-insights.graph.v1') {\n throw new Error('Unsupported graph payload schema')\n }\n\n return {\n ...payload,\n schema: 'chain-insights.graph.v1',\n nodes: Array.isArray(payload['nodes']) ? payload['nodes'].map(normalizeNode) : [],\n edges: Array.isArray(payload['edges']) ? payload['edges'].map(normalizeEdge) : [],\n flows: Array.isArray(payload['flows']) ? payload['flows'] : [],\n edge_anchors: Array.isArray(payload['edge_anchors']) ? payload['edge_anchors'] : [],\n }\n}\n","/**\n * Risk-level normalization shared by the AML tools and graph payload builders.\n *\n * The Chain Insights Graph serves the ML verdict verbatim, which historically\n * mixed cases (HIGH vs high) and — since the calibrated-scoring release —\n * includes an explicit abstention band: risk_level = UNSCORED means the model\n * had too little labeled graph context to stand behind a band. UNSCORED is a\n * \"no stance\" state and must never be rendered or reasoned about as low risk.\n */\n\nexport const RISK_SEVERITIES = ['low', 'medium', 'high', 'critical'] as const\nexport const UNSCORED_RISK_LEVEL = 'unscored'\n\n/** Lowercased, trimmed risk level; undefined for empty/unrecognized values. */\nexport function normalizeRiskLevel(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const normalized = value.trim().toLowerCase()\n if (!normalized) return undefined\n if (normalized === UNSCORED_RISK_LEVEL) return UNSCORED_RISK_LEVEL\n if ((RISK_SEVERITIES as readonly string[]).includes(normalized)) return normalized\n return undefined\n}\n\n/** True when the ML verdict explicitly abstained (no stance, not low). */\nexport function isUnscoredRiskLevel(value: unknown): boolean {\n return normalizeRiskLevel(value) === UNSCORED_RISK_LEVEL\n}\n\n/**\n * Severity ordering for comparisons: low(0) < medium(1) < high(2) <\n * critical(3). Unknown/unscored values rank -1 — they never win a\n * severity comparison, they abstain from it.\n */\nexport function riskSeverityRank(value: unknown): number {\n const normalized = normalizeRiskLevel(value)\n if (!normalized || normalized === UNSCORED_RISK_LEVEL) return -1\n return (RISK_SEVERITIES as readonly string[]).indexOf(normalized)\n}\n","import path from 'node:path'\nimport { findActiveWorkspace } from './active.js'\n\nexport const NO_WORKSPACE_ERROR = 'No Chain Insights workspace found. Run: cia init .'\n\nexport interface WorkspaceOutputPaths {\n root: string\n metadataDir: string\n schemaDir: string\n runtimeDir: string\n artifactsRoot: string\n entitiesRoot: string\n sessionsRoot: string\n publishedRoot: string\n reportsRoot: string\n reportGraphsRoot: string\n reportTablesRoot: string\n logsRoot: string\n}\n\nexport function requireWorkspaceRoot(startDir = process.cwd()): string {\n const workspace = findActiveWorkspace(startDir)\n if (!workspace) throw new Error(NO_WORKSPACE_ERROR)\n return workspace.root\n}\n\nexport function workspaceOutputPaths(workspaceRoot = requireWorkspaceRoot()): WorkspaceOutputPaths {\n const root = path.resolve(workspaceRoot)\n return {\n root,\n metadataDir: path.join(root, '.chain-insights'),\n schemaDir: path.join(root, '.chain-insights', 'schema'),\n runtimeDir: path.join(root, '.chain-insights', 'runtime'),\n artifactsRoot: path.join(root, 'artifacts'),\n entitiesRoot: path.join(root, 'entities'),\n sessionsRoot: path.join(root, 'sessions'),\n publishedRoot: path.join(root, 'published'),\n reportsRoot: path.join(root, 'reports'),\n reportGraphsRoot: path.join(root, 'reports', 'graphs'),\n reportTablesRoot: path.join(root, 'reports', 'tables'),\n logsRoot: path.join(root, '.chain-insights', 'runtime', 'logs'),\n }\n}\n","/**\n * Per-workflow billable-units accounting for the aml_* tools (internal feature:\n * usage-units-propagation). The `aml_*` workflow tools make MANY internal\n * graph_query_batch round trips to answer one high-level question; this\n * module totals what those round trips billed so the workflow response can\n * report a single `usage` block instead of exposing nothing at all.\n *\n * Capture point: aml_address_risk (and internal graph_query_batch helper\n * calls feeding its enrichment) wraps its `remoteClient` once via\n * `wrapClientForUsageTracking` before making any internal calls, then reads\n * the shared `UsageTotals` back out when building its response. Every\n * internal helper (callGraphBatch, probeSeedAddresses, ...) keeps calling\n * `client.callTool(...)` exactly as before -- the wrapper observes\n * graph_query_batch/graph_query responses in transit and never changes their\n * return value.\n *\n * Defensive by design: a backend that does not yet emit billing fields (or\n * an unparsable response) contributes 0 units but the call still counts\n * toward `query_count` -- this must never throw and never block a workflow.\n */\n\ninterface CallToolLike {\n callTool(...args: unknown[]): Promise<unknown>\n}\n\nexport interface UsageTotals {\n billable_units: number\n query_count: number\n truncated_queries: number\n}\n\nexport function createUsageAccumulator(): UsageTotals {\n return { billable_units: 0, query_count: 0, truncated_queries: 0 }\n}\n\n// Snapshot as a plain object for embedding in a response -- callers should\n// not hand out the live mutable accumulator.\nexport function usageBlock(totals: UsageTotals): UsageTotals {\n return {\n billable_units: totals.billable_units,\n query_count: totals.query_count,\n truncated_queries: totals.truncated_queries,\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction numberValue(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\nfunction textOfToolResult(result: unknown): string {\n if (!isRecord(result)) return ''\n const content = result['content']\n if (!Array.isArray(content)) return ''\n return content\n .filter(\n (item): item is { type: 'text'; text: string } =>\n isRecord(item) && item['type'] === 'text' && typeof item['text'] === 'string'\n )\n .map((item) => item.text)\n .join('\\n')\n}\n\n// facts.query.* is the single-query shape (graph_query). facts.batch.* /\n// facts.queries[] is the batch shape (graph_query_batch). Both are read\n// defensively -- missing/malformed fields never throw, they just contribute\n// nothing to the total.\nfunction accumulateFacts(totals: UsageTotals, facts: unknown): void {\n if (!isRecord(facts)) return\n\n const query = facts['query']\n if (isRecord(query)) {\n const units = numberValue(query['billable_units'])\n if (units !== undefined) totals.billable_units += units\n if (query['truncated'] === true) totals.truncated_queries += 1\n }\n\n const batch = facts['batch']\n const batchUnits = isRecord(batch) ? numberValue(batch['billable_units']) : undefined\n const queries = Array.isArray(facts['queries'])\n ? (facts['queries'] as unknown[]).filter(isRecord)\n : undefined\n\n if (batchUnits !== undefined) {\n totals.billable_units += batchUnits\n } else if (queries) {\n // No batch-level total (older/mocked backend): fall back to summing\n // whatever per-query billable_units are present. Entries with no field\n // contribute 0, same as the single-query defensive path.\n for (const entry of queries) {\n const units = numberValue(entry['billable_units'])\n if (units !== undefined) totals.billable_units += units\n }\n }\n\n if (queries) {\n for (const entry of queries) {\n if (entry['truncated'] === true) totals.truncated_queries += 1\n }\n } else if (isRecord(batch) && batch['truncated'] === true) {\n totals.truncated_queries += 1\n }\n}\n\n// One graph_query_batch round trip is one query_count unit per Cypher\n// statement it carried (facts.queries.length) -- that is what \"one internal\n// graph query\" means for this tool. A round trip whose response could not be\n// attributed to individual statements (parse failure, missing facts.queries)\n// still represents one internal graph query having been issued, so it counts\n// as 1 rather than 0.\nfunction recordGraphQueryBatchResult(totals: UsageTotals, rawResult: unknown): void {\n let facts: unknown\n try {\n const text = textOfToolResult(rawResult).trim()\n if (text) facts = (JSON.parse(text) as { facts?: unknown }).facts\n else if (\n isRecord(rawResult) &&\n isRecord((rawResult as Record<string, unknown>)['structuredContent'])\n ) {\n facts = (\n (rawResult as Record<string, unknown>)['structuredContent'] as Record<string, unknown>\n )['facts']\n }\n } catch {\n facts = undefined\n }\n\n const queries =\n isRecord(facts) && Array.isArray(facts['queries']) ? (facts['queries'] as unknown[]) : undefined\n totals.query_count += queries ? queries.length : 1\n accumulateFacts(totals, facts)\n}\n\n// One graph_query round trip is exactly one internal graph query.\nfunction recordGraphQueryResult(totals: UsageTotals, rawResult: unknown): void {\n totals.query_count += 1\n let facts: unknown\n try {\n if (\n isRecord(rawResult) &&\n isRecord((rawResult as Record<string, unknown>)['structuredContent'])\n ) {\n facts = (\n (rawResult as Record<string, unknown>)['structuredContent'] as Record<string, unknown>\n )['facts']\n } else {\n const text = textOfToolResult(rawResult).trim()\n if (text) facts = (JSON.parse(text) as { facts?: unknown }).facts\n }\n } catch {\n facts = undefined\n }\n accumulateFacts(totals, facts)\n}\n\n// Wraps a Client-shaped object (real MCP SDK Client or a test double) so\n// every graph_query_batch/graph_query call it makes is observed and totaled\n// into `totals`, without changing what the call returns. Any other method\n// or property passes through untouched.\nexport function wrapClientForUsageTracking<T extends CallToolLike>(\n client: T,\n totals: UsageTotals\n): T {\n return new Proxy(client as object, {\n get(target, prop, receiver) {\n if (prop === 'callTool') {\n return async (...args: unknown[]) => {\n const result = await (target as CallToolLike).callTool(...args)\n const request = args[0]\n const toolName =\n isRecord(request) && typeof request['name'] === 'string' ? request['name'] : undefined\n if (toolName === 'graph_query_batch') {\n recordGraphQueryBatchResult(totals, result)\n } else if (toolName === 'graph_query') {\n recordGraphQueryResult(totals, result)\n }\n return result\n }\n }\n const value = Reflect.get(target, prop, target)\n return typeof value === 'function' ? value.bind(target) : value\n },\n }) as T\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport type { Client } from '@modelcontextprotocol/sdk/client/index.js'\nimport type { ContentBlock } from '@modelcontextprotocol/sdk/types.js'\nimport { normalizeGraphPayload } from '../viz/graph-normalizer.js'\nimport { isUnscoredRiskLevel, normalizeRiskLevel, riskSeverityRank } from './risk-level.js'\nimport { workspaceOutputPaths } from '../workspace/output-root.js'\nimport {\n createUsageAccumulator,\n usageBlock,\n wrapClientForUsageTracking,\n} from '../lib/usage-accumulator.js'\n\nexport { buildTruthProposal, resolveTruthIngressTlsPaths } from './truth-proposal.js'\nexport type { TruthProposalCommand } from './truth-proposal.js'\n\ntype RemoteToolResult = {\n content?: ContentBlock[]\n isError?: boolean\n}\n\ninterface ParsedGraphBatch {\n facts?: {\n queries?: Array<{\n id?: string\n ok?: boolean\n results?: Array<Record<string, unknown>>\n error?: string\n }>\n }\n}\n\ntype QueryFailure = {\n id: string\n error: string\n}\n\nconst GRAPH_QUERY_BATCH_TIMEOUT_SECONDS = 10\nconst GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS = 5 * 60 * 1000\n\nexport interface AddressRiskOptions {\n address: string\n network: string\n compareAddress?: string\n writeArtifacts?: boolean\n}\n\nconst AML_ADDRESS_RISK_SUPPORTED_VERSIONS = ['v1'] as const\nexport type AmlAddressRiskVersion = (typeof AML_ADDRESS_RISK_SUPPORTED_VERSIONS)[number]\nexport const LATEST_AML_ADDRESS_RISK_VERSION: AmlAddressRiskVersion = 'v1'\n\nexport function resolveAmlAddressRiskVersion(requested?: string): AmlAddressRiskVersion {\n const normalized = requested?.trim().toLowerCase()\n if (!normalized) return LATEST_AML_ADDRESS_RISK_VERSION\n if (\n AML_ADDRESS_RISK_SUPPORTED_VERSIONS.includes(\n normalized as (typeof AML_ADDRESS_RISK_SUPPORTED_VERSIONS)[number]\n )\n ) {\n return normalized as AmlAddressRiskVersion\n }\n throw new Error(\n `Unsupported aml_address_risk version \"${requested?.trim() ?? ''}\". Supported versions: ${AML_ADDRESS_RISK_SUPPORTED_VERSIONS.join(', ')}.`\n )\n}\n\nfunction escapeCypherString(value: string): string {\n return value.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')\n}\n\nfunction textFromToolResult(result: RemoteToolResult): string {\n return (result.content ?? [])\n .filter((item): item is Extract<ContentBlock, { type: 'text' }> => item.type === 'text')\n .map((item) => item.text)\n .join('\\n')\n}\n\nfunction parseGraphBatchResult(result: RemoteToolResult): ParsedGraphBatch {\n const text = textFromToolResult(result).trim()\n if (!text) throw new Error('graph_query_batch returned no text content')\n const parsed = JSON.parse(text) as ParsedGraphBatch\n if (!parsed.facts?.queries)\n throw new Error('graph_query_batch response did not include facts.queries')\n return parsed\n}\n\nfunction topologyGraphQuery(query: string): string {\n const trimmed = query.trim()\n if (/^USE\\s+/i.test(trimmed)) return trimmed\n return `USE topology ${trimmed}`\n}\n\nfunction collectQueryFailure(\n failures: QueryFailure[],\n id: string,\n error: string | undefined\n): void {\n failures.push({ id, error: error || 'unknown error' })\n}\n\nfunction optionalResultsFor(\n batch: ParsedGraphBatch,\n id: string,\n failures: QueryFailure[]\n): Array<Record<string, unknown>> {\n const query = batch.facts?.queries?.find((entry) => entry.id === id)\n if (!query) return []\n if (query.ok === false) {\n collectQueryFailure(failures, id, query.error)\n return []\n }\n return query.results ?? []\n}\n\nfunction optionalResultsWithPrefix(\n batch: ParsedGraphBatch,\n prefix: string,\n failures: QueryFailure[]\n): Array<Record<string, unknown>> {\n return (batch.facts?.queries ?? [])\n .filter((entry) => entry.id?.startsWith(prefix))\n .flatMap((entry) => {\n if (entry.ok === false) {\n collectQueryFailure(failures, entry.id ?? prefix, entry.error)\n return []\n }\n return entry.results ?? []\n })\n}\n\nasync function callGraphBatch(\n remoteClient: Client,\n network: string,\n queries: Array<{ id: string; query: string }>\n): Promise<ParsedGraphBatch> {\n const result = (await remoteClient.callTool(\n {\n name: 'graph_query_batch',\n arguments: {\n network,\n queries: queries.map((query) => ({\n ...query,\n query: topologyGraphQuery(query.query),\n })),\n per_query_timeout_seconds: GRAPH_QUERY_BATCH_TIMEOUT_SECONDS,\n },\n },\n undefined,\n {\n timeout: GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS,\n maxTotalTimeout: GRAPH_QUERY_BATCH_REQUEST_TIMEOUT_MS,\n }\n )) as RemoteToolResult\n if (result.isError) throw new Error(textFromToolResult(result) || 'graph_query_batch failed')\n return parseGraphBatchResult(result)\n}\n\nfunction addressProfileQuery(address: string): { id: string; query: string } {\n // risk_score/risk_level are always projected: topology is now the only ML\n // risk source (the retired facts_risk_scores_view never had a risk_level\n // column, so UNSCORED abstention was invisible until this move).\n const riskFields = ', a.risk_score AS live_risk_score, a.risk_level AS live_risk_level'\n // Per-label risk is the overlay graphsync materializes on the node, one\n // entry per current label row. DozerDB properties cannot hold a list of\n // maps, so it is three parallel lists: label_risk_labels, label_risk_levels\n // and label_risk_updated_timestamps. deriveLabelRows zips them back.\n // Missing/empty is \"no label-risk signal,\" never an error.\n const labelRiskFields =\n ', a.label_risk_labels AS label_risk_labels, a.label_risk_levels AS label_risk_levels, a.label_risk_updated_timestamps AS label_risk_updated_timestamps'\n return {\n id: 'address_profile',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})`,\n `RETURN a.address AS address, a.network AS network, a.labels AS display_labels, a.labels AS system_labels, a.is_exchange AS is_exchange${riskFields}${labelRiskFields}`,\n 'LIMIT 1',\n ].join(' '),\n }\n}\n\n// Lifetime address features are materialized on the one USE topology node.\nfunction addressFeatureQuery(address: string): { id: string; query: string } {\n return {\n id: 'address_feature',\n query: [\n 'USE topology',\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})`,\n 'RETURN a.degree_in AS degree_in, a.degree_out AS degree_out, a.degree_total AS degree_total, a.tx_in_count AS tx_in_count, a.tx_out_count AS tx_out_count, a.tx_total_count AS tx_total_count, a.total_volume_usd AS total_volume_usd, a.total_in_usd AS total_in_usd, a.total_out_usd AS total_out_usd, a.net_flow_usd AS net_flow_usd, a.first_activity_timestamp AS first_activity_timestamp, a.last_activity_timestamp AS last_activity_timestamp, a.activity_span_days AS activity_span_days',\n 'LIMIT 1',\n ].join(' '),\n }\n}\n\nfunction flowEdgeMap(variableName: string): string {\n return `{amount_usd_sum: ${variableName}.amount_usd_sum, tx_count: ${variableName}.tx_count, first_seen_timestamp: ${variableName}.first_seen_timestamp, last_seen_timestamp: ${variableName}.last_seen_timestamp}`\n}\n\nfunction pathNodeMap(variableName: string): string {\n // risk_score/risk_level are always projected on topology nodes.\n const riskFields = `, risk_score: ${variableName}.risk_score, risk_level: ${variableName}.risk_level`\n return `{address: ${variableName}.address, network: ${variableName}.network, labels: ${variableName}.labels, system_labels: ${variableName}.labels, is_exchange: ${variableName}.is_exchange${riskFields}}`\n}\n\n// money-trail enrichment: incident MONEY_TRAIL edges on the subject address\n// (either direction), and the TRAIL_ENDS_AT fan-out of whichever seed\n// generated the trail -- both are optional-results reads, so a graph with no\n// money-trail layer (or a failed query) never fails aml_address_risk itself.\nexport function moneyTrailIncidentQuery(address: string): string {\n return [\n 'USE topology',\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:MONEY_TRAIL]-()`,\n 'RETURN r.edge_class AS edge_class, r.value AS value, r.min_hop AS min_hop, r.seed_count AS seed_count, r.primary_seed AS primary_seed, r.generation AS generation, r.network AS network, r.first_ts AS first_ts, r.last_ts AS last_ts',\n 'LIMIT 200',\n ].join(' ')\n}\n\nexport function moneyTrailEndsQuery(seed: string): string {\n return [\n 'USE topology',\n `MATCH (s:Address {address: \"${escapeCypherString(seed)}\"})-[r:TRAIL_ENDS_AT]->(t:Address)`,\n 'RETURN t.address AS address, r.fact_type AS fact_type, r.direction AS direction, r.terminal_role AS terminal_role, r.hop AS hop, r.value AS value, r.generation AS generation',\n 'LIMIT 200',\n ].join(' ')\n}\n\n// Task 2 (trace fast path): a resolved trace target IS a money-trail seed\n// when it has outgoing TRAIL_ENDS_AT edges -- same shape as\n// moneyTrailEndsQuery, reused here under a probe-specific name so the\n// trace-tool call site reads as \"check whether this address is a seed\"\n// rather than \"fetch the ends fan of a known seed\" (they happen to be the\n// same read).\nexport function moneyTrailSeedProbeQuery(address: string): string {\n return moneyTrailEndsQuery(address)\n}\n\n// Precomputed MONEY_TRAIL corridor from a confirmed seed: bounded to 6 hops\n// (min_hop is a property baked onto the edge by the walk engine, not a\n// variable-length path depth) and excludes peripheral-only continuation --\n// peripheral is the \"touched funds\" floor (see MONEY_TRAIL_CLASS_RANK), not\n// a real trail hop, so it never extends the fast-path corridor.\nexport function moneyTrailCorridorQuery(seed: string): string {\n return [\n 'USE topology',\n `MATCH (s:Address {address: \"${escapeCypherString(seed)}\"})-[r:MONEY_TRAIL]->(t:Address)`,\n 'WHERE r.min_hop <= 6 AND r.edge_class <> \"peripheral\"',\n 'RETURN t.address AS address, r.edge_class AS edge_class, r.value AS value, r.min_hop AS min_hop, r.seed_count AS seed_count, r.primary_seed AS primary_seed, r.generation AS generation, r.network AS network',\n 'ORDER BY r.min_hop ASC',\n 'LIMIT 200',\n ].join(' ')\n}\n\nexport interface MoneyTrailBlock {\n on_trail: true\n class: string\n min_hop: number\n primary_seed: string\n generation: number\n nearest_trail_end?: { address: string; fact_type: string; value: string }\n}\n\nconst MONEY_TRAIL_CLASS_RANK: Record<string, number> = { transport: 3, holding: 2, peripheral: 1 }\n\nexport function buildMoneyTrailBlock(\n incidentRows: Array<Record<string, unknown>>,\n endRows: Array<Record<string, unknown>>\n): MoneyTrailBlock | undefined {\n if (incidentRows.length === 0) return undefined\n\n const winningClassRank = Math.max(\n ...incidentRows.map((row) => MONEY_TRAIL_CLASS_RANK[firstString(row['edge_class']) ?? ''] ?? 0)\n )\n const winningClass =\n Object.entries(MONEY_TRAIL_CLASS_RANK).find(([, rank]) => rank === winningClassRank)?.[0] ??\n firstString(incidentRows[0]?.['edge_class']) ??\n 'peripheral'\n const rowsOfWinningClass = incidentRows.filter(\n (row) => firstString(row['edge_class']) === winningClass\n )\n const bestRow = rowsOfWinningClass.reduce((best, row) => {\n const rowHop = numberValue(row['min_hop']) ?? Number.POSITIVE_INFINITY\n const bestHop = numberValue(best['min_hop']) ?? Number.POSITIVE_INFINITY\n return rowHop < bestHop ? row : best\n }, rowsOfWinningClass[0]!)\n\n const nearestEnd = endRows.reduce<Record<string, unknown> | undefined>((best, row) => {\n const rowValue = numberValue(row['value']) ?? Number.NEGATIVE_INFINITY\n const bestValue = best\n ? (numberValue(best['value']) ?? Number.NEGATIVE_INFINITY)\n : Number.NEGATIVE_INFINITY\n return !best || rowValue > bestValue ? row : best\n }, undefined)\n\n return {\n on_trail: true,\n class: winningClass,\n min_hop: numberValue(bestRow['min_hop']) ?? 0,\n primary_seed: firstString(bestRow['primary_seed']) ?? '',\n generation: numberValue(bestRow['generation']) ?? 0,\n ...(nearestEnd\n ? {\n nearest_trail_end: {\n address: firstString(nearestEnd['address']) ?? '',\n fact_type: firstString(nearestEnd['fact_type']) ?? '',\n value: String(nearestEnd['value'] ?? ''),\n },\n }\n : {}),\n }\n}\n\nexport function moneyTrailSummarySentence(block: MoneyTrailBlock): string {\n return block.class === 'peripheral'\n ? `This address touched money-trail funds (${block.class}, min hop ${block.min_hop}).`\n : `This address sits on a money trail (${block.class}, min hop ${block.min_hop}).`\n}\n\n// Pool guard (schema skill, \"Pool trace rule\"): a FLOWS_TO walk never starts\n// at a :Pool and never passes through one, and each guard is its own AND term\n// (inside an OR it would guard nothing). Fixed-hop form of\n// skills/chain-insights-cypher: `WHERE NOT src:Pool AND NOT mid:Pool`.\nfunction poolGuardTerms(nodeVariables: string[]): string {\n return nodeVariables.map((nodeVariable) => `NOT ${nodeVariable}:Pool`).join(' AND ')\n}\n\nfunction exchangeOutflowQueries(address: string): Array<{ id: string; query: string }> {\n return Array.from({ length: 3 }, (_, index) => exchangeOutflowQueryAtDepth(address, index + 1))\n}\n\nfunction exchangeOutflowQueryAtDepth(\n address: string,\n depth: number\n): { id: string; query: string } {\n const intermediateVariables = Array.from(\n { length: Math.max(depth - 1, 0) },\n (_, index) => `n${index + 1}`\n )\n const nodeVariables = ['a', ...intermediateVariables, 'exchange']\n const edgeVariables = Array.from({ length: depth }, (_, index) => `r${index + 1}`)\n const relationshipChain = edgeVariables\n .map((edgeVariable, index) => {\n const targetVariable =\n index === edgeVariables.length - 1 ? 'exchange' : intermediateVariables[index]!\n return `-[${edgeVariable}:FLOWS_TO]->(${targetVariable}:Address)`\n })\n .join('')\n const intermediatePredicates = intermediateVariables.map(\n (nodeVariable) => `${nodeVariable}.is_exchange IS NULL`\n )\n // Pool trace rule: the walk starts at the screened address and passes\n // through the intermediates, so none of them may be a :Pool. The exchange\n // end is the target, which may be one.\n const poolGuards = poolGuardTerms(['a', ...intermediateVariables])\n const depositVariable = nodeVariables[nodeVariables.length - 2]!\n const terminalEdgeVariable = edgeVariables[edgeVariables.length - 1]!\n return {\n id: `exchange_outflows_${depth}`,\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})${relationshipChain}`,\n `WHERE a.address <> exchange.address AND exchange.is_exchange IS NOT NULL${intermediatePredicates.length > 0 ? ` AND ${intermediatePredicates.join(' AND ')}` : ''} AND ${poolGuards}`,\n `RETURN \"outflow\" AS direction, exchange.address AS exchange_address, exchange.labels AS exchange_display_labels, exchange.labels AS exchange_system_labels, ${depositVariable}.address AS deposit_address, ${depth} AS hops, ${terminalEdgeVariable}.amount_usd_sum AS amount_usd_sum, ${terminalEdgeVariable}.tx_count AS tx_count, ${terminalEdgeVariable}.first_seen_timestamp AS first_seen_timestamp, ${terminalEdgeVariable}.last_seen_timestamp AS last_seen_timestamp, [${nodeVariables.map((nodeVariable) => `${nodeVariable}.address`).join(', ')}] AS addresses, [${nodeVariables.map((nodeVariable) => pathNodeMap(nodeVariable)).join(', ')}] AS path_nodes, [${edgeVariables.map(flowEdgeMap).join(', ')}] AS edge_props`,\n 'ORDER BY hops ASC',\n 'LIMIT 200',\n ].join(' '),\n }\n}\n\nfunction exchangeInflowQueries(address: string): Array<{ id: string; query: string }> {\n return Array.from({ length: 3 }, (_, index) => exchangeInflowQueryAtDepth(address, index + 1))\n}\n\nfunction exchangeInflowQueryAtDepth(address: string, depth: number): { id: string; query: string } {\n const intermediateVariables = Array.from(\n { length: Math.max(depth - 1, 0) },\n (_, index) => `n${index + 1}`\n )\n const nodeVariables = ['exchange', ...intermediateVariables, 'a']\n const edgeVariables = Array.from({ length: depth }, (_, index) => `r${index + 1}`)\n const relationshipChain = edgeVariables\n .map((edgeVariable, index) => {\n const targetVariable =\n index === edgeVariables.length - 1 ? 'a' : intermediateVariables[index]!\n return `-[${edgeVariable}:FLOWS_TO]->(${targetVariable}:Address)`\n })\n .join('')\n const intermediatePredicates = intermediateVariables.map(\n (nodeVariable) => `${nodeVariable}.is_exchange IS NULL`\n )\n // Pool trace rule: the trace starts at the screened address `a` and walks\n // back through the intermediates, so none of them may be a :Pool. The\n // exchange end is the target, which may be one.\n const poolGuards = poolGuardTerms(['a', ...intermediateVariables])\n const withdrawalVariable = nodeVariables[1]!\n const terminalEdgeVariable = edgeVariables[edgeVariables.length - 1]!\n return {\n id: `exchange_inflows_${depth}`,\n query: [\n `MATCH (exchange:Address)${relationshipChain}`,\n `WHERE a.address = \"${escapeCypherString(address)}\" AND a.address <> exchange.address AND exchange.is_exchange IS NOT NULL${intermediatePredicates.length > 0 ? ` AND ${intermediatePredicates.join(' AND ')}` : ''} AND ${poolGuards}`,\n `RETURN \"inflow\" AS direction, exchange.address AS exchange_address, exchange.labels AS exchange_display_labels, exchange.labels AS exchange_system_labels, ${withdrawalVariable}.address AS withdrawal_address, ${depth} AS hops, ${terminalEdgeVariable}.amount_usd_sum AS amount_usd_sum, ${terminalEdgeVariable}.tx_count AS tx_count, ${terminalEdgeVariable}.first_seen_timestamp AS first_seen_timestamp, ${terminalEdgeVariable}.last_seen_timestamp AS last_seen_timestamp, [${nodeVariables.map((nodeVariable) => `${nodeVariable}.address`).join(', ')}] AS addresses, [${nodeVariables.map((nodeVariable) => pathNodeMap(nodeVariable)).join(', ')}] AS path_nodes, [${edgeVariables.map(flowEdgeMap).join(', ')}] AS edge_props`,\n 'ORDER BY hops ASC',\n 'LIMIT 200',\n ].join(' '),\n }\n}\n\nfunction connectionProbeQuery(\n address: string,\n compareAddress: string\n): { id: string; query: string } {\n return {\n id: 'connection_probe',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:FLOWS_TO]-(b:Address {address: \"${escapeCypherString(compareAddress)}\"})`,\n 'RETURN [a.address, b.address] AS addresses, 1 AS hops',\n 'LIMIT 5',\n ].join(' '),\n }\n}\n\n// Address-grain has no separate identity-resolution lookup: existence of the\n// compare_address is checked directly against :Address, distinct from the\n// primary address_profile query so both existence checks can run in one batch.\nfunction compareAddressExistsQuery(address: string): { id: string; query: string } {\n return {\n id: 'compare_address_exists',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})`,\n 'RETURN a.address AS address',\n 'LIMIT 1',\n ].join(' '),\n }\n}\n\n// Pool trace rule: the via_linked read is a two-hop walk through `owned`, so\n// `a` and `owned` carry the guard and the target `b` may be a pool. The direct\n// read is a counterparty listing of one address, not a trace, and stays\n// unguarded (ruling: a pool's own counterparty list is a listing).\n// AC11: FLOWS_TO reachability UNIONed over one -[:LINKED]- hop, so an\n// investigator surveying an address's exposure also sees exposure carried by\n// an address that is only ownership-LINKED to it (LINKED is undirected).\nfunction linkedExposureQueries(address: string): Array<{ id: string; query: string }> {\n return [\n {\n id: 'linked_exposure_direct',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:FLOWS_TO]-(b:Address)`,\n 'WHERE a.address <> b.address',\n `RETURN a.address AS subject_address, b.address AS counterparty_address, b.network AS counterparty_network, \"direct\" AS exposure_basis, r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count`,\n 'LIMIT 200',\n ].join(' '),\n },\n {\n id: 'linked_exposure_via_linked',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[l:LINKED]-(owned:Address)-[r:FLOWS_TO]-(b:Address)`,\n 'WHERE NOT a:Pool AND NOT owned:Pool AND owned.address <> b.address AND a.address <> b.address',\n `RETURN a.address AS subject_address, b.address AS counterparty_address, b.network AS counterparty_network, \"via_linked\" AS exposure_basis, owned.address AS linked_via_address, l.basis AS link_basis, l.confidence AS link_confidence, r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count`,\n 'LIMIT 200',\n ].join(' '),\n },\n ]\n}\n\n// AC5: LINKED ownership-overlay probe. On the single public robinhood graph,\n// :Address.network is a node PROPERTY (robinhood), never a second query\n// network: LINKED is the ownership edge within that one space, served on the\n// topology graph only, with no network switch.\nfunction crossSpaceLinkedQuery(address: string): { id: string; query: string } {\n return {\n id: 'cross_space_linked',\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[l:LINKED]-(b:Address)`,\n 'WHERE a.network <> b.network',\n 'RETURN a.address AS address, a.network AS network, b.address AS linked_address, b.network AS linked_network, l.basis AS basis, l.confidence AS confidence, l.source_event AS source_event, l.declared_owner AS declared_owner',\n 'LIMIT 50',\n ].join(' '),\n }\n}\n\n// ── Pairwise route evidence ──\n// Undirected shortest route between two KNOWN identity endpoints. The\n// topology graph speaks ISO GQL, so both routes use one shortest path.\n// Exchange intermediates on a returned route are DISCLOSED in the evidence,\n// never silently filtered out.\n//\n// Pool trace rule (schema skill, \"Pool trace rule\"): a route never starts at\n// a :Pool and never passes through one, and it may end at one. It follows\n// FLOWS_TO and SWAPPED, so it crosses a swap from payer to recipient. The\n// guard sits inside the path pattern, so the search finds the shortest route\n// that avoids pools instead of dropping a shortest route that crosses one.\n//\n// The text is the guarded route contract (ruled 2026-09-28), served word for\n// word by skills/chain-insights-cypher, and graphrag-mcp rewrites exactly this\n// shape onto DozerDB's fast shortest-path search (data-pipeline\n// route_rewrite.go). Change the addresses and the RETURN only. The quantifier\n// bound covers the guarded middle; the last hop is the unguarded target end,\n// so a route is 1 to CONNECTION_ROUTE_DEPTH_BOUND + 1 hops. The `{0,...}`\n// lower bound keeps DozerDB off the slow undirected search.\n\nexport const CONNECTION_ROUTE_DEPTH_BOUND = 4\n// A route is the guarded middle plus the last hop to the target end.\nexport const CONNECTION_ROUTE_MAX_HOPS = CONNECTION_ROUTE_DEPTH_BOUND + 1\n\nexport function shouldIncludeRouteQueries(compareAddress: string | undefined): boolean {\n // GQL shortest-path traversal always fires when a compare address is given.\n return Boolean(compareAddress)\n}\n\nexport function connectionRouteQueries(\n address: string,\n compareAddress: string\n): Array<{ id: string; query: string }> {\n const routeQuery = (fromAddress: string, toAddress: string): string =>\n [\n `MATCH p = SHORTEST 1 (a:Address {address: \"${escapeCypherString(fromAddress)}\"} WHERE NOT a:Pool)`,\n ` (()-[:FLOWS_TO|SWAPPED]-(via:Address) WHERE NOT via:Pool){0,${CONNECTION_ROUTE_DEPTH_BOUND}}`,\n ` ()-[:FLOWS_TO|SWAPPED]-(b:Address {address: \"${escapeCypherString(toAddress)}\"}) RETURN p LIMIT 1`,\n ].join('')\n return [\n { id: 'connection_route_outbound', query: routeQuery(address, compareAddress) },\n { id: 'connection_route_inbound', query: routeQuery(compareAddress, address) },\n ]\n}\n\nexport interface RouteEvidenceSide {\n hops: number\n identities: string[]\n exchange_intermediates: string[]\n amount_usd_sum_total: number\n}\n\nfunction collectOrdered(\n value: unknown,\n matches: (candidate: Record<string, unknown>) => boolean\n): Array<Record<string, unknown>> {\n const collected: Array<Record<string, unknown>> = []\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const entry of node) walk(entry)\n return\n }\n if (node && typeof node === 'object') {\n const candidate = node as Record<string, unknown>\n if (matches(candidate)) collected.push(candidate)\n for (const nested of Object.values(candidate)) walk(nested)\n }\n }\n walk(value)\n return collected\n}\n\n// is_exchange on topology nodes is a MARKER, not a boolean: it usually\n// carries the exchange name (e.g. \"binance\"). Treat explicit falsy\n// encodings as not-exchange (a bare non-null check would falsely disclose\n// nodes serialized as false/\"false\"/0), and any other non-empty value as\n// exchange. isExchangeFlag() is NOT reused here — it is boolean-only and\n// would miss name-valued markers.\nexport function isExchangeMarker(value: unknown): boolean {\n if (value === null || value === undefined || value === false) return false\n if (typeof value === 'number') return value !== 0\n if (typeof value === 'string') {\n const normalized = value.trim().toLowerCase()\n return (\n normalized !== '' && normalized !== 'false' && normalized !== '0' && normalized !== 'null'\n )\n }\n return value === true\n}\n\n// Shape-tolerant hydrated-path reader: walks the returned path value for\n// ordered address nodes and FLOWS_TO edge amounts without pinning the\n// backend's exact path envelope.\nexport function routeFromPathValue(value: unknown): RouteEvidenceSide | null {\n if (!value || typeof value !== 'object') return null\n const nodes = collectOrdered(value, (candidate) => typeof candidate['address'] === 'string')\n if (nodes.length < 2) return null\n const edges = collectOrdered(\n value,\n (candidate) => typeof candidate['amount_usd_sum'] === 'number' && !('address' in candidate)\n )\n const identities = nodes.map((node) => String(node['address']))\n const exchangeIntermediates = nodes\n .slice(1, -1)\n .filter((node) => isExchangeMarker(node['is_exchange']))\n .map((node) => String(node['address']))\n return {\n // Hop count is a structural property of the node sequence — never\n // derived from edge-object detection, which keys on numeric\n // amount_usd_sum and would under-report on partially hydrated edges.\n hops: nodes.length - 1,\n identities,\n exchange_intermediates: exchangeIntermediates,\n amount_usd_sum_total: edges.reduce((total, edge) => total + Number(edge['amount_usd_sum']), 0),\n }\n}\n\nexport interface RouteEvidence {\n search_strategy: 'any_shortest'\n route_rank_basis: 'hop_count'\n depth_bound: number\n route_found: boolean\n outbound: RouteEvidenceSide | null\n inbound: RouteEvidenceSide | null\n}\n\nexport function buildRouteEvidence(\n outboundRows: Array<Record<string, unknown>>,\n inboundRows: Array<Record<string, unknown>>\n): RouteEvidence {\n const sideFrom = (rows: Array<Record<string, unknown>>): RouteEvidenceSide | null => {\n const first = rows[0]\n if (!first) return null\n const pathValue = 'p' in first ? first['p'] : Object.values(first)[0]\n return routeFromPathValue(pathValue)\n }\n const outbound = sideFrom(outboundRows)\n const inbound = sideFrom(inboundRows)\n return {\n search_strategy: 'any_shortest',\n route_rank_basis: 'hop_count',\n depth_bound: CONNECTION_ROUTE_MAX_HOPS,\n route_found: outbound !== null || inbound !== null,\n outbound,\n inbound,\n }\n}\n\nfunction formatExchangeRows(rows: Array<Record<string, unknown>>): string[] {\n return rows.map((row) => {\n const direction = String(row['direction'] ?? 'flow')\n const exchange = String(row['exchange_address'] ?? '')\n const amount = row['amount_usd_sum'] ?? ''\n const hops = row['hops'] ?? ''\n return `- ${direction}: ${exchange} (${hops} hop(s), amount_usd_sum ${amount})`\n })\n}\n\nfunction numberValue(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\nfunction firstNumber(...values: unknown[]): number | undefined {\n for (const value of values) {\n const parsed = numberValue(value)\n if (parsed !== undefined) return parsed\n }\n return undefined\n}\n\nfunction firstString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value === 'string' && value.trim()) return value.trim()\n }\n return undefined\n}\n\nfunction riskLevelFromScore(score: number): string {\n if (score >= 0.85) return 'critical'\n if (score >= 0.7) return 'high'\n if (score >= 0.4) return 'medium'\n return 'low'\n}\n\nfunction riskRecommendation(level: string): string {\n if (level === 'critical' || level === 'high') return 'Escalate for manual review.'\n if (level === 'medium') return 'Review exchange exposure and counterparties before clearing.'\n return 'No stored risk signal found; continue with normal monitoring.'\n}\n\nfunction riskDrivers(\n profile: Record<string, unknown>,\n labelRows: Array<Record<string, unknown>>,\n exchangeRows: Array<Record<string, unknown>>\n): string[] {\n const drivers: string[] = []\n\n const riskLabels = labelRows\n .map((row) => firstString(row['label']))\n .filter((label): label is string => Boolean(label))\n if (riskLabels.length > 0) drivers.push(`Labels: ${[...new Set(riskLabels)].join('; ')}`)\n\n const outflowCount = exchangeRows.filter((row) => row['direction'] === 'outflow').length\n const inflowCount = exchangeRows.filter((row) => row['direction'] === 'inflow').length\n if (outflowCount > 0)\n drivers.push(`Forward bounded search reached ${outflowCount} exchange path(s).`)\n if (inflowCount > 0)\n drivers.push(`Backward bounded search found ${inflowCount} source exchange path(s).`)\n\n return [...new Set(drivers)]\n}\n\n// Derives the deterministic labelRows subset from the profile row's three\n// label_risk_* lists (topology), reproducing the retired\n// addressLabelRiskQuery's `ORDER BY label.updated_timestamp DESC LIMIT 10`\n// subset byte-for-byte: same sort, same cap. The lists are parallel: entry i\n// of each one describes the same label row. Missing/empty lists -> empty\n// array, same as \"no labels\" behaved under the old facts read.\nfunction deriveLabelRows(profile: Record<string, unknown>): Array<Record<string, unknown>> {\n // Dozer/Neo4j properties cannot store lists of maps. Sync therefore\n // materializes the three map fields as parallel primitive arrays.\n const labels = Array.isArray(profile['label_risk_labels'])\n ? (profile['label_risk_labels'] as unknown[]).map((value) => String(value))\n : []\n const riskLevels = Array.isArray(profile['label_risk_levels'])\n ? (profile['label_risk_levels'] as unknown[]).map((value) => String(value))\n : []\n const timestamps = Array.isArray(profile['label_risk_updated_timestamps'])\n ? (profile['label_risk_updated_timestamps'] as unknown[]).map((value) => numberValue(value))\n : []\n const rows = labels.map((label, index) => ({\n label,\n risk_level: riskLevels[index] ?? '',\n updated_timestamp: timestamps[index] ?? 0,\n }))\n return [...rows]\n .sort(\n (a, b) =>\n (numberValue(b['updated_timestamp']) ?? 0) - (numberValue(a['updated_timestamp']) ?? 0)\n )\n .slice(0, 10)\n}\n\nconst RISK_LEVEL_ORDER = ['critical', 'high', 'medium', 'low'] as const\n\nfunction strongestLabelRiskLevel(labelRows: Array<Record<string, unknown>>): string | undefined {\n const levels = labelRows\n .map((row) => firstString(row['risk_level'])?.toLowerCase())\n .filter((level): level is string =>\n Boolean(level && (RISK_LEVEL_ORDER as readonly string[]).includes(level))\n )\n if (levels.length === 0) return undefined\n return RISK_LEVEL_ORDER.find((candidate) => levels.includes(candidate))\n}\n\nfunction riskScoreSources(\n profile: Record<string, unknown>,\n labelRows: Array<Record<string, unknown>>\n): Array<Record<string, unknown>> {\n const sources: Array<Record<string, unknown>> = []\n if (numberValue(profile['live_risk_score']) !== undefined) {\n sources.push({\n family: 'ml_risk_score',\n layer: 'topology',\n source: 'address_node',\n fields: ['risk_score', 'risk_level'],\n })\n }\n if (labelRows.length > 0) {\n sources.push({\n family: 'label_risk',\n layer: 'topology',\n source: 'address_node',\n labels: labelRows.map((row) => ({\n label: row['label'],\n risk_level: row['risk_level'],\n })),\n })\n }\n return sources\n}\n\nfunction terminalEdgeProperties(row: Record<string, unknown>): Record<string, unknown> | undefined {\n const edgeProps = Array.isArray(row['edge_props'])\n ? (row['edge_props'] as Array<Record<string, unknown>>)\n : []\n return edgeProps[edgeProps.length - 1]\n}\n\nfunction enrichExchangeRows(rows: Array<Record<string, unknown>>): Array<Record<string, unknown>> {\n return rows.map((row) => {\n const terminal = terminalEdgeProperties(row)\n if (!terminal) return row\n return {\n ...row,\n amount_usd_sum: row['amount_usd_sum'] ?? terminal['amount_usd_sum'],\n tx_count: row['tx_count'] ?? terminal['tx_count'],\n first_seen_timestamp: row['first_seen_timestamp'] ?? terminal['first_seen_timestamp'],\n last_seen_timestamp: row['last_seen_timestamp'] ?? terminal['last_seen_timestamp'],\n }\n })\n}\n\nconst FALLBACK_SHARED_TX_COUNT = 1000\nconst FALLBACK_SHARED_USD_SUM = 5_000_000\nconst FALLBACK_SHARED_DAMPING = 0.1\nconst FALLBACK_VALUE_SATURATION_USD = 1_000_000\nconst FALLBACK_BASE_SCORE = 0.1\nconst FALLBACK_MAX_SCORE = 0.6\n\n/**\n * Score exchange exposure when no ML risk score exists. Topology-grain:\n * log-scaled USD volume across exchange rows, with shared/omnibus edges\n * (high tx_count or USD throughput) dampened, bounded in (0, 0.6] so a\n * fallback can never impersonate a high ML score band.\n */\nexport function exchangeExposureFallbackScore(\n exchangeRows: Array<Record<string, unknown>>\n): number {\n if (exchangeRows.length === 0) return 0\n let weightedUsd = 0\n for (const row of exchangeRows) {\n const usd = numberValue(row['amount_usd_sum']) ?? 0\n const txCount = numberValue(row['tx_count']) ?? 0\n const shared = txCount >= FALLBACK_SHARED_TX_COUNT || usd >= FALLBACK_SHARED_USD_SUM\n weightedUsd += shared ? usd * FALLBACK_SHARED_DAMPING : usd\n }\n const capped = Math.min(weightedUsd, FALLBACK_VALUE_SATURATION_USD)\n const factor = Math.log10(1 + capped) / Math.log10(1 + FALLBACK_VALUE_SATURATION_USD)\n return Math.min(\n FALLBACK_MAX_SCORE,\n FALLBACK_BASE_SCORE + (FALLBACK_MAX_SCORE - FALLBACK_BASE_SCORE) * factor\n )\n}\n\nexport function riskAssessment(\n profile: Record<string, unknown>,\n labelRows: Array<Record<string, unknown>>,\n exchangeRows: Array<Record<string, unknown>>\n): Record<string, unknown> {\n const mlRiskScore = firstNumber(profile['live_risk_score'])\n // UNSCORED is the model's explicit abstention (calibrated-scoring release):\n // the score exists for transparency but carries no stance — deriving a\n // severity band from it would silently launder an abstention into a\n // confident verdict (typically \"low\"). Fall back to labels/exchange\n // exposure and say so.\n const mlAbstained = isUnscoredRiskLevel(profile['live_risk_level'])\n const labelRiskLevel = strongestLabelRiskLevel(labelRows)\n const usableMlScore = mlAbstained ? undefined : mlRiskScore\n const score = usableMlScore ?? exchangeExposureFallbackScore(exchangeRows)\n let level =\n labelRiskLevel ??\n (mlAbstained && exchangeRows.length === 0 ? 'unscored' : riskLevelFromScore(score))\n const drivers = riskDrivers(profile, labelRows, exchangeRows)\n if (mlAbstained)\n drivers.push(\n 'ml_abstained: ML verdict is UNSCORED (insufficient labeled graph context); level derived from labels/exchange exposure only'\n )\n // Labels are curated truth and stay first — but a lower-severity label\n // must never SUPPRESS a more severe usable ML band. Failing toward\n // \"looks safe\" is the one direction an AML triage tool may not fail.\n const usableMlBand = usableMlScore !== undefined ? riskLevelFromScore(usableMlScore) : undefined\n if (\n labelRiskLevel &&\n usableMlBand &&\n riskSeverityRank(usableMlBand) > riskSeverityRank(labelRiskLevel)\n ) {\n level = usableMlBand\n drivers.push(\n `ml_label_divergence: usable ML band ${usableMlBand} exceeds strongest label level ${labelRiskLevel}; reporting the more severe band — review the label`\n )\n }\n return {\n level,\n score,\n ...(mlRiskScore !== undefined ? { ml_risk_score: mlRiskScore } : {}),\n ...(mlAbstained ? { ml_verdict: 'unscored' } : {}),\n confidence:\n usableMlScore !== undefined || labelRiskLevel\n ? 'high'\n : exchangeRows.length > 0\n ? 'medium'\n : 'low',\n recommendation:\n level === 'unscored'\n ? 'Model abstained and no label/exchange signal found; gather more context before clearing.'\n : riskRecommendation(level),\n drivers,\n sources: riskScoreSources(profile, labelRows),\n }\n}\n\nfunction formatRiskScore(score: unknown): string {\n const parsed = numberValue(score)\n if (parsed === undefined) return String(score ?? 'unknown')\n return Number.isInteger(parsed) ? parsed.toString() : parsed.toFixed(2)\n}\n\nfunction stringArrayValue(value: unknown): string[] | undefined {\n if (Array.isArray(value)) return value.map(String)\n if (typeof value === 'string' && value.trim()) return [value]\n return undefined\n}\n\nfunction restoreSystemLabels(\n graph: Record<string, unknown>,\n rawNodes: Array<Record<string, unknown>>\n): Record<string, unknown> {\n if (!Array.isArray(graph['nodes'])) return graph\n const labelsByAddress = new Map(\n rawNodes\n .map(\n (node) =>\n [\n typeof node['address'] === 'string'\n ? node['address']\n : typeof node['id'] === 'string'\n ? node['id']\n : '',\n stringArrayValue(node['system_labels']),\n ] as const\n )\n .filter(\n (entry): entry is readonly [string, string[]] =>\n Boolean(entry[0]) && Array.isArray(entry[1]) && entry[1].length > 0\n )\n )\n return {\n ...graph,\n nodes: graph['nodes'].map((node) => {\n if (typeof node !== 'object' || node === null || Array.isArray(node)) return node\n const record = node as Record<string, unknown>\n const address =\n typeof record['address'] === 'string'\n ? record['address']\n : typeof record['id'] === 'string'\n ? record['id']\n : ''\n const systemLabels = labelsByAddress.get(address)\n return systemLabels ? { ...record, system_labels: systemLabels } : record\n }),\n }\n}\n\nfunction buildRiskGraph(\n address: string,\n profile: Record<string, unknown>,\n rows: Array<Record<string, unknown>>,\n network: string\n): Record<string, unknown> {\n const nodes = new Map<string, Record<string, unknown>>()\n nodes.set(address, {\n id: address,\n address,\n node_type: 'address',\n labels: stringArrayValue(profile['display_labels']) ?? [],\n ...(stringArrayValue(profile['system_labels'])\n ? { system_labels: stringArrayValue(profile['system_labels']) }\n : {}),\n ...(stringArrayValue(profile['member_addresses'])?.length\n ? { member_addresses: stringArrayValue(profile['member_addresses']) }\n : {}),\n ...(numberValue(profile['live_risk_score']) !== undefined\n ? { risk_score: numberValue(profile['live_risk_score']) }\n : {}),\n ...(firstString(profile['live_risk_level'])\n ? { risk_level: firstString(profile['live_risk_level']) }\n : {}),\n roles: ['subject'],\n })\n const edges: Array<Record<string, unknown>> = []\n const mergeNode = (entry: string, metadata?: Record<string, unknown>) => {\n const existing = nodes.get(entry) ?? {\n id: entry,\n address: entry,\n node_type: 'address',\n labels: [],\n }\n const labels = stringArrayValue(metadata?.['labels']) ?? existing['labels']\n const systemLabels = stringArrayValue(metadata?.['system_labels']) ?? existing['system_labels']\n const memberAddresses =\n stringArrayValue(metadata?.['member_addresses']) ?? existing['member_addresses']\n const riskScore = numberValue(metadata?.['risk_score']) ?? existing['risk_score']\n const riskLevel =\n normalizeRiskLevel(firstString(metadata?.['risk_level'])) ?? existing['risk_level']\n nodes.set(entry, {\n ...existing,\n labels,\n ...(systemLabels ? { system_labels: systemLabels } : {}),\n ...(Array.isArray(memberAddresses) && memberAddresses.length > 0\n ? { member_addresses: memberAddresses }\n : {}),\n ...(riskScore !== undefined ? { risk_score: riskScore } : {}),\n ...(riskLevel ? { risk_level: riskLevel } : {}),\n })\n }\n for (const row of rows) {\n const rawPath = Array.isArray(row['path']) ? row['path'] : row['addresses']\n const path = Array.isArray(rawPath) ? rawPath.map(String) : []\n const pathNodes = Array.isArray(row['path_nodes'])\n ? (row['path_nodes'] as Array<Record<string, unknown>>)\n : []\n for (let index = 0; index < path.length; index += 1) {\n const entry = path[index]!\n mergeNode(entry, pathNodes[index])\n }\n const exchange = typeof row['exchange_address'] === 'string' ? row['exchange_address'] : ''\n if (exchange) {\n const displayLabels = stringArrayValue(row['exchange_display_labels']) ?? []\n const systemLabels =\n stringArrayValue(row['exchange_system_labels']) ??\n stringArrayValue(row['exchange_labels']) ??\n []\n nodes.set(exchange, {\n id: exchange,\n address: exchange,\n node_type: 'address',\n labels: displayLabels,\n ...(systemLabels.length > 0 ? { system_labels: systemLabels } : {}),\n roles: ['exchange'],\n })\n }\n for (let index = 0; index < path.length - 1; index += 1) {\n const edgeProps = Array.isArray(row['edge_props'])\n ? (row['edge_props'] as Array<Record<string, unknown>>)\n : []\n const edge = edgeProps[index] ?? row\n edges.push({\n source: path[index],\n target: path[index + 1],\n edge_type: 'flows_to',\n usd_amount: edge['amount_usd_sum'] ?? 0,\n amount_usd_sum: edge['amount_usd_sum'] ?? 0,\n tx_count: edge['tx_count'] ?? 0,\n direction: row['direction'],\n })\n }\n }\n const rawNodes = [...nodes.values()]\n return restoreSystemLabels(\n normalizeGraphPayload({\n schema: 'chain-insights.graph.v1',\n nodes: rawNodes,\n edges,\n flows: [],\n edge_anchors: [],\n metadata: { address, network, generated_at: new Date().toISOString() },\n }),\n rawNodes\n )\n}\n\nexport async function addressRisk(\n remoteClient: Client,\n options: AddressRiskOptions\n): Promise<{\n summaryText: string\n structuredContent: Record<string, unknown>\n graphData: Record<string, unknown>\n}> {\n const address = options.address.trim()\n const network = options.network.trim()\n const compareInput = options.compareAddress?.trim() ?? ''\n if (!address) throw new Error('address is required')\n if (!network) throw new Error('network is required')\n\n // Every internal graph_query_batch round trip this workflow makes is\n // observed here and totaled into a usage block on the response (never\n // blocks/throws on a backend that doesn't emit billing fields yet).\n const usage = createUsageAccumulator()\n const trackedClient = wrapClientForUsageTracking(remoteClient, usage)\n\n // Address-grain has no separate resolution step for the SUBJECT address:\n // the input address IS the graph node key. Its existence is inferred\n // post-hoc from the address_profile row (below) instead of the pre-revert\n // pre-flight lookup -- deliberate: it saves a round trip on the hot path.\n // The COMPARE address keeps a pre-flight existence probe, because the\n // connection probe and shortest-path route queries must be SUPPRESSED (not\n // ignored) when the compare address does not exist -- pre-revert behavior\n // never issued route probes for an unresolved compare input.\n let compareUnresolved = false\n if (compareInput) {\n const compareBatch = await callGraphBatch(trackedClient, network, [\n compareAddressExistsQuery(compareInput),\n ])\n const compareRows = optionalResultsFor(compareBatch, 'compare_address_exists', [])\n compareUnresolved = !firstString(compareRows[0]?.['address'])\n }\n const compareAddress = compareInput && !compareUnresolved ? compareInput : ''\n\n const queries = [\n addressProfileQuery(address),\n addressFeatureQuery(address),\n { id: 'money_trail_incident', query: moneyTrailIncidentQuery(address) },\n ...exchangeOutflowQueries(address),\n ...exchangeInflowQueries(address),\n ...(compareAddress\n ? [connectionProbeQuery(address, compareAddress)]\n : [\n {\n id: 'connection_probe',\n query:\n 'MATCH (n:Address {address: \"__chain_insights_noop__\"}) RETURN n.address AS noop LIMIT 0',\n },\n ]),\n // Route evidence is additive: ISO GQL shortest-path traversal always\n // fires for a compare address whose existence probe succeeded (the\n // 1-hop probe above always runs).\n ...(shouldIncludeRouteQueries(compareAddress)\n ? connectionRouteQueries(address, compareAddress)\n : []),\n ]\n const batch = await callGraphBatch(trackedClient, network, queries)\n const partialQueryFailures: QueryFailure[] = []\n // Deliberate post-hoc existence inference (address grain): an empty\n // address_profile result means the subject :Address does not exist ->\n // report unresolved; this replaces the pre-revert identity pre-flight.\n const addressProfileRows = optionalResultsFor(batch, 'address_profile', partialQueryFailures)\n if (addressProfileRows.length === 0) {\n return {\n summaryText: `Address risk for ${network}:${address}\\n\\nUnresolved: no Address found for \"${address}\". The address may not have on-chain activity in this network.`,\n structuredContent: {\n schema: 'chain-insights.result.v1',\n tool: 'aml_address_risk',\n facts: {\n subject: { network, addresses: [address] },\n unresolved: [address],\n usage: usageBlock(usage),\n },\n },\n graphData: {\n schema: 'chain-insights.graph.v1',\n nodes: [],\n edges: [],\n flows: [],\n edge_anchors: [],\n metadata: { address, network, generated_at: new Date().toISOString() },\n },\n }\n }\n const profile: Record<string, unknown> = {\n address,\n ...(addressProfileRows[0] ?? {}),\n ...(optionalResultsFor(batch, 'address_feature', partialQueryFailures)[0] ?? {}),\n }\n const labelRows = deriveLabelRows(profile)\n const outflows = enrichExchangeRows(\n optionalResultsWithPrefix(batch, 'exchange_outflows_', partialQueryFailures)\n )\n const inflows = enrichExchangeRows(\n optionalResultsWithPrefix(batch, 'exchange_inflows_', partialQueryFailures)\n )\n const connections = compareAddress\n ? optionalResultsFor(batch, 'connection_probe', partialQueryFailures)\n : []\n const routeEvidence = shouldIncludeRouteQueries(compareAddress)\n ? buildRouteEvidence(\n optionalResultsFor(batch, 'connection_route_outbound', partialQueryFailures),\n optionalResultsFor(batch, 'connection_route_inbound', partialQueryFailures)\n )\n : undefined\n const exchangeRows = [...outflows, ...inflows]\n // A hop-depth exchange_outflows_N/exchange_inflows_N query can fail\n // independently (e.g. the archive-tier query-memory limit on a deep\n // multi-hop search) while other hop depths succeed with zero rows. An\n // empty exchangeRows result must then read as \"search incomplete,\" not\n // \"no exchange exposure\" -- the two are not the same claim, and an AML\n // tool that silently reports a false-clean verdict on a partial search\n // failure is a real risk (found during MoA review, 2026-07-05).\n const exchangeSearchFailures = partialQueryFailures.filter(\n (failure) =>\n failure.id.startsWith('exchange_outflows_') || failure.id.startsWith('exchange_inflows_')\n )\n const exchangeSearchComplete = exchangeSearchFailures.length === 0\n // money-trail enrichment (optional): a preliminary block resolves the\n // primary_seed off the incident-only rows, then a second batch call fans\n // out TRAIL_ENDS_AT for that seed so the block can pick the highest-value\n // terminal fact. Both reads are optional-results -- a graph with no\n // money-trail layer never fails the tool.\n const moneyTrailIncidentRows = optionalResultsFor(\n batch,\n 'money_trail_incident',\n partialQueryFailures\n )\n const preliminaryMoneyTrail = buildMoneyTrailBlock(moneyTrailIncidentRows, [])\n let moneyTrailEndRows: Array<Record<string, unknown>> = []\n if (preliminaryMoneyTrail?.primary_seed) {\n try {\n const endsBatch = await callGraphBatch(trackedClient, network, [\n { id: 'money_trail_ends', query: moneyTrailEndsQuery(preliminaryMoneyTrail.primary_seed) },\n ])\n moneyTrailEndRows = optionalResultsFor(endsBatch, 'money_trail_ends', partialQueryFailures)\n } catch (error) {\n // Degrade, don't fail: a transport/parse error on the second batch call\n // (unlike a query-level ok:false, which optionalResultsFor already\n // absorbs) must not blow up the whole tool -- the incident rows are\n // already in hand, so the block still reports on_trail without\n // nearest_trail_end.\n collectQueryFailure(\n partialQueryFailures,\n 'money_trail_ends',\n error instanceof Error ? error.message : String(error)\n )\n }\n }\n const moneyTrail = buildMoneyTrailBlock(moneyTrailIncidentRows, moneyTrailEndRows)\n const graphData = buildRiskGraph(address, profile, exchangeRows, network)\n const risk = riskAssessment(profile, labelRows, exchangeRows)\n const liveRiskScore = numberValue(profile['live_risk_score'])\n const liveRiskLevel = firstString(profile['live_risk_level'])\n const liveNodeVerdict =\n liveRiskScore !== undefined || liveRiskLevel\n ? {\n ...(liveRiskScore !== undefined ? { risk_score: liveRiskScore } : {}),\n ...(liveRiskLevel ? { risk_level: liveRiskLevel } : {}),\n source: 'topology_node',\n }\n : undefined\n\n const lines = [\n `Address risk for ${network}:${address}`,\n '',\n `Risk: ${risk['level']} (${formatRiskScore(risk['score'])})`,\n `Confidence: ${risk['confidence']}`,\n `Recommendation: ${risk['recommendation']}`,\n ...(liveNodeVerdict\n ? [`Live node triage: ${liveRiskLevel ?? 'unknown'} (${formatRiskScore(liveRiskScore)})`]\n : []),\n `Graph degree: in ${profile['degree_in'] ?? 'unknown'}, out ${profile['degree_out'] ?? 'unknown'}.`,\n '',\n 'Exchange behavior',\n ...(exchangeRows.length > 0\n ? [\n formatExchangeRows(exchangeRows).join('\\n'),\n ...(exchangeSearchComplete\n ? []\n : [\n `(incomplete: ${exchangeSearchFailures.length} other hop-depth quer${exchangeSearchFailures.length === 1 ? 'y' : 'ies'} failed -- there may be more exchange exposure than shown here)`,\n ]),\n ]\n : [\n exchangeSearchComplete\n ? '- No exchange inflow/outflow paths found in bounded search.'\n : `- Exchange search incomplete: ${exchangeSearchFailures.length} hop-depth quer${exchangeSearchFailures.length === 1 ? 'y' : 'ies'} failed before returning a result. This is NOT a clean finding -- retry or narrow the search (see Partial query failures below).`,\n ]),\n ]\n if (Array.isArray(risk['drivers']) && risk['drivers'].length > 0) {\n lines.push('', 'Risk drivers', risk['drivers'].map((driver) => `- ${driver}`).join('\\n'))\n }\n if (moneyTrail) {\n lines.push('', moneyTrailSummarySentence(moneyTrail))\n }\n if (compareAddress) {\n lines.push(\n '',\n `Connection compare target: ${compareAddress}`,\n connections.length > 0\n ? `Connection paths found: ${connections.length}`\n : 'Connection paths found: 0'\n )\n }\n if (compareUnresolved) {\n lines.push(\n '',\n `Unresolved compare_address: no Address found for \"${compareInput}\"; comparison skipped.`\n )\n }\n if (partialQueryFailures.length > 0) {\n lines.push(\n '',\n 'Partial query failures',\n partialQueryFailures.map((failure) => `- ${failure.id}: ${failure.error}`).join('\\n')\n )\n }\n const summaryText = lines.join('\\n')\n const structuredContent = {\n schema: 'chain-insights.result.v1',\n tool: 'aml_address_risk',\n facts: {\n subject: {\n network,\n addresses: compareAddress ? [address, compareAddress] : [address],\n },\n risk: {\n ...risk,\n ...(liveNodeVerdict ? { live_node: liveNodeVerdict } : {}),\n },\n exchange_behavior: {\n outflows,\n inflows,\n search_status: exchangeSearchComplete ? 'complete' : 'incomplete',\n ...(exchangeSearchComplete\n ? {}\n : { failed_query_ids: exchangeSearchFailures.map((failure) => failure.id) }),\n },\n connection: compareAddress\n ? {\n compare_address: compareAddress,\n paths: connections,\n ...(routeEvidence ? { route_evidence: routeEvidence } : {}),\n }\n : undefined,\n money_trail: moneyTrail,\n unresolved: compareUnresolved ? [compareInput] : undefined,\n partial_query_errors: partialQueryFailures.length > 0 ? partialQueryFailures : undefined,\n usage: usageBlock(usage),\n },\n }\n const artifacts = options.writeArtifacts\n ? await writeAddressRiskArtifacts(\n network,\n address,\n compareAddress,\n graphData,\n exchangeRows,\n summaryText\n )\n : statelessArtifacts()\n const evidence = artifactEvidence(artifacts)\n\n return {\n summaryText,\n structuredContent: {\n ...structuredContent,\n artifacts,\n evidence: [\n ...evidence,\n {\n evidence_type: 'tool_summary',\n summary: `aml_address_risk ${address} completed for ${network}`,\n },\n ],\n },\n graphData,\n }\n}\n\n/**\n * Routes the public AML tool contract to the requested implementation. An\n * omitted version always follows the latest supported contract.\n */\nexport async function runAmlAddressRisk(\n remoteClient: Client,\n options: AddressRiskOptions,\n requestedVersion?: string\n) {\n const version = resolveAmlAddressRiskVersion(requestedVersion)\n switch (version) {\n case 'v1': {\n const result = await addressRisk(remoteClient, options)\n return {\n ...result,\n structuredContent: {\n ...result.structuredContent,\n tool_version: version,\n },\n }\n }\n }\n}\n\nfunction toCsvValue(value: unknown): string {\n return value === undefined || value === null ? '' : String(value)\n}\n\nfunction subjectNodeForExchangeRow(row: Record<string, unknown>, fallbackAddress: string): string {\n return String(\n row['direction'] === 'inflow'\n ? (row['withdrawal_address'] ?? row['deposit_address'] ?? fallbackAddress)\n : (row['deposit_address'] ?? row['withdrawal_address'] ?? fallbackAddress)\n )\n}\n\nfunction buildAddressRiskTableHtml(\n tool: string,\n network: string,\n rows: Array<Record<string, unknown>>,\n subject: string\n): string {\n const headers = [\n 'direction',\n 'exchange_address',\n 'subject_path_node',\n 'hops',\n 'amount_usd_sum',\n 'tx_count',\n ] as const\n const body = rows\n .map((row) => {\n const exchangeAddress = String(row['exchange_address'] ?? '')\n const subjectNode = subjectNodeForExchangeRow(row, subject)\n const rowValues = [\n row['direction'] ?? '',\n exchangeAddress,\n subjectNode,\n row['hops'] ?? '',\n row['amount_usd_sum'] ?? '',\n row['tx_count'] ?? '',\n ]\n return `<tr>${rowValues.map((value) => `<td>${htmlEscape(toCsvValue(value))}</td>`).join('')}</tr>`\n })\n .join('\\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\">\n<title>${htmlEscape(tool)} Risk Table</title>\n<style>\n :root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui; background: #0b0d12; color: #f4f2ea; }\n body { margin: 0; background: #0b0d12; color: #f4f2ea; }\n main { padding: 24px; }\n h1 { font-size: 20px; margin: 0 0 8px; font-weight: 650; }\n table { border-collapse: collapse; width: 100%; min-width: 900px; }\n th, td { border-bottom: 1px solid rgba(255,255,255,.08); padding: 8px 10px; text-align: left; }\n th { position: sticky; top: 0; background: #161a24; color: #f2dda6; font-weight: 600; }\n</style>\n</head>\n<body>\n<main>\n <h1>${htmlEscape(tool)} Table</h1>\n <div>Network: <strong>${htmlEscape(network)}</strong></div>\n <div>Generated: <strong>${htmlEscape(new Date().toISOString())}</strong></div>\n <table>\n <thead><tr>${headers.map((header) => `<th>${htmlEscape(header)}</th>`).join('')}</tr></thead>\n <tbody>${body}</tbody>\n </table>\n</main>\n</body>\n</html>\n`\n}\n\nasync function writeAddressRiskArtifacts(\n network: string,\n address: string,\n compareAddress: string | undefined,\n graphData: Record<string, unknown>,\n exchangeRows: Array<Record<string, unknown>>,\n summaryText: string\n): Promise<Record<string, string>> {\n const paths = workspaceOutputPaths()\n await Promise.all([\n mkdir(paths.reportsRoot, { recursive: true }),\n mkdir(paths.reportGraphsRoot, { recursive: true }),\n mkdir(paths.reportTablesRoot, { recursive: true }),\n ])\n const safeNetwork = network.replace(/[^A-Za-z0-9._-]+/g, '_')\n const safeAddress = address.replace(/[^A-Za-z0-9._-]+/g, '_')\n const slug = `${new Date()\n .toISOString()\n .replace(/[-:]/g, '')\n .replace(/\\.\\d{3}Z$/, 'Z')}_aml_address_risk_${safeNetwork}_${safeAddress}`\n const graphPath = path.join(paths.reportGraphsRoot, `${slug}.graph.json`)\n const tableJsonPath = path.join(paths.reportTablesRoot, `${slug}.compact-evidence.json`)\n const csvPath = path.join(paths.reportTablesRoot, `${slug}.flows.csv`)\n const tableHtmlPath = path.join(paths.reportsRoot, `${slug}.table.html`)\n const reportPath = path.join(paths.reportsRoot, `${slug}.aml-address-report.md`)\n const graphHtmlPath = path.join(paths.reportsRoot, `${slug}.graph.html`)\n const { generateInlineGraphHtml } = await import('../viz/html-generator.js')\n const header = [\n 'direction',\n 'exchange_address',\n 'subject_path_node',\n 'hops',\n 'amount_usd_sum',\n 'tx_count',\n ]\n const csv =\n [\n header.join(','),\n ...exchangeRows.map((row) => {\n const exchangeAddress = String(row['exchange_address'] ?? '')\n const subjectPathNode = subjectNodeForExchangeRow(row, address)\n return [\n row['direction'] ?? '',\n exchangeAddress,\n subjectPathNode,\n row['hops'] ?? '',\n row['amount_usd_sum'] ?? '',\n row['tx_count'] ?? '',\n ]\n .map((value) => JSON.stringify(String(value)))\n .join(',')\n }),\n ].join('\\n') + '\\n'\n const evidence = {\n schema: 'chain-insights.trace.v1',\n tool: 'aml_address_risk',\n network,\n input: {\n address,\n ...(compareAddress ? { compare_address: compareAddress } : {}),\n },\n profile: {\n exchange_rows: exchangeRows.map((row) => ({\n direction: row['direction'],\n exchange_address: row['exchange_address'],\n subject_node: subjectNodeForExchangeRow(row, address),\n hops: row['hops'],\n amount_usd_sum: row['amount_usd_sum'],\n tx_count: row['tx_count'],\n })),\n report_summary: summaryText,\n },\n }\n await writeFile(graphPath, JSON.stringify(graphData, null, 2) + '\\n', { mode: 0o600 })\n await writeFile(tableJsonPath, JSON.stringify(evidence, null, 2) + '\\n', { mode: 0o600 })\n await writeFile(csvPath, csv, { mode: 0o600 })\n await writeFile(\n tableHtmlPath,\n buildAddressRiskTableHtml('aml_address_risk', network, exchangeRows, address),\n { mode: 0o600 }\n )\n await writeFile(graphHtmlPath, generateInlineGraphHtml(graphData), { mode: 0o600 })\n await writeFile(\n reportPath,\n [\n `# Address Risk Report (${network}:${address})`,\n `- Graph JSON: ${graphPath}`,\n `- Table JSON: ${tableJsonPath}`,\n `- CSV: ${csvPath}`,\n `- Report HTML: ${tableHtmlPath}`,\n `- Graph HTML: ${graphHtmlPath}`,\n '',\n summaryText,\n ].join('\\n'),\n { mode: 0o600 }\n )\n\n return {\n graph_json: graphPath,\n graph_html: graphHtmlPath,\n table_json: tableJsonPath,\n flows_csv: csvPath,\n table_html: tableHtmlPath,\n report_md: reportPath,\n }\n}\n\nfunction statelessArtifacts(): Record<string, unknown> {\n return {\n artifacts_written: false,\n artifact_mode: 'stateless',\n }\n}\n\nfunction artifactEvidence(artifacts: Record<string, unknown>): Array<Record<string, unknown>> {\n return Object.entries(artifacts)\n .filter(\n (entry): entry is [string, string] =>\n entry[0] !== 'artifact_mode' && typeof entry[1] === 'string' && entry[1].length > 0\n )\n .map(([kind, filePath]) => ({\n evidence_type: 'artifact_pointer',\n path: filePath,\n summary: `${kind} artifact`,\n }))\n}\n\nfunction htmlEscape(value: unknown): string {\n return String(value ?? '')\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;')\n}\n\n// Deterministic query-builder surface for the committed corpus and\n// query-with-a-documented-call-contract tests. Reflects the retained\n// aml_address_risk/graph query builders after the aml_trace_* cut.\n\n// pairAnchorQuery resolves a pair's first/last transaction id through the\n// facts lane (openspec dozerdb-flows-to-slim-schema: the topology edge\n// carries no tx anchors). order 'ASC' returns the first transfer between the\n// pair, 'DESC' the latest. Bounded: LIMIT 1 on an indexed endpoint pattern.\nexport function pairAnchorQuery(from: string, to: string, order: 'ASC' | 'DESC' = 'ASC'): string {\n return [\n 'USE facts',\n `MATCH (a:Address {address: \"${escapeCypherString(from)}\"})-[t:TRANSFER]->(b:Address {address: \"${escapeCypherString(to)}\"})`,\n 'RETURN t.tx_id AS tx_id, t.block_timestamp AS block_timestamp',\n `ORDER BY t.block_timestamp ${order} LIMIT 1`,\n ].join(' ')\n}\n\n// inlineAverageFlowQuery is the documented recipe for the retired\n// avg_tx_size_usd edge property: compute the average at read time from the\n// two fields the slim edge carries.\nexport function inlineAverageFlowQuery(address: string, limit = 25): { query: string } {\n return {\n query: [\n `MATCH (a:Address {address: \"${escapeCypherString(address)}\"})-[r:FLOWS_TO]->(b:Address)`,\n 'RETURN b.address AS neighbor, r.amount_usd_sum AS amount_usd_sum, r.tx_count AS tx_count,',\n ' r.amount_usd_sum / toFloat(r.tx_count) AS avg_tx_size_usd',\n `ORDER BY r.amount_usd_sum DESC LIMIT ${limit}`,\n ].join(' '),\n }\n}\n\nexport const queryBuilderContract = {\n topologyGraphQuery,\n pairAnchorQuery,\n inlineAverageFlowQuery,\n addressProfileQuery,\n compareAddressExistsQuery,\n addressFeatureQuery,\n exchangeOutflowQueries,\n exchangeInflowQueries,\n connectionProbeQuery,\n connectionRouteQueries,\n linkedExposureQueries,\n crossSpaceLinkedQuery,\n}\n"],"mappings":"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"}