sonobat 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1985 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/db/migrations/v0.ts","../src/db/migrations/v1.ts","../src/db/migrations/v2.ts","../src/db/migrations/v3.ts","../src/db/migrations/v4.ts","../src/db/migrations/v5.ts","../src/db/migrations/index.ts","../src/db/migrate.ts","../src/mcp/server.ts","../src/mcp/tools/query.ts","../src/db/repository/node-repository.ts","../src/types/graph.ts","../src/db/repository/edge-repository.ts","../src/db/repository/graph-query-repository.ts","../src/mcp/tools/mutate.ts","../src/mcp/tools/ingest.ts","../src/engine/ingest.ts","../src/parser/nmap-parser.ts","../src/types/parser.ts","../src/parser/ffuf-parser.ts","../src/parser/nuclei-parser.ts","../src/engine/normalizer.ts","../src/mcp/tools/propose.ts","../src/engine/proposer.ts","../src/mcp/tools/kb.ts","../src/db/repository/technique-doc-repository.ts","../src/engine/indexer.ts","../src/engine/data-dir.ts","../src/engine/git-ops.ts","../src/mcp/resources.ts"],"sourcesContent":["/**\r\n * sonobat — AttackDataGraph for autonomous penetration testing\r\n *\r\n * MCP Server エントリポイント。\r\n * stdio トランスポートで LLM Agent と接続する。\r\n */\r\n\r\nimport { readFileSync } from 'node:fs';\r\nimport { dirname, resolve } from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport Database from 'better-sqlite3';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport { migrateDatabase } from './db/migrate.js';\r\nimport { createMcpServer } from './mcp/server.js';\r\n\r\nconst __dirname = dirname(fileURLToPath(import.meta.url));\r\nconst pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8')) as {\r\n version: string;\r\n};\r\n\r\nconst DB_PATH = process.env['SONOBAT_DB_PATH'] ?? 'sonobat.db';\r\nconst db = new Database(DB_PATH);\r\nmigrateDatabase(db);\r\n\r\nconst server = createMcpServer(db, pkg.version);\r\nconst transport = new StdioServerTransport();\r\nawait server.connect(transport);\r\n","/**\r\n * Migration v0: Base schema\r\n *\r\n * 元の sonobat スキーマ(v1/v2/v3 の追加分を除く)。\r\n * fresh DB でマイグレーション順次実行するための基盤。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Migration } from './index.js';\r\n\r\nconst migration: Migration = {\r\n version: 0,\r\n description: 'Base schema (scans, artifacts, hosts, services, endpoints, etc.)',\r\n up(db: Database.Database): void {\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS scans (\r\n id TEXT PRIMARY KEY,\r\n started_at TEXT NOT NULL,\r\n finished_at TEXT,\r\n notes TEXT\r\n );\r\n\r\n CREATE TABLE IF NOT EXISTS artifacts (\r\n id TEXT PRIMARY KEY,\r\n scan_id TEXT,\r\n tool TEXT NOT NULL,\r\n kind TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n sha256 TEXT,\r\n captured_at TEXT NOT NULL,\r\n attrs_json TEXT,\r\n FOREIGN KEY (scan_id) REFERENCES scans(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_tool ON artifacts(tool);\r\n\r\n CREATE TABLE IF NOT EXISTS hosts (\r\n id TEXT PRIMARY KEY,\r\n authority_kind TEXT NOT NULL,\r\n authority TEXT NOT NULL UNIQUE,\r\n resolved_ips_json TEXT NOT NULL DEFAULT '[]',\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n\r\n CREATE TABLE IF NOT EXISTS vhosts (\r\n id TEXT PRIMARY KEY,\r\n host_id TEXT NOT NULL,\r\n hostname TEXT NOT NULL,\r\n source TEXT,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (host_id, hostname),\r\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_vhosts_host ON vhosts(host_id);\r\n\r\n CREATE TABLE IF NOT EXISTS services (\r\n id TEXT PRIMARY KEY,\r\n host_id TEXT NOT NULL,\r\n transport TEXT NOT NULL,\r\n port INTEGER NOT NULL,\r\n app_proto TEXT NOT NULL,\r\n proto_confidence TEXT NOT NULL,\r\n banner TEXT,\r\n product TEXT,\r\n version TEXT,\r\n state TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n UNIQUE (host_id, transport, port),\r\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_services_host ON services(host_id);\r\n\r\n CREATE TABLE IF NOT EXISTS service_observations (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n key TEXT NOT NULL,\r\n value TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_svc_obs_service ON service_observations(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS http_endpoints (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n vhost_id TEXT,\r\n base_uri TEXT NOT NULL,\r\n method TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n status_code INTEGER,\r\n content_length INTEGER,\r\n words INTEGER,\r\n lines INTEGER,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (service_id, method, path),\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (vhost_id) REFERENCES vhosts(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_endpoints_service ON http_endpoints(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS inputs (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n location TEXT NOT NULL,\r\n name TEXT NOT NULL,\r\n type_hint TEXT,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n UNIQUE (service_id, location, name),\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_inputs_service ON inputs(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS endpoint_inputs (\r\n id TEXT PRIMARY KEY,\r\n endpoint_id TEXT NOT NULL,\r\n input_id TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (endpoint_id, input_id),\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE CASCADE,\r\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_ep_inputs_endpoint ON endpoint_inputs(endpoint_id);\r\n CREATE INDEX IF NOT EXISTS idx_ep_inputs_input ON endpoint_inputs(input_id);\r\n\r\n CREATE TABLE IF NOT EXISTS observations (\r\n id TEXT PRIMARY KEY,\r\n input_id TEXT NOT NULL,\r\n raw_value TEXT NOT NULL,\r\n norm_value TEXT NOT NULL,\r\n body_path TEXT,\r\n source TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n observed_at TEXT NOT NULL,\r\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_obs_input ON observations(input_id);\r\n\r\n CREATE TABLE IF NOT EXISTS credentials (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n endpoint_id TEXT,\r\n username TEXT NOT NULL,\r\n secret TEXT NOT NULL,\r\n secret_type TEXT NOT NULL,\r\n source TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_creds_service ON credentials(service_id);\r\n CREATE INDEX IF NOT EXISTS idx_creds_endpoint ON credentials(endpoint_id);\r\n\r\n CREATE TABLE IF NOT EXISTS vulnerabilities (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n endpoint_id TEXT,\r\n vuln_type TEXT NOT NULL,\r\n title TEXT NOT NULL,\r\n description TEXT,\r\n severity TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_vulns_service ON vulnerabilities(service_id);\r\n CREATE INDEX IF NOT EXISTS idx_vulns_endpoint ON vulnerabilities(endpoint_id);\r\n CREATE INDEX IF NOT EXISTS idx_vulns_severity ON vulnerabilities(severity);\r\n\r\n CREATE TABLE IF NOT EXISTS cves (\r\n id TEXT PRIMARY KEY,\r\n vulnerability_id TEXT NOT NULL,\r\n cve_id TEXT NOT NULL,\r\n description TEXT,\r\n cvss_score REAL,\r\n cvss_vector TEXT,\r\n reference_url TEXT,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_cves_vuln ON cves(vulnerability_id);\r\n CREATE INDEX IF NOT EXISTS idx_cves_cveid ON cves(cve_id);\r\n `);\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\n * Migration v1: Add datalog_rules table\n *\n * This migration adds the datalog_rules table for storing\n * custom Datalog rules created by humans or AI agents.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 1,\n description: 'Add datalog_rules table',\n up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS datalog_rules (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n rule_text TEXT NOT NULL,\n generated_by TEXT NOT NULL, -- \"human\" | \"ai\" | \"preset\"\n is_preset INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_datalog_rules_name ON datalog_rules(name);\n `);\n },\n};\n\nexport default migration;\n","/**\n * Migration v2: Add status column to vulnerabilities table\n *\n * Adds a `status` column to track the verification state of vulnerabilities.\n * Values: 'unverified' (default), 'confirmed', 'false_positive', 'not_exploitable'\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 2,\n description: 'Add status column to vulnerabilities table',\n up(db: Database.Database): void {\n db.exec(`\n ALTER TABLE vulnerabilities ADD COLUMN status TEXT NOT NULL DEFAULT 'unverified';\n `);\n },\n};\n\nexport default migration;\n","/**\n * Migration v3: Add technique_docs table with FTS5 full-text search\n *\n * Creates a technique documentation table for indexing penetration testing\n * knowledge (e.g. HackTricks) and a FTS5 virtual table for efficient search.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 3,\n description: 'Add technique_docs table with FTS5 full-text search',\n up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS technique_docs (\n id TEXT PRIMARY KEY,\n source TEXT NOT NULL,\n file_path TEXT NOT NULL,\n title TEXT NOT NULL,\n category TEXT NOT NULL,\n content TEXT NOT NULL,\n chunk_index INTEGER NOT NULL,\n indexed_at TEXT NOT NULL\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS technique_docs_fts USING fts5(\n title, category, content,\n content=technique_docs,\n content_rowid=rowid,\n tokenize='porter unicode61'\n );\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_ai AFTER INSERT ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(rowid, title, category, content)\n VALUES (new.rowid, new.title, new.category, new.content);\n END;\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_ad AFTER DELETE ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(technique_docs_fts, rowid, title, category, content)\n VALUES ('delete', old.rowid, old.title, old.category, old.content);\n END;\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_au AFTER UPDATE ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(technique_docs_fts, rowid, title, category, content)\n VALUES ('delete', old.rowid, old.title, old.category, old.content);\n INSERT INTO technique_docs_fts(rowid, title, category, content)\n VALUES (new.rowid, new.title, new.category, new.content);\n END;\n `);\n },\n};\n\nexport default migration;\n","/**\r\n * Migration v4: Graph-native schema\r\n *\r\n * 12 エンティティテーブル → nodes + edges の 2 テーブルに移行。\r\n * 既存データをマイグレーションし、旧テーブルを DROP する。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Migration } from './index.js';\r\nimport { randomUUID } from 'node:crypto';\r\n\r\nconst migration: Migration = {\r\n version: 4,\r\n description: 'Graph-native schema: nodes + edges tables',\r\n up(db: Database.Database): void {\r\n // --------------------------------------------------\r\n // 1. nodes + edges テーブル作成\r\n // --------------------------------------------------\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS nodes (\r\n id TEXT PRIMARY KEY,\r\n kind TEXT NOT NULL,\r\n natural_key TEXT NOT NULL UNIQUE,\r\n props_json TEXT NOT NULL DEFAULT '{}',\r\n evidence_artifact_id TEXT REFERENCES artifacts(id),\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);\r\n CREATE INDEX IF NOT EXISTS idx_nodes_evidence ON nodes(evidence_artifact_id);\r\n\r\n CREATE TABLE IF NOT EXISTS edges (\r\n id TEXT PRIMARY KEY,\r\n kind TEXT NOT NULL,\r\n source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\r\n target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\r\n props_json TEXT NOT NULL DEFAULT '{}',\r\n evidence_artifact_id TEXT REFERENCES artifacts(id),\r\n created_at TEXT NOT NULL,\r\n UNIQUE(kind, source_id, target_id)\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\r\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\r\n CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);\r\n `);\r\n\r\n // --------------------------------------------------\r\n // 2. 既存データのマイグレーション\r\n // --------------------------------------------------\r\n\r\n // ヘルパー: edge 挿入\r\n const insertEdge = db.prepare(`\r\n INSERT INTO edges (id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at)\r\n VALUES (?, ?, ?, ?, '{}', ?, ?)\r\n `);\r\n\r\n const insertNode = db.prepare(`\r\n INSERT INTO nodes (id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n // テーブル存在チェック\r\n const tableExists = (name: string): boolean => {\r\n const row = db\r\n .prepare(\"SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name=?\")\r\n .get(name) as { cnt: number };\r\n return row.cnt > 0;\r\n };\r\n\r\n // 2a. hosts → nodes (kind=\"host\")\r\n if (tableExists('hosts')) {\r\n const hosts = db.prepare('SELECT * FROM hosts').all() as Array<Record<string, unknown>>;\r\n for (const h of hosts) {\r\n const props = JSON.stringify({\r\n authorityKind: h.authority_kind,\r\n authority: h.authority,\r\n resolvedIpsJson: h.resolved_ips_json ?? '[]',\r\n });\r\n insertNode.run(\r\n h.id,\r\n 'host',\r\n `host:${h.authority}`,\r\n props,\r\n null, // hosts didn't have evidence_artifact_id\r\n h.created_at,\r\n h.updated_at,\r\n );\r\n }\r\n }\r\n\r\n // 2b. vhosts → nodes (kind=\"vhost\") + edge (HOST_VHOST)\r\n if (tableExists('vhosts')) {\r\n const vhosts = db.prepare('SELECT * FROM vhosts').all() as Array<Record<string, unknown>>;\r\n for (const v of vhosts) {\r\n const props = JSON.stringify({\r\n hostname: v.hostname,\r\n source: v.source ?? undefined,\r\n });\r\n insertNode.run(\r\n v.id,\r\n 'vhost',\r\n `vhost:${v.host_id}:${v.hostname}`,\r\n props,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n v.created_at, // vhosts had no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'HOST_VHOST',\r\n v.host_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2c. services → nodes (kind=\"service\") + edge (HOST_SERVICE)\r\n if (tableExists('services')) {\r\n const services = db.prepare('SELECT * FROM services').all() as Array<Record<string, unknown>>;\r\n for (const s of services) {\r\n const props = JSON.stringify({\r\n transport: s.transport,\r\n port: s.port,\r\n appProto: s.app_proto,\r\n protoConfidence: s.proto_confidence,\r\n banner: s.banner ?? undefined,\r\n product: s.product ?? undefined,\r\n version: s.version ?? undefined,\r\n state: s.state,\r\n });\r\n insertNode.run(\r\n s.id,\r\n 'service',\r\n `svc:${s.host_id}:${s.transport}:${s.port}`,\r\n props,\r\n s.evidence_artifact_id,\r\n s.created_at,\r\n s.updated_at,\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'HOST_SERVICE',\r\n s.host_id,\r\n s.id,\r\n s.evidence_artifact_id,\r\n s.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2d. service_observations → nodes (kind=\"svc_observation\") + edge (SERVICE_OBSERVATION)\r\n if (tableExists('service_observations')) {\r\n const svcObs = db.prepare('SELECT * FROM service_observations').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const so of svcObs) {\r\n const props = JSON.stringify({\r\n key: so.key,\r\n value: so.value,\r\n confidence: so.confidence,\r\n });\r\n insertNode.run(\r\n so.id,\r\n 'svc_observation',\r\n `svcobs:${so.id}`,\r\n props,\r\n so.evidence_artifact_id,\r\n so.created_at,\r\n so.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_OBSERVATION',\r\n so.service_id,\r\n so.id,\r\n so.evidence_artifact_id,\r\n so.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2e. http_endpoints → nodes (kind=\"endpoint\") + edge (SERVICE_ENDPOINT) + optional (VHOST_ENDPOINT)\r\n if (tableExists('http_endpoints')) {\r\n const endpoints = db.prepare('SELECT * FROM http_endpoints').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const ep of endpoints) {\r\n const props = JSON.stringify({\r\n baseUri: ep.base_uri,\r\n method: ep.method,\r\n path: ep.path,\r\n statusCode: ep.status_code ?? undefined,\r\n contentLength: ep.content_length ?? undefined,\r\n words: ep.words ?? undefined,\r\n lines: ep.lines ?? undefined,\r\n });\r\n insertNode.run(\r\n ep.id,\r\n 'endpoint',\r\n `ep:${ep.service_id}:${ep.method}:${ep.path}`,\r\n props,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n ep.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_ENDPOINT',\r\n ep.service_id,\r\n ep.id,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n );\r\n // Optional: VHOST_ENDPOINT\r\n if (ep.vhost_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'VHOST_ENDPOINT',\r\n ep.vhost_id,\r\n ep.id,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2f. inputs → nodes (kind=\"input\") + edge (SERVICE_INPUT)\r\n if (tableExists('inputs')) {\r\n const inputs = db.prepare('SELECT * FROM inputs').all() as Array<Record<string, unknown>>;\r\n for (const inp of inputs) {\r\n const props = JSON.stringify({\r\n location: inp.location,\r\n name: inp.name,\r\n typeHint: inp.type_hint ?? undefined,\r\n });\r\n insertNode.run(\r\n inp.id,\r\n 'input',\r\n `in:${inp.service_id}:${inp.location}:${inp.name}`,\r\n props,\r\n null, // inputs had no evidence_artifact_id\r\n inp.created_at,\r\n inp.updated_at,\r\n );\r\n insertEdge.run(randomUUID(), 'SERVICE_INPUT', inp.service_id, inp.id, null, inp.created_at);\r\n }\r\n }\r\n\r\n // 2g. endpoint_inputs → edges (ENDPOINT_INPUT)\r\n if (tableExists('endpoint_inputs')) {\r\n const epInputs = db.prepare('SELECT * FROM endpoint_inputs').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const ei of epInputs) {\r\n insertEdge.run(\r\n ei.id,\r\n 'ENDPOINT_INPUT',\r\n ei.endpoint_id,\r\n ei.input_id,\r\n ei.evidence_artifact_id,\r\n ei.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2h. observations → nodes (kind=\"observation\") + edge (INPUT_OBSERVATION)\r\n if (tableExists('observations')) {\r\n const obs = db.prepare('SELECT * FROM observations').all() as Array<Record<string, unknown>>;\r\n for (const o of obs) {\r\n const props = JSON.stringify({\r\n rawValue: o.raw_value,\r\n normValue: o.norm_value,\r\n bodyPath: o.body_path ?? undefined,\r\n source: o.source,\r\n confidence: o.confidence,\r\n observedAt: o.observed_at,\r\n });\r\n insertNode.run(\r\n o.id,\r\n 'observation',\r\n `obs:${o.id}`,\r\n props,\r\n o.evidence_artifact_id,\r\n o.observed_at,\r\n o.observed_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'INPUT_OBSERVATION',\r\n o.input_id,\r\n o.id,\r\n o.evidence_artifact_id,\r\n o.observed_at,\r\n );\r\n }\r\n }\r\n\r\n // 2i. credentials → nodes (kind=\"credential\") + edge (SERVICE_CREDENTIAL) + optional (ENDPOINT_CREDENTIAL)\r\n if (tableExists('credentials')) {\r\n const creds = db.prepare('SELECT * FROM credentials').all() as Array<Record<string, unknown>>;\r\n for (const c of creds) {\r\n const props = JSON.stringify({\r\n username: c.username,\r\n secret: c.secret,\r\n secretType: c.secret_type,\r\n source: c.source,\r\n confidence: c.confidence,\r\n });\r\n insertNode.run(\r\n c.id,\r\n 'credential',\r\n `cred:${c.id}`,\r\n props,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n c.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_CREDENTIAL',\r\n c.service_id,\r\n c.id,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n );\r\n if (c.endpoint_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'ENDPOINT_CREDENTIAL',\r\n c.endpoint_id,\r\n c.id,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2j. vulnerabilities → nodes (kind=\"vulnerability\") + edge (SERVICE_VULNERABILITY) + optional (ENDPOINT_VULNERABILITY)\r\n if (tableExists('vulnerabilities')) {\r\n const vulns = db.prepare('SELECT * FROM vulnerabilities').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const v of vulns) {\r\n const props = JSON.stringify({\r\n vulnType: v.vuln_type,\r\n title: v.title,\r\n description: v.description ?? undefined,\r\n severity: v.severity,\r\n confidence: v.confidence,\r\n status: v.status ?? 'unverified',\r\n });\r\n insertNode.run(\r\n v.id,\r\n 'vulnerability',\r\n `vuln:${v.id}`,\r\n props,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n v.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_VULNERABILITY',\r\n v.service_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n if (v.endpoint_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'ENDPOINT_VULNERABILITY',\r\n v.endpoint_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2k. cves → nodes (kind=\"cve\") + edge (VULNERABILITY_CVE)\r\n if (tableExists('cves')) {\r\n const cves = db.prepare('SELECT * FROM cves').all() as Array<Record<string, unknown>>;\r\n for (const c of cves) {\r\n const props = JSON.stringify({\r\n cveId: c.cve_id,\r\n description: c.description ?? undefined,\r\n cvssScore: c.cvss_score ?? undefined,\r\n cvssVector: c.cvss_vector ?? undefined,\r\n referenceUrl: c.reference_url ?? undefined,\r\n });\r\n insertNode.run(\r\n c.id,\r\n 'cve',\r\n `cve:${c.vulnerability_id}:${c.cve_id}`,\r\n props,\r\n null, // cves had no evidence_artifact_id\r\n c.created_at,\r\n c.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'VULNERABILITY_CVE',\r\n c.vulnerability_id,\r\n c.id,\r\n null,\r\n c.created_at,\r\n );\r\n }\r\n }\r\n\r\n // --------------------------------------------------\r\n // 3. 旧テーブル DROP\r\n // FK 依存順に削除(子テーブルから親テーブルへ)\r\n // --------------------------------------------------\r\n // FK を一時的に無効化して DROP\r\n db.pragma('foreign_keys = OFF');\r\n\r\n const tablesToDrop = [\r\n 'endpoint_inputs',\r\n 'observations',\r\n 'credentials',\r\n 'cves',\r\n 'vulnerabilities',\r\n 'http_endpoints',\r\n 'service_observations',\r\n 'inputs',\r\n 'services',\r\n 'vhosts',\r\n 'hosts',\r\n 'datalog_rules',\r\n ];\r\n\r\n for (const table of tablesToDrop) {\r\n if (tableExists(table)) {\r\n db.exec(`DROP TABLE ${table}`);\r\n }\r\n }\r\n\r\n // FK を再度有効化\r\n db.pragma('foreign_keys = ON');\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\r\n * Migration v5: file_mtime + 運用テーブル (Continuous STG Pentest)\r\n *\r\n * Part A: technique_docs\r\n * - Adds `file_mtime` TEXT column (NULL-able) for incremental indexing.\r\n * - Adds composite index on (source, file_path) for efficient mtime lookups.\r\n *\r\n * Part B: 7 新テーブル\r\n * - engagements, runs, action_queue, action_executions,\r\n * findings, finding_events, risk_snapshots\r\n *\r\n * Part C: scans/artifacts ALTER TABLE(リネージカラム追加)\r\n * - scans に engagement_id, run_id を追加\r\n * - artifacts に engagement_id, run_id, action_execution_id を追加\r\n *\r\n * Part D: 既存データの backfill\r\n * - 既存 scans がある場合、デフォルト engagement を作成\r\n * - scans.engagement_id と artifacts.engagement_id をデフォルト engagement で埋める\r\n *\r\n * See docs/v5-db-design.md for full design rationale.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport { randomUUID } from 'node:crypto';\r\nimport type { Migration } from './index.js';\r\n\r\nconst migration: Migration = {\r\n version: 5,\r\n description:\r\n 'Add file_mtime, operational tables (engagements, runs, action_queue, etc.), and lineage columns',\r\n up(db: Database.Database): void {\r\n // -------------------------------------------------------\r\n // Part A: technique_docs — file_mtime + 複合インデックス\r\n // -------------------------------------------------------\r\n db.exec(`\r\n ALTER TABLE technique_docs ADD COLUMN file_mtime TEXT;\r\n\r\n CREATE INDEX IF NOT EXISTS idx_technique_docs_source_filepath\r\n ON technique_docs(source, file_path);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part B: 運用テーブル — Continuous STG Pentest\r\n // -------------------------------------------------------\r\n\r\n // 1) engagements\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS engagements (\r\n id TEXT PRIMARY KEY,\r\n name TEXT NOT NULL,\r\n environment TEXT NOT NULL DEFAULT 'stg',\r\n scope_json TEXT NOT NULL DEFAULT '{}',\r\n policy_json TEXT NOT NULL DEFAULT '{}',\r\n schedule_cron TEXT,\r\n status TEXT NOT NULL DEFAULT 'active',\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_engagements_status ON engagements(status);\r\n `);\r\n\r\n // 2) runs\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS runs (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n trigger_kind TEXT NOT NULL,\r\n trigger_ref TEXT,\r\n status TEXT NOT NULL,\r\n started_at TEXT,\r\n finished_at TEXT,\r\n summary_json TEXT NOT NULL DEFAULT '{}',\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_runs_engagement_created ON runs(engagement_id, created_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);\r\n `);\r\n\r\n // 3) action_queue\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS action_queue (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n run_id TEXT,\r\n parent_action_id TEXT,\r\n kind TEXT NOT NULL,\r\n priority INTEGER NOT NULL DEFAULT 100,\r\n dedupe_key TEXT NOT NULL,\r\n params_json TEXT NOT NULL DEFAULT '{}',\r\n state TEXT NOT NULL,\r\n attempt_count INTEGER NOT NULL DEFAULT 0,\r\n max_attempts INTEGER NOT NULL DEFAULT 3,\r\n available_at TEXT NOT NULL,\r\n lease_owner TEXT,\r\n lease_expires_at TEXT,\r\n last_error TEXT,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (parent_action_id) REFERENCES action_queue(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_action_queue_poll\r\n ON action_queue(state, available_at, priority, created_at);\r\n CREATE INDEX IF NOT EXISTS idx_action_queue_engagement_state\r\n ON action_queue(engagement_id, state, created_at DESC);\r\n CREATE UNIQUE INDEX IF NOT EXISTS uq_action_queue_active_dedupe\r\n ON action_queue(engagement_id, dedupe_key)\r\n WHERE state IN ('queued', 'running');\r\n `);\r\n\r\n // 4) action_executions\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS action_executions (\r\n id TEXT PRIMARY KEY,\r\n action_id TEXT NOT NULL,\r\n run_id TEXT,\r\n executor TEXT NOT NULL,\r\n command TEXT,\r\n input_json TEXT NOT NULL DEFAULT '{}',\r\n output_json TEXT NOT NULL DEFAULT '{}',\r\n stdout_artifact_id TEXT,\r\n stderr_artifact_id TEXT,\r\n exit_code INTEGER,\r\n error_type TEXT,\r\n error_message TEXT,\r\n started_at TEXT NOT NULL,\r\n finished_at TEXT,\r\n duration_ms INTEGER,\r\n FOREIGN KEY (action_id) REFERENCES action_queue(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (stdout_artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL,\r\n FOREIGN KEY (stderr_artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_action_exec_action_started ON action_executions(action_id, started_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_action_exec_run_started ON action_executions(run_id, started_at DESC);\r\n `);\r\n\r\n // 5) findings\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS findings (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n canonical_key TEXT NOT NULL,\r\n node_id TEXT,\r\n title TEXT NOT NULL,\r\n severity TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n state TEXT NOT NULL,\r\n state_reason TEXT,\r\n owner TEXT,\r\n ticket_ref TEXT,\r\n first_seen_run_id TEXT,\r\n last_seen_run_id TEXT,\r\n first_seen_at TEXT NOT NULL,\r\n last_seen_at TEXT NOT NULL,\r\n sla_due_at TEXT,\r\n attrs_json TEXT NOT NULL DEFAULT '{}',\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE SET NULL,\r\n FOREIGN KEY (first_seen_run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (last_seen_run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n UNIQUE (engagement_id, canonical_key)\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_findings_engagement_state_sev\r\n ON findings(engagement_id, state, severity, last_seen_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_findings_node ON findings(node_id);\r\n `);\r\n\r\n // 6) finding_events\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS finding_events (\r\n id TEXT PRIMARY KEY,\r\n finding_id TEXT NOT NULL,\r\n run_id TEXT,\r\n event_type TEXT NOT NULL,\r\n before_json TEXT NOT NULL DEFAULT '{}',\r\n after_json TEXT NOT NULL DEFAULT '{}',\r\n artifact_id TEXT,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (finding_id) REFERENCES findings(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_finding_events_finding_created\r\n ON finding_events(finding_id, created_at DESC);\r\n `);\r\n\r\n // 7) risk_snapshots\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS risk_snapshots (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n run_id TEXT,\r\n score REAL NOT NULL,\r\n open_critical INTEGER NOT NULL DEFAULT 0,\r\n open_high INTEGER NOT NULL DEFAULT 0,\r\n open_medium INTEGER NOT NULL DEFAULT 0,\r\n open_low INTEGER NOT NULL DEFAULT 0,\r\n open_info INTEGER NOT NULL DEFAULT 0,\r\n open_total INTEGER NOT NULL DEFAULT 0,\r\n attack_path_count INTEGER NOT NULL DEFAULT 0,\r\n exposed_cred_count INTEGER NOT NULL DEFAULT 0,\r\n model_version TEXT,\r\n attrs_json TEXT NOT NULL DEFAULT '{}',\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_risk_snapshots_engagement_created\r\n ON risk_snapshots(engagement_id, created_at DESC);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part C: 既存テーブルへのリネージカラム追加\r\n // -------------------------------------------------------\r\n\r\n // scans に engagement_id, run_id を追加\r\n // NOTE: SQLite の ALTER TABLE は REFERENCES 句を無視するが、設計意図として記載\r\n db.exec(`\r\n ALTER TABLE scans ADD COLUMN engagement_id TEXT; -- REFERENCES engagements(id) ON DELETE SET NULL\r\n ALTER TABLE scans ADD COLUMN run_id TEXT; -- REFERENCES runs(id) ON DELETE SET NULL\r\n CREATE INDEX IF NOT EXISTS idx_scans_engagement_started ON scans(engagement_id, started_at DESC);\r\n `);\r\n\r\n // artifacts に engagement_id, run_id, action_execution_id を追加\r\n db.exec(`\r\n ALTER TABLE artifacts ADD COLUMN engagement_id TEXT; -- REFERENCES engagements(id) ON DELETE SET NULL\r\n ALTER TABLE artifacts ADD COLUMN run_id TEXT; -- REFERENCES runs(id) ON DELETE SET NULL\r\n ALTER TABLE artifacts ADD COLUMN action_execution_id TEXT; -- REFERENCES action_executions(id) ON DELETE SET NULL\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_engagement_captured ON artifacts(engagement_id, captured_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_run_captured ON artifacts(run_id, captured_at DESC);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part D: 既存データの backfill\r\n // -------------------------------------------------------\r\n const scanCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM scans').get() as { cnt: number }\r\n ).cnt;\r\n\r\n if (scanCount > 0) {\r\n const now = new Date().toISOString();\r\n const defaultEngId = randomUUID();\r\n\r\n // デフォルト engagement を作成\r\n db.prepare(\r\n `INSERT INTO engagements (id, name, environment, scope_json, policy_json, status, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\r\n ).run(defaultEngId, 'default', 'stg', '{}', '{}', 'active', now, now);\r\n\r\n // scans.engagement_id をデフォルト engagement で埋める\r\n db.prepare('UPDATE scans SET engagement_id = ? WHERE engagement_id IS NULL').run(\r\n defaultEngId,\r\n );\r\n\r\n // artifacts.engagement_id をデフォルト engagement で埋める\r\n db.prepare('UPDATE artifacts SET engagement_id = ? WHERE engagement_id IS NULL').run(\r\n defaultEngId,\r\n );\r\n }\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\r\n * sonobat — Database migration registry\r\n *\r\n * Manages versioned migrations using SQLite's PRAGMA user_version.\r\n * Each migration has a version number and an up() function.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport v0 from './v0.js';\r\nimport v1 from './v1.js';\r\nimport v2 from './v2.js';\r\nimport v3 from './v3.js';\r\nimport v4 from './v4.js';\r\nimport v5 from './v5.js';\r\n\r\nexport interface Migration {\r\n version: number;\r\n description: string;\r\n up(db: Database.Database): void;\r\n}\r\n\r\n/** All migrations in order. Must be sorted by version ascending. */\r\nconst migrations: Migration[] = [v0, v1, v2, v3, v4, v5];\r\n\r\n/** The latest schema version (after all migrations applied). */\r\nexport const LATEST_VERSION: number =\r\n migrations.length > 0 ? migrations[migrations.length - 1].version : 0;\r\n\r\n/**\r\n * Get the current schema version from the database.\r\n */\r\nexport function getSchemaVersion(db: Database.Database): number {\r\n const row = db.prepare('PRAGMA user_version').get() as {\r\n user_version: number;\r\n };\r\n return row.user_version;\r\n}\r\n\r\n/**\r\n * Set the schema version in the database.\r\n */\r\nexport function setSchemaVersion(db: Database.Database, version: number): void {\r\n db.pragma(`user_version = ${version}`);\r\n}\r\n\r\n/**\r\n * Run all pending migrations from currentVersion to LATEST_VERSION.\r\n * Each migration runs inside a transaction for safety.\r\n * Schema version is updated after each successful migration so that\r\n * a failure mid-sequence leaves the DB at the last completed version.\r\n */\r\nexport function runMigrations(db: Database.Database, currentVersion: number): void {\r\n for (const migration of migrations) {\r\n if (migration.version > currentVersion) {\r\n const runMigration = db.transaction(() => {\r\n migration.up(db);\r\n });\r\n runMigration();\r\n setSchemaVersion(db, migration.version);\r\n }\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport { getSchemaVersion, runMigrations, LATEST_VERSION } from './migrations/index.js';\r\n\r\n/**\r\n * Migrate the database to the latest schema version.\r\n *\r\n * - New database (user_version = 0, no tables): runs ALL migrations from v0.\r\n * - Existing database (user_version = 0, has tables): runs incremental migrations from v1.\r\n * - Partially migrated: runs remaining migrations.\r\n * - Already up-to-date (user_version = LATEST_VERSION): no-op.\r\n */\r\nexport function migrateDatabase(db: Database.Database): void {\r\n db.pragma('foreign_keys = ON');\r\n\r\n const currentVersion = getSchemaVersion(db);\r\n\r\n if (currentVersion >= LATEST_VERSION) {\r\n return;\r\n }\r\n\r\n if (currentVersion === 0) {\r\n // Check if this is a truly new DB or an existing v0 DB\r\n const tableCount = (\r\n db\r\n .prepare(\r\n \"SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\r\n )\r\n .get() as { cnt: number }\r\n ).cnt;\r\n\r\n if (tableCount === 0) {\r\n // Brand new database: run ALL migrations including v0 (base schema)\r\n runMigrations(db, -1);\r\n return;\r\n }\r\n\r\n // Existing v0 database: run incremental migrations from v1\r\n runMigrations(db, 0);\r\n return;\r\n }\r\n\r\n // Partially migrated: run remaining migrations\r\n runMigrations(db, currentVersion);\r\n}\r\n","/**\r\n * sonobat — MCP Server\r\n *\r\n * Creates and configures the MCP server with all tools and resources.\r\n */\r\n\r\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { registerQueryTool } from './tools/query.js';\r\nimport { registerMutateTool } from './tools/mutate.js';\r\nimport { registerIngestTool } from './tools/ingest.js';\r\nimport { registerProposeTool } from './tools/propose.js';\r\nimport { registerKbTools } from './tools/kb.js';\r\nimport { registerResources } from './resources.js';\r\n\r\n/**\r\n * Create a fully configured MCP server with all sonobat tools and resources.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param version - Package version string (read from package.json by caller)\r\n * @returns Configured McpServer instance\r\n */\r\nexport function createMcpServer(db: Database.Database, version?: string): McpServer {\r\n const server = new McpServer({\r\n name: 'sonobat',\r\n version: version ?? '0.0.0',\r\n });\r\n\r\n // Register tools (6 tools total)\r\n registerQueryTool(server, db);\r\n registerMutateTool(server, db);\r\n registerIngestTool(server, db);\r\n registerProposeTool(server, db);\r\n registerKbTools(server, db); // search_kb + index_kb\r\n\r\n // Register resources\r\n registerResources(server, db);\r\n\r\n return server;\r\n}\r\n","/**\r\n * sonobat — MCP Query Tool (unified)\r\n *\r\n * Single 'query' tool with an 'action' parameter that replaces\r\n * all previous read-only tools (list_hosts, get_host, list_services, etc.).\r\n *\r\n * Actions: list_nodes, get_node, traverse, summary, attack_paths\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { z } from 'zod';\r\nimport { NodeRepository } from '../../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../../db/repository/edge-repository.js';\r\nimport { GraphQueryRepository } from '../../db/repository/graph-query-repository.js';\r\nimport { NODE_KINDS, EDGE_KINDS } from '../../types/graph.js';\r\nimport type { NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\nexport function registerQueryTool(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n const graphQueryRepo = new GraphQueryRepository(db);\r\n\r\n server.tool(\r\n 'query',\r\n 'Query the AttackDataGraph. Actions: list_nodes, get_node, traverse, summary, attack_paths',\r\n {\r\n action: z.enum(['list_nodes', 'get_node', 'traverse', 'summary', 'attack_paths']),\r\n // Parameters for list_nodes\r\n kind: z.string().optional().describe('Node kind filter (host, service, endpoint, etc.)'),\r\n // Parameters for get_node\r\n id: z.string().optional().describe('Node ID'),\r\n // Parameters for traverse\r\n startId: z.string().optional().describe('Start node ID for traversal'),\r\n depth: z.number().optional().describe('Max traversal depth'),\r\n edgeKinds: z.array(z.string()).optional().describe('Edge kinds to follow'),\r\n // Parameters for attack_paths\r\n pattern: z.string().optional().describe('Preset pattern name'),\r\n // Common filters\r\n filtersJson: z\r\n .string()\r\n .optional()\r\n .describe('Filters as JSON object for list_nodes (JSON_EXTRACT on props)'),\r\n },\r\n async ({ action, kind, id, startId, depth, edgeKinds, pattern, filtersJson }) => {\r\n switch (action) {\r\n case 'list_nodes': {\r\n if (!kind) {\r\n return {\r\n content: [{ type: 'text', text: 'kind parameter required for list_nodes' }],\r\n isError: true,\r\n };\r\n }\r\n // Validate kind\r\n if (!NODE_KINDS.includes(kind as NodeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid kind: ${kind}. Valid: ${NODE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n const filters = filtersJson\r\n ? (JSON.parse(filtersJson) as Record<string, unknown>)\r\n : undefined;\r\n const nodes = nodeRepo.findByKind(kind as NodeKind, filters);\r\n const result = nodes.map((n) => ({ ...n, props: JSON.parse(n.propsJson) }));\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n case 'get_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter required for get_node' }],\r\n isError: true,\r\n };\r\n }\r\n const node = nodeRepo.findById(id);\r\n if (!node) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n // Get adjacent edges and nodes\r\n const outEdges = edgeRepo.findBySource(node.id);\r\n const inEdges = edgeRepo.findByTarget(node.id);\r\n const adjacentNodeIds = new Set([\r\n ...outEdges.map((e) => e.targetId),\r\n ...inEdges.map((e) => e.sourceId),\r\n ]);\r\n const adjacentNodes = [...adjacentNodeIds]\r\n .map((nid) => nodeRepo.findById(nid))\r\n .filter(Boolean);\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n outEdges,\r\n inEdges,\r\n adjacentNodes: adjacentNodes.map((n) => ({\r\n ...n!,\r\n props: JSON.parse(n!.propsJson),\r\n })),\r\n };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n case 'traverse': {\r\n if (!startId) {\r\n return {\r\n content: [{ type: 'text', text: 'startId parameter required for traverse' }],\r\n isError: true,\r\n };\r\n }\r\n const validEdgeKinds = edgeKinds?.filter((ek) => EDGE_KINDS.includes(ek as EdgeKind)) as\r\n | EdgeKind[]\r\n | undefined;\r\n const results = graphQueryRepo.traverse(startId, depth, validEdgeKinds);\r\n const enriched = results.map((r) => ({\r\n ...r,\r\n node: { ...r.node, props: JSON.parse(r.node.propsJson) },\r\n }));\r\n return { content: [{ type: 'text', text: JSON.stringify(enriched, null, 2) }] };\r\n }\r\n case 'summary': {\r\n // Count nodes by kind and edges by kind\r\n const nodeCounts: Record<string, number> = {};\r\n for (const k of NODE_KINDS) {\r\n nodeCounts[k] = nodeRepo.findByKind(k).length;\r\n }\r\n const edgeCounts: Record<string, number> = {};\r\n for (const ek of EDGE_KINDS) {\r\n edgeCounts[ek] = edgeRepo.findByKind(ek).length;\r\n }\r\n // Also count artifacts\r\n const artifactCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM artifacts').get() as { cnt: number }\r\n ).cnt;\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: JSON.stringify(\r\n { nodes: nodeCounts, edges: edgeCounts, artifacts: artifactCount },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n case 'attack_paths': {\r\n if (!pattern) {\r\n return {\r\n content: [{ type: 'text', text: 'pattern parameter required for attack_paths' }],\r\n isError: true,\r\n };\r\n }\r\n try {\r\n const results = graphQueryRepo.runPreset(pattern);\r\n return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n return {\r\n content: [{ type: 'text', text: `attack_paths error: ${message}` }],\r\n isError: true,\r\n };\r\n }\r\n }\r\n }\r\n },\r\n );\r\n}\r\n","/**\r\n * sonobat — NodeRepository\r\n *\r\n * Graph-native スキーマの nodes テーブルに対する CRUD 操作を提供する。\r\n * snake_case (DB) ↔ camelCase (TypeScript) の変換を内部で行う。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\nimport type { GraphNode, NodeKind } from '../../types/graph.js';\r\nimport { validateProps, buildNaturalKey } from '../../types/graph.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// DB row 型\r\n// ---------------------------------------------------------------------------\r\n\r\n/** better-sqlite3 から返る nodes テーブルの行形状 */\r\ninterface NodeRow {\r\n id: string;\r\n kind: string;\r\n natural_key: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n updated_at: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Row → GraphNode 変換\r\n// ---------------------------------------------------------------------------\r\n\r\n/** snake_case DB row を camelCase GraphNode にマッピング */\r\nfunction rowToGraphNode(row: NodeRow): GraphNode {\r\n return {\r\n id: row.id,\r\n kind: row.kind as NodeKind,\r\n naturalKey: row.natural_key,\r\n propsJson: row.props_json,\r\n ...(row.evidence_artifact_id !== null ? { evidenceArtifactId: row.evidence_artifact_id } : {}),\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// NodeRepository\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * nodes テーブルの CRUD リポジトリ。\r\n *\r\n * - props は Zod バリデーション後に JSON 文字列としてストア\r\n * - natural_key は buildNaturalKey() で自動生成\r\n * - ID は crypto.randomUUID() で生成\r\n */\r\nexport class NodeRepository {\r\n private readonly db: Database.Database;\r\n\r\n private readonly insertStmt: Database.Statement;\r\n private readonly selectByIdStmt: Database.Statement;\r\n private readonly selectByKindStmt: Database.Statement;\r\n private readonly selectByNaturalKeyStmt: Database.Statement;\r\n private readonly updatePropsStmt: Database.Statement;\r\n private readonly deleteStmt: Database.Statement;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n\r\n this.insertStmt = this.db.prepare(\r\n `INSERT INTO nodes (id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\r\n );\r\n\r\n this.selectByIdStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE id = ?`,\r\n );\r\n\r\n this.selectByKindStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE kind = ?`,\r\n );\r\n\r\n this.selectByNaturalKeyStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE natural_key = ?`,\r\n );\r\n\r\n this.updatePropsStmt = this.db.prepare(\r\n `UPDATE nodes SET props_json = ?, updated_at = ? WHERE id = ?`,\r\n );\r\n\r\n this.deleteStmt = this.db.prepare(`DELETE FROM nodes WHERE id = ?`);\r\n }\r\n\r\n /**\r\n * ノードを新規作成して返す。\r\n *\r\n * @param kind ノード種別\r\n * @param props ノードの props(Zod バリデーション対象)\r\n * @param evidenceArtifactId 証拠 artifact の ID(任意)\r\n * @param parentId 親ノード ID(service, endpoint 等で必要)\r\n * @throws props バリデーションエラー、または natural_key 重複時\r\n */\r\n create(\r\n kind: NodeKind,\r\n props: Record<string, unknown>,\r\n evidenceArtifactId?: string,\r\n parentId?: string,\r\n ): GraphNode {\r\n // props バリデーション\r\n const validation = validateProps(kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${kind}\": ${validation.error}`);\r\n }\r\n\r\n const id = crypto.randomUUID();\r\n const naturalKey = buildNaturalKey(kind, validation.data, parentId);\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n\r\n this.insertStmt.run(\r\n id,\r\n kind,\r\n naturalKey,\r\n propsJson,\r\n evidenceArtifactId ?? null,\r\n timestamp,\r\n timestamp,\r\n );\r\n\r\n return {\r\n id,\r\n kind,\r\n naturalKey,\r\n propsJson,\r\n ...(evidenceArtifactId !== undefined ? { evidenceArtifactId } : {}),\r\n createdAt: timestamp,\r\n updatedAt: timestamp,\r\n };\r\n }\r\n\r\n /**\r\n * Upsert: natural_key が存在すれば更新、なければ新規作成。\r\n *\r\n * UUID ベースの natural key (observation, credential, vulnerability, svc_observation) は\r\n * 毎回異なるキーが生成されるため、常に新規作成となる。\r\n *\r\n * @returns { node, created } — created は新規作成時 true、既存更新時 false\r\n */\r\n upsert(\r\n kind: NodeKind,\r\n props: Record<string, unknown>,\r\n evidenceArtifactId?: string,\r\n parentId?: string,\r\n ): { node: GraphNode; created: boolean } {\r\n // props バリデーション\r\n const validation = validateProps(kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${kind}\": ${validation.error}`);\r\n }\r\n\r\n const naturalKey = buildNaturalKey(kind, validation.data, parentId);\r\n\r\n // 既存ノードを natural_key で検索\r\n const existing = this.findByNaturalKey(naturalKey);\r\n\r\n if (existing !== undefined) {\r\n // 既存ノードの props を更新\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n this.updatePropsStmt.run(propsJson, timestamp, existing.id);\r\n\r\n const updated = this.findById(existing.id)!;\r\n return { node: updated, created: false };\r\n }\r\n\r\n // 新規作成\r\n const node = this.create(kind, props, evidenceArtifactId, parentId);\r\n return { node, created: true };\r\n }\r\n\r\n /**\r\n * ID でノードを取得する。存在しなければ undefined。\r\n */\r\n findById(id: string): GraphNode | undefined {\r\n const row = this.selectByIdStmt.get(id) as NodeRow | undefined;\r\n if (row === undefined) {\r\n return undefined;\r\n }\r\n return rowToGraphNode(row);\r\n }\r\n\r\n /**\r\n * kind でノード一覧を取得する。\r\n *\r\n * filters を指定すると、props_json の中身に対して JSON_EXTRACT で絞り込む。\r\n * 例: findByKind('host', { authority: '192.168.1.1' })\r\n * → WHERE kind = 'host' AND JSON_EXTRACT(props_json, '$.authority') = '192.168.1.1'\r\n */\r\n findByKind(kind: NodeKind, filters?: Record<string, unknown>): GraphNode[] {\r\n if (filters === undefined || Object.keys(filters).length === 0) {\r\n const rows = this.selectByKindStmt.all(kind) as NodeRow[];\r\n return rows.map(rowToGraphNode);\r\n }\r\n\r\n // 動的フィルタ構築(プリペアドステートメントで安全に)\r\n const filterKeys = Object.keys(filters);\r\n const whereClauses = filterKeys.map((_key) => `JSON_EXTRACT(props_json, '$.' || ?) = ?`);\r\n const sql = `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes\r\n WHERE kind = ? AND ${whereClauses.join(' AND ')}`;\r\n\r\n const params: unknown[] = [kind];\r\n for (const key of filterKeys) {\r\n params.push(key, filters[key]);\r\n }\r\n\r\n const rows = this.db.prepare(sql).all(...params) as NodeRow[];\r\n return rows.map(rowToGraphNode);\r\n }\r\n\r\n /**\r\n * natural_key でノードを取得する。存在しなければ undefined。\r\n */\r\n findByNaturalKey(naturalKey: string): GraphNode | undefined {\r\n const row = this.selectByNaturalKeyStmt.get(naturalKey) as NodeRow | undefined;\r\n if (row === undefined) {\r\n return undefined;\r\n }\r\n return rowToGraphNode(row);\r\n }\r\n\r\n /**\r\n * ノードの props を更新する。updated_at も自動更新される。\r\n *\r\n * @returns 更新後の GraphNode。id が存在しなければ undefined。\r\n * @throws props バリデーションエラー時\r\n */\r\n updateProps(id: string, props: Record<string, unknown>): GraphNode | undefined {\r\n // 既存ノードを取得して kind を確認\r\n const existing = this.findById(id);\r\n if (existing === undefined) {\r\n return undefined;\r\n }\r\n\r\n // props バリデーション\r\n const validation = validateProps(existing.kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${existing.kind}\": ${validation.error}`);\r\n }\r\n\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n\r\n this.updatePropsStmt.run(propsJson, timestamp, id);\r\n\r\n return this.findById(id);\r\n }\r\n\r\n /**\r\n * ノードを削除する。\r\n *\r\n * @returns 削除成功時 true、id が存在しない場合 false。\r\n */\r\n delete(id: string): boolean {\r\n const result = this.deleteStmt.run(id);\r\n return result.changes > 0;\r\n }\r\n}\r\n","/**\r\n * sonobat — Graph type system\r\n *\r\n * グラフネイティブスキーマの型定義。\r\n * NodeKind/EdgeKind 列挙、Zod props スキーマ、\r\n * GraphNode/GraphEdge インターフェース、natural key builder。\r\n */\r\n\r\nimport { z } from 'zod';\r\nimport { randomUUID } from 'node:crypto';\r\n\r\n// ============================================================\r\n// NodeKind / EdgeKind 列挙\r\n// ============================================================\r\n\r\nexport const NODE_KINDS = [\r\n 'host',\r\n 'vhost',\r\n 'service',\r\n 'endpoint',\r\n 'input',\r\n 'observation',\r\n 'credential',\r\n 'vulnerability',\r\n 'cve',\r\n 'svc_observation',\r\n] as const;\r\nexport type NodeKind = (typeof NODE_KINDS)[number];\r\n\r\nexport const EDGE_KINDS = [\r\n 'HOST_SERVICE',\r\n 'HOST_VHOST',\r\n 'SERVICE_ENDPOINT',\r\n 'SERVICE_INPUT',\r\n 'SERVICE_CREDENTIAL',\r\n 'SERVICE_VULNERABILITY',\r\n 'SERVICE_OBSERVATION',\r\n 'ENDPOINT_INPUT',\r\n 'ENDPOINT_VULNERABILITY',\r\n 'ENDPOINT_CREDENTIAL',\r\n 'INPUT_OBSERVATION',\r\n 'VULNERABILITY_CVE',\r\n 'VHOST_ENDPOINT',\r\n] as const;\r\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\r\n\r\n// ============================================================\r\n// Zod Props スキーマ\r\n// ============================================================\r\n\r\nexport const HostPropsSchema = z.object({\r\n authorityKind: z.enum(['IP', 'DOMAIN']),\r\n authority: z.string().min(1),\r\n resolvedIpsJson: z.string().default('[]'),\r\n});\r\nexport type HostProps = z.infer<typeof HostPropsSchema>;\r\n\r\nexport const VhostPropsSchema = z.object({\r\n hostname: z.string().min(1),\r\n source: z.string().optional(),\r\n});\r\nexport type VhostProps = z.infer<typeof VhostPropsSchema>;\r\n\r\nexport const ServicePropsSchema = z.object({\r\n transport: z.string().min(1),\r\n port: z.number().int().nonnegative(),\r\n appProto: z.string().min(1),\r\n protoConfidence: z.string().min(1),\r\n banner: z.string().optional(),\r\n product: z.string().optional(),\r\n version: z.string().optional(),\r\n state: z.string().min(1),\r\n});\r\nexport type ServiceProps = z.infer<typeof ServicePropsSchema>;\r\n\r\nexport const EndpointPropsSchema = z.object({\r\n baseUri: z.string().min(1),\r\n method: z.string().min(1),\r\n path: z.string().min(1),\r\n statusCode: z.number().int().optional(),\r\n contentLength: z.number().int().optional(),\r\n words: z.number().int().optional(),\r\n lines: z.number().int().optional(),\r\n});\r\nexport type EndpointProps = z.infer<typeof EndpointPropsSchema>;\r\n\r\nexport const InputPropsSchema = z.object({\r\n location: z.string().min(1),\r\n name: z.string().min(1),\r\n typeHint: z.string().optional(),\r\n});\r\nexport type InputProps = z.infer<typeof InputPropsSchema>;\r\n\r\nexport const ObservationPropsSchema = z.object({\r\n rawValue: z.string(),\r\n normValue: z.string(),\r\n bodyPath: z.string().optional(),\r\n source: z.string().min(1),\r\n confidence: z.string().min(1),\r\n observedAt: z.string().min(1),\r\n});\r\nexport type ObservationProps = z.infer<typeof ObservationPropsSchema>;\r\n\r\nexport const CredentialPropsSchema = z.object({\r\n username: z.string(),\r\n secret: z.string(),\r\n secretType: z.string().min(1),\r\n source: z.string().min(1),\r\n confidence: z.string().min(1),\r\n});\r\nexport type CredentialProps = z.infer<typeof CredentialPropsSchema>;\r\n\r\nexport const VulnerabilityPropsSchema = z.object({\r\n vulnType: z.string().min(1),\r\n title: z.string().min(1),\r\n description: z.string().optional(),\r\n severity: z.string().min(1),\r\n confidence: z.string().min(1),\r\n status: z.string().min(1).default('unverified'),\r\n});\r\nexport type VulnerabilityProps = z.infer<typeof VulnerabilityPropsSchema>;\r\n\r\nexport const CvePropsSchema = z.object({\r\n cveId: z.string().min(1),\r\n description: z.string().optional(),\r\n cvssScore: z.number().optional(),\r\n cvssVector: z.string().optional(),\r\n referenceUrl: z.string().optional(),\r\n});\r\nexport type CveProps = z.infer<typeof CvePropsSchema>;\r\n\r\nexport const SvcObservationPropsSchema = z.object({\r\n key: z.string().min(1),\r\n value: z.string(),\r\n confidence: z.string().min(1),\r\n});\r\nexport type SvcObservationProps = z.infer<typeof SvcObservationPropsSchema>;\r\n\r\n/** NodeKind → Zod スキーマのマッピング */\r\nconst PROPS_SCHEMA_MAP: Record<NodeKind, z.ZodTypeAny> = {\r\n host: HostPropsSchema,\r\n vhost: VhostPropsSchema,\r\n service: ServicePropsSchema,\r\n endpoint: EndpointPropsSchema,\r\n input: InputPropsSchema,\r\n observation: ObservationPropsSchema,\r\n credential: CredentialPropsSchema,\r\n vulnerability: VulnerabilityPropsSchema,\r\n cve: CvePropsSchema,\r\n svc_observation: SvcObservationPropsSchema,\r\n};\r\n\r\n// ============================================================\r\n// GraphNode / GraphEdge インターフェース\r\n// ============================================================\r\n\r\nexport interface GraphNode {\r\n id: string;\r\n kind: NodeKind;\r\n naturalKey: string;\r\n propsJson: string;\r\n evidenceArtifactId?: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\nexport interface GraphEdge {\r\n id: string;\r\n kind: EdgeKind;\r\n sourceId: string;\r\n targetId: string;\r\n propsJson: string;\r\n evidenceArtifactId?: string;\r\n createdAt: string;\r\n}\r\n\r\n// ============================================================\r\n// validateProps\r\n// ============================================================\r\n\r\nexport type ValidateResult = { ok: true; data: unknown } | { ok: false; error: string };\r\n\r\n/**\r\n * 指定された NodeKind に対する props のバリデーション。\r\n */\r\nexport function validateProps(kind: NodeKind, props: unknown): ValidateResult {\r\n const schema = PROPS_SCHEMA_MAP[kind];\r\n const result = schema.safeParse(props);\r\n if (result.success) {\r\n return { ok: true, data: result.data };\r\n }\r\n return { ok: false, error: result.error.message };\r\n}\r\n\r\n// ============================================================\r\n// buildNaturalKey\r\n// ============================================================\r\n\r\n/**\r\n * ノード種別と props から自然キーを構築する。\r\n *\r\n * 決定的なキーが生成できるノード (host, vhost, service, endpoint, input, cve) は\r\n * 常に同じ入力に対して同じキーを返す。\r\n *\r\n * 一意性が保証できないノード (observation, credential, vulnerability, svc_observation)\r\n * は UUID ベースのキーを生成する。\r\n *\r\n * @param kind ノード種別\r\n * @param props ノードの props(部分的でも可)\r\n * @param parentId 親ノードの ID(vhost, service, endpoint, input, cve で必須)\r\n */\r\nexport function buildNaturalKey(kind: NodeKind, props: unknown, parentId?: string): string {\r\n const p = props as Record<string, unknown>;\r\n\r\n switch (kind) {\r\n case 'host':\r\n return `host:${p.authority}`;\r\n\r\n case 'vhost':\r\n return `vhost:${parentId}:${p.hostname}`;\r\n\r\n case 'service':\r\n return `svc:${parentId}:${p.transport}:${p.port}`;\r\n\r\n case 'endpoint':\r\n return `ep:${parentId}:${p.method}:${p.path}`;\r\n\r\n case 'input':\r\n return `in:${parentId}:${p.location}:${p.name}`;\r\n\r\n case 'cve':\r\n return `cve:${parentId}:${p.cveId}`;\r\n\r\n case 'observation':\r\n return `obs:${randomUUID()}`;\r\n\r\n case 'credential':\r\n return `cred:${randomUUID()}`;\r\n\r\n case 'vulnerability':\r\n return `vuln:${randomUUID()}`;\r\n\r\n case 'svc_observation':\r\n return `svcobs:${randomUUID()}`;\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\nimport type { EdgeKind, GraphEdge } from '../../types/graph.js';\r\n\r\n/** Row shape returned by better-sqlite3 for the edges table. */\r\ninterface EdgeRow {\r\n id: string;\r\n kind: string;\r\n source_id: string;\r\n target_id: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n}\r\n\r\n/** Maps a snake_case DB row to a camelCase GraphEdge entity. */\r\nfunction rowToEdge(row: EdgeRow): GraphEdge {\r\n return {\r\n id: row.id,\r\n kind: row.kind as EdgeKind,\r\n sourceId: row.source_id,\r\n targetId: row.target_id,\r\n propsJson: row.props_json,\r\n ...(row.evidence_artifact_id !== null ? { evidenceArtifactId: row.evidence_artifact_id } : {}),\r\n createdAt: row.created_at,\r\n };\r\n}\r\n\r\n/**\r\n * Repository for the `edges` table.\r\n *\r\n * Provides CRUD operations with camelCase <-> snake_case mapping\r\n * between the TypeScript entity layer and the SQLite storage layer.\r\n */\r\nexport class EdgeRepository {\r\n private readonly db: Database.Database;\r\n\r\n private readonly insertStmt: Database.Statement;\r\n private readonly selectByCompositeKeyStmt: Database.Statement;\r\n private readonly selectBySourceStmt: Database.Statement;\r\n private readonly selectBySourceAndKindStmt: Database.Statement;\r\n private readonly selectByTargetStmt: Database.Statement;\r\n private readonly selectByTargetAndKindStmt: Database.Statement;\r\n private readonly selectByKindStmt: Database.Statement;\r\n private readonly deleteStmt: Database.Statement;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n\r\n this.insertStmt = this.db.prepare(\r\n 'INSERT INTO edges (id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)',\r\n );\r\n\r\n this.selectByCompositeKeyStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE kind = ? AND source_id = ? AND target_id = ?',\r\n );\r\n\r\n this.selectBySourceStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE source_id = ?',\r\n );\r\n\r\n this.selectBySourceAndKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE source_id = ? AND kind = ?',\r\n );\r\n\r\n this.selectByTargetStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE target_id = ?',\r\n );\r\n\r\n this.selectByTargetAndKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE target_id = ? AND kind = ?',\r\n );\r\n\r\n this.selectByKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE kind = ?',\r\n );\r\n\r\n this.deleteStmt = this.db.prepare('DELETE FROM edges WHERE id = ?');\r\n }\r\n\r\n /**\r\n * Create a new edge linking two nodes.\r\n *\r\n * Throws if the (kind, source_id, target_id) combination already exists.\r\n */\r\n create(\r\n kind: EdgeKind,\r\n sourceId: string,\r\n targetId: string,\r\n evidenceArtifactId?: string,\r\n propsJson?: string,\r\n ): GraphEdge {\r\n const id = crypto.randomUUID();\r\n const createdAt = new Date().toISOString();\r\n const props = propsJson ?? '{}';\r\n\r\n this.insertStmt.run(id, kind, sourceId, targetId, props, evidenceArtifactId ?? null, createdAt);\r\n\r\n return {\r\n id,\r\n kind,\r\n sourceId,\r\n targetId,\r\n propsJson: props,\r\n ...(evidenceArtifactId !== undefined ? { evidenceArtifactId } : {}),\r\n createdAt,\r\n };\r\n }\r\n\r\n /**\r\n * Upsert an edge by (kind, source_id, target_id).\r\n *\r\n * If the combination already exists, returns the existing edge with `created: false`.\r\n * Otherwise creates a new edge and returns it with `created: true`.\r\n */\r\n upsert(\r\n kind: EdgeKind,\r\n sourceId: string,\r\n targetId: string,\r\n evidenceArtifactId?: string,\r\n propsJson?: string,\r\n ): { edge: GraphEdge; created: boolean } {\r\n const existing = this.selectByCompositeKeyStmt.get(kind, sourceId, targetId) as\r\n | EdgeRow\r\n | undefined;\r\n\r\n if (existing !== undefined) {\r\n return { edge: rowToEdge(existing), created: false };\r\n }\r\n\r\n const edge = this.create(kind, sourceId, targetId, evidenceArtifactId, propsJson);\r\n return { edge, created: true };\r\n }\r\n\r\n /**\r\n * Find all edges originating from a source node.\r\n *\r\n * Optionally filter by edge kind.\r\n */\r\n findBySource(sourceId: string, edgeKind?: EdgeKind): GraphEdge[] {\r\n if (edgeKind !== undefined) {\r\n const rows = this.selectBySourceAndKindStmt.all(sourceId, edgeKind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n const rows = this.selectBySourceStmt.all(sourceId) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Find all edges targeting a node.\r\n *\r\n * Optionally filter by edge kind.\r\n */\r\n findByTarget(targetId: string, edgeKind?: EdgeKind): GraphEdge[] {\r\n if (edgeKind !== undefined) {\r\n const rows = this.selectByTargetAndKindStmt.all(targetId, edgeKind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n const rows = this.selectByTargetStmt.all(targetId) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Find all edges of a specific kind.\r\n */\r\n findByKind(kind: EdgeKind): GraphEdge[] {\r\n const rows = this.selectByKindStmt.all(kind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Delete an edge by ID.\r\n *\r\n * Returns true if the edge was found and deleted, false otherwise.\r\n */\r\n delete(id: string): boolean {\r\n const result = this.deleteStmt.run(id);\r\n return result.changes > 0;\r\n }\r\n}\r\n","/**\r\n * sonobat — GraphQueryRepository\r\n *\r\n * グラフ走査・パス探索・プリセットクエリを提供するリポジトリ。\r\n * WITH RECURSIVE CTE を活用した SQLite ネイティブなグラフ操作。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { GraphNode, GraphEdge, NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Row → Entity マッピング\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface NodeRow {\r\n id: string;\r\n kind: string;\r\n natural_key: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n updated_at: string;\r\n}\r\n\r\ninterface EdgeRow {\r\n id: string;\r\n kind: string;\r\n source_id: string;\r\n target_id: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n}\r\n\r\nfunction rowToNode(row: NodeRow): GraphNode {\r\n return {\r\n id: row.id,\r\n kind: row.kind as NodeKind,\r\n naturalKey: row.natural_key,\r\n propsJson: row.props_json,\r\n evidenceArtifactId: row.evidence_artifact_id ?? undefined,\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nfunction rowToEdge(row: EdgeRow): GraphEdge {\r\n return {\r\n id: row.id,\r\n kind: row.kind as EdgeKind,\r\n sourceId: row.source_id,\r\n targetId: row.target_id,\r\n propsJson: row.props_json,\r\n evidenceArtifactId: row.evidence_artifact_id ?? undefined,\r\n createdAt: row.created_at,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 公開型\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TraversalResult {\r\n node: GraphNode;\r\n depth: number;\r\n path: string[]; // node IDs from start to this node\r\n}\r\n\r\nexport interface PathResult {\r\n nodes: GraphNode[];\r\n edges: GraphEdge[];\r\n length: number;\r\n}\r\n\r\nexport type PresetResult = Record<string, unknown>[];\r\n\r\n// ---------------------------------------------------------------------------\r\n// GraphQueryRepository\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * グラフ走査・パス探索・プリセットクエリを提供するリポジトリ。\r\n *\r\n * すべてのクエリはプリペアドステートメントを使用し、SQL インジェクションを防止。\r\n */\r\nexport class GraphQueryRepository {\r\n private readonly db: Database.Database;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n }\r\n\r\n /**\r\n * 開始ノードから有向エッジを辿り、到達可能なノードを幅優先で返す。\r\n *\r\n * @param startId 開始ノード ID\r\n * @param maxDepth 最大探索深度(デフォルト: 10)\r\n * @param edgeKinds 辿るエッジ種別のフィルタ(省略時は全種別)\r\n * @returns 到達可能なノードのリスト(開始ノード自身は含まない)\r\n */\r\n traverse(startId: string, maxDepth?: number, edgeKinds?: EdgeKind[]): TraversalResult[] {\r\n const depth = maxDepth ?? 10;\r\n\r\n // 開始ノードの存在確認\r\n const startNode = this.db\r\n .prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?')\r\n .get(startId);\r\n if (!startNode) {\r\n return [];\r\n }\r\n\r\n // edgeKinds フィルタ用の条件構築\r\n let edgeFilter = '';\r\n const params: unknown[] = [startId, startId, depth];\r\n\r\n if (edgeKinds && edgeKinds.length > 0) {\r\n const placeholders = edgeKinds.map(() => '?').join(', ');\r\n edgeFilter = `AND e.kind IN (${placeholders})`;\r\n params.push(...edgeKinds);\r\n }\r\n\r\n const sql = `\r\n WITH RECURSIVE graph_walk(node_id, depth, path) AS (\r\n SELECT ?, 0, ?\r\n UNION ALL\r\n SELECT e.target_id, gw.depth + 1, gw.path || ',' || e.target_id\r\n FROM graph_walk gw\r\n JOIN edges e ON e.source_id = gw.node_id\r\n WHERE gw.depth < ?\r\n ${edgeFilter}\r\n AND instr(',' || gw.path || ',', ',' || e.target_id || ',') = 0\r\n )\r\n SELECT DISTINCT\r\n n.id, n.kind, n.natural_key, n.props_json,\r\n n.evidence_artifact_id, n.created_at, n.updated_at,\r\n gw.depth AS walk_depth,\r\n gw.path AS walk_path\r\n FROM graph_walk gw\r\n JOIN nodes n ON n.id = gw.node_id\r\n WHERE gw.depth > 0\r\n ORDER BY gw.depth ASC\r\n `;\r\n\r\n const stmt = this.db.prepare(sql);\r\n const rows = stmt.all(...params) as Array<NodeRow & { walk_depth: number; walk_path: string }>;\r\n\r\n // 同一ノードが複数パスで到達可能な場合、最短パスのみ保持\r\n const seen = new Map<string, TraversalResult>();\r\n\r\n for (const row of rows) {\r\n if (!seen.has(row.id) || seen.get(row.id)!.depth > row.walk_depth) {\r\n seen.set(row.id, {\r\n node: rowToNode(row),\r\n depth: row.walk_depth,\r\n path: row.walk_path.split(','),\r\n });\r\n }\r\n }\r\n\r\n return Array.from(seen.values()).sort((a, b) => a.depth - b.depth);\r\n }\r\n\r\n /**\r\n * 開始ノードから有向エッジを辿り、到達可能な全ノードを返す。\r\n *\r\n * @param nodeId 開始ノード ID\r\n * @param targetKind フィルタ対象のノード種別(省略時は全種別)\r\n * @returns 到達可能なノードのリスト\r\n */\r\n reachableFrom(nodeId: string, targetKind?: NodeKind): GraphNode[] {\r\n // BFS を TypeScript 側で実装する\r\n const visited = new Set<string>();\r\n const queue: string[] = [nodeId];\r\n visited.add(nodeId);\r\n\r\n const results: GraphNode[] = [];\r\n\r\n const edgeStmt = this.db.prepare<[string], EdgeRow>('SELECT * FROM edges WHERE source_id = ?');\r\n const nodeStmt = this.db.prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?');\r\n\r\n while (queue.length > 0) {\r\n const currentId = queue.shift()!;\r\n const edges = edgeStmt.all(currentId);\r\n\r\n for (const edge of edges) {\r\n if (!visited.has(edge.target_id)) {\r\n visited.add(edge.target_id);\r\n queue.push(edge.target_id);\r\n\r\n const targetNode = nodeStmt.get(edge.target_id);\r\n if (targetNode) {\r\n const graphNode = rowToNode(targetNode);\r\n if (targetKind === undefined || graphNode.kind === targetKind) {\r\n results.push(graphNode);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * 2 ノード間の最短パスを BFS で探索する。\r\n *\r\n * @param sourceId 起点ノード ID\r\n * @param targetId 終点ノード ID\r\n * @returns パス情報。パスが存在しない場合は undefined\r\n */\r\n shortestPath(sourceId: string, targetId: string): PathResult | undefined {\r\n const nodeStmt = this.db.prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?');\r\n\r\n // 同一ノードの場合\r\n if (sourceId === targetId) {\r\n const node = nodeStmt.get(sourceId);\r\n if (!node) {\r\n return undefined;\r\n }\r\n return {\r\n nodes: [rowToNode(node)],\r\n edges: [],\r\n length: 0,\r\n };\r\n }\r\n\r\n // BFS で最短パスを探索\r\n const edgeStmt = this.db.prepare<[string], EdgeRow>('SELECT * FROM edges WHERE source_id = ?');\r\n\r\n // parent[nodeId] = { parentId, edge }\r\n const parent = new Map<string, { parentId: string; edge: EdgeRow }>();\r\n const visited = new Set<string>();\r\n const queue: string[] = [sourceId];\r\n visited.add(sourceId);\r\n\r\n let found = false;\r\n\r\n while (queue.length > 0 && !found) {\r\n const currentId = queue.shift()!;\r\n const edges = edgeStmt.all(currentId);\r\n\r\n for (const edge of edges) {\r\n if (!visited.has(edge.target_id)) {\r\n visited.add(edge.target_id);\r\n parent.set(edge.target_id, { parentId: currentId, edge });\r\n if (edge.target_id === targetId) {\r\n found = true;\r\n break;\r\n }\r\n queue.push(edge.target_id);\r\n }\r\n }\r\n }\r\n\r\n if (!found) {\r\n return undefined;\r\n }\r\n\r\n // パスを逆順に再構築\r\n const pathNodeIds: string[] = [];\r\n const pathEdges: GraphEdge[] = [];\r\n\r\n let currentId = targetId;\r\n while (currentId !== sourceId) {\r\n pathNodeIds.unshift(currentId);\r\n const info = parent.get(currentId)!;\r\n pathEdges.unshift(rowToEdge(info.edge));\r\n currentId = info.parentId;\r\n }\r\n pathNodeIds.unshift(sourceId);\r\n\r\n // ノード情報を取得\r\n const nodes: GraphNode[] = [];\r\n for (const nid of pathNodeIds) {\r\n const row = nodeStmt.get(nid);\r\n if (row) {\r\n nodes.push(rowToNode(row));\r\n }\r\n }\r\n\r\n return {\r\n nodes,\r\n edges: pathEdges,\r\n length: pathEdges.length,\r\n };\r\n }\r\n\r\n /**\r\n * プリセットクエリを実行する。\r\n *\r\n * @param pattern プリセット名\r\n * @param params パラメータ(プリセットによって異なる)\r\n * @returns クエリ結果の配列\r\n */\r\n runPreset(pattern: string, params?: Record<string, unknown>): PresetResult {\r\n switch (pattern) {\r\n case 'attack_surface':\r\n return this.presetAttackSurface();\r\n case 'critical_vulns':\r\n return this.presetCriticalVulns();\r\n case 'credential_exposure':\r\n return this.presetCredentialExposure();\r\n case 'unscanned_services':\r\n return this.presetUnscannedServices();\r\n case 'vuln_by_host':\r\n return this.presetVulnByHost();\r\n case 'reachable_services':\r\n return this.presetReachableServices(params);\r\n default:\r\n throw new Error(`Unknown preset pattern: ${pattern}`);\r\n }\r\n }\r\n\r\n // =========================================================================\r\n // プリセットクエリ実装\r\n // =========================================================================\r\n\r\n /**\r\n * attack_surface: host → service → endpoint → input の完全パスを返す。\r\n * input がないエンドポイントも含む(inputId は null)。\r\n */\r\n private presetAttackSurface(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n ep.id AS endpoint_id,\r\n ep.props_json AS endpoint_props,\r\n inp.id AS input_id,\r\n inp.props_json AS input_props\r\n FROM nodes h\r\n JOIN edges e_hs ON e_hs.source_id = h.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n JOIN edges e_se ON e_se.source_id = s.id AND e_se.kind = 'SERVICE_ENDPOINT'\r\n JOIN nodes ep ON ep.id = e_se.target_id AND ep.kind = 'endpoint'\r\n LEFT JOIN edges e_ei ON e_ei.source_id = ep.id AND e_ei.kind = 'ENDPOINT_INPUT'\r\n LEFT JOIN nodes inp ON inp.id = e_ei.target_id AND inp.kind = 'input'\r\n WHERE h.kind = 'host'\r\n ORDER BY h.id, s.id, ep.id, inp.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n endpointId: row.endpoint_id,\r\n endpointProps: row.endpoint_props,\r\n inputId: row.input_id ?? null,\r\n inputProps: row.input_props ?? null,\r\n }));\r\n }\r\n\r\n /**\r\n * critical_vulns: severity が critical/high の脆弱性をホスト情報付きで返す。\r\n */\r\n private presetCriticalVulns(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n v.id AS vuln_id,\r\n v.props_json AS vuln_props,\r\n json_extract(v.props_json, '$.severity') AS severity,\r\n json_extract(v.props_json, '$.title') AS title\r\n FROM nodes v\r\n JOIN edges e_sv ON e_sv.target_id = v.id AND e_sv.kind = 'SERVICE_VULNERABILITY'\r\n JOIN nodes s ON s.id = e_sv.source_id AND s.kind = 'service'\r\n JOIN edges e_hs ON e_hs.target_id = s.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes h ON h.id = e_hs.source_id AND h.kind = 'host'\r\n WHERE v.kind = 'vulnerability'\r\n AND json_extract(v.props_json, '$.severity') IN ('critical', 'high')\r\n ORDER BY\r\n CASE json_extract(v.props_json, '$.severity')\r\n WHEN 'critical' THEN 0\r\n WHEN 'high' THEN 1\r\n END,\r\n h.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n vulnId: row.vuln_id,\r\n vulnProps: row.vuln_props,\r\n severity: row.severity,\r\n title: row.title,\r\n }));\r\n }\r\n\r\n /**\r\n * credential_exposure: service → credential の全マッピングを返す。\r\n */\r\n private presetCredentialExposure(): PresetResult {\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n c.id AS credential_id,\r\n c.props_json AS credential_props\r\n FROM nodes c\r\n JOIN edges e_sc ON e_sc.target_id = c.id AND e_sc.kind = 'SERVICE_CREDENTIAL'\r\n JOIN nodes s ON s.id = e_sc.source_id AND s.kind = 'service'\r\n WHERE c.kind = 'credential'\r\n ORDER BY s.id, c.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n credentialId: row.credential_id,\r\n credentialProps: row.credential_props,\r\n }));\r\n }\r\n\r\n /**\r\n * unscanned_services: endpoint が 0 件のサービスを返す。\r\n */\r\n private presetUnscannedServices(): PresetResult {\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n h.id AS host_id,\r\n h.props_json AS host_props\r\n FROM nodes s\r\n JOIN edges e_hs ON e_hs.target_id = s.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes h ON h.id = e_hs.source_id AND h.kind = 'host'\r\n LEFT JOIN edges e_se ON e_se.source_id = s.id AND e_se.kind = 'SERVICE_ENDPOINT'\r\n WHERE s.kind = 'service'\r\n AND e_se.id IS NULL\r\n ORDER BY h.id, s.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n }));\r\n }\r\n\r\n /**\r\n * vuln_by_host: ホスト別脆弱性カウントを返す。\r\n */\r\n private presetVulnByHost(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n COUNT(v.id) AS vuln_count\r\n FROM nodes h\r\n JOIN edges e_hs ON e_hs.source_id = h.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n JOIN edges e_sv ON e_sv.source_id = s.id AND e_sv.kind = 'SERVICE_VULNERABILITY'\r\n JOIN nodes v ON v.id = e_sv.target_id AND v.kind = 'vulnerability'\r\n WHERE h.kind = 'host'\r\n GROUP BY h.id\r\n ORDER BY vuln_count DESC\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n vulnCount: row.vuln_count,\r\n }));\r\n }\r\n\r\n /**\r\n * reachable_services: 指定 host から到達可能な全サービスを返す。\r\n */\r\n private presetReachableServices(params?: Record<string, unknown>): PresetResult {\r\n const hostId = params?.hostId as string | undefined;\r\n if (!hostId) {\r\n throw new Error('reachable_services preset requires hostId parameter');\r\n }\r\n\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n e_hs.kind AS edge_kind\r\n FROM edges e_hs\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n WHERE e_hs.source_id = ?\r\n AND e_hs.kind = 'HOST_SERVICE'\r\n ORDER BY s.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all(hostId) as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n edgeKind: row.edge_kind,\r\n }));\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Mutate Tool (unified)\r\n *\r\n * Single 'mutate' tool with an 'action' parameter that replaces\r\n * all previous mutation tools (add_host, add_credential, etc.).\r\n *\r\n * Actions: add_node, add_edge, update_node, delete_node\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { z } from 'zod';\r\nimport { NodeRepository } from '../../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../../db/repository/edge-repository.js';\r\nimport { NODE_KINDS, EDGE_KINDS, validateProps } from '../../types/graph.js';\r\nimport type { NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\n/**\r\n * Get or create a singleton \"manual\" artifact for manual data entry.\r\n * Reused across all manual mutations to avoid artifact proliferation.\r\n * Uses direct SQL since ArtifactRepository is being removed.\r\n */\r\nfunction getOrCreateManualArtifact(db: Database.Database): string {\r\n const row = db.prepare(\"SELECT id FROM artifacts WHERE tool = 'manual' LIMIT 1\").get() as\r\n | { id: string }\r\n | undefined;\r\n if (row) return row.id;\r\n\r\n const id = crypto.randomUUID();\r\n const now = new Date().toISOString();\r\n db.prepare(\r\n \"INSERT INTO artifacts (id, tool, kind, path, captured_at) VALUES (?, 'manual', 'manual_entry', 'manual', ?)\",\r\n ).run(id, now);\r\n return id;\r\n}\r\n\r\nexport function registerMutateTool(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n server.tool(\r\n 'mutate',\r\n 'Mutate the AttackDataGraph. Actions: add_node, add_edge, update_node, delete_node',\r\n {\r\n action: z.enum(['add_node', 'add_edge', 'update_node', 'delete_node']),\r\n // Parameters for add_node\r\n kind: z.string().optional().describe('Node kind (host, service, endpoint, etc.)'),\r\n propsJson: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Node properties as JSON string (e.g. {\"authorityKind\":\"IP\",\"authority\":\"10.0.0.1\",\"resolvedIpsJson\":\"[]\"})',\r\n ),\r\n parentId: z\r\n .string()\r\n .optional()\r\n .describe('Parent node ID (required for service, endpoint, input, vhost, cve)'),\r\n // Parameters for add_edge\r\n edgeKind: z.string().optional().describe('Edge kind (HOST_SERVICE, SERVICE_ENDPOINT, etc.)'),\r\n sourceId: z.string().optional().describe('Source node ID for edge'),\r\n targetId: z.string().optional().describe('Target node ID for edge'),\r\n // Parameters for update_node / delete_node\r\n id: z.string().optional().describe('Node ID for update or delete'),\r\n // Common optional\r\n evidenceArtifactId: z\r\n .string()\r\n .optional()\r\n .describe('Evidence artifact ID. If omitted, a \"manual\" artifact is auto-created/reused.'),\r\n },\r\n async ({\r\n action,\r\n kind,\r\n propsJson: propsJsonStr,\r\n parentId,\r\n edgeKind,\r\n sourceId,\r\n targetId,\r\n id,\r\n evidenceArtifactId,\r\n }) => {\r\n // Parse propsJson string into an object when provided\r\n let props: Record<string, unknown> | undefined;\r\n if (propsJsonStr) {\r\n try {\r\n props = JSON.parse(propsJsonStr) as Record<string, unknown>;\r\n } catch {\r\n return {\r\n content: [{ type: 'text', text: `Invalid JSON in propsJson: ${propsJsonStr}` }],\r\n isError: true,\r\n };\r\n }\r\n }\r\n\r\n switch (action) {\r\n // ----------------------------------------------------------------\r\n // add_node\r\n // ----------------------------------------------------------------\r\n case 'add_node': {\r\n if (!kind) {\r\n return {\r\n content: [{ type: 'text', text: 'kind parameter is required for add_node' }],\r\n isError: true,\r\n };\r\n }\r\n if (!NODE_KINDS.includes(kind as NodeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid kind: ${kind}. Valid kinds: ${NODE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n if (!props) {\r\n return {\r\n content: [{ type: 'text', text: 'propsJson parameter is required for add_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Validate props against the schema for this kind\r\n const validation = validateProps(kind as NodeKind, props);\r\n if (!validation.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Props validation failed for kind=\"${kind}\": ${validation.error}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n const artifactId = evidenceArtifactId ?? getOrCreateManualArtifact(db);\r\n const { node, created } = nodeRepo.upsert(kind as NodeKind, props, artifactId, parentId);\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n created,\r\n };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // add_edge\r\n // ----------------------------------------------------------------\r\n case 'add_edge': {\r\n if (!edgeKind) {\r\n return {\r\n content: [{ type: 'text', text: 'edgeKind parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n if (!EDGE_KINDS.includes(edgeKind as EdgeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid edgeKind: ${edgeKind}. Valid kinds: ${EDGE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n if (!sourceId) {\r\n return {\r\n content: [{ type: 'text', text: 'sourceId parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n if (!targetId) {\r\n return {\r\n content: [{ type: 'text', text: 'targetId parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n const artifactId = evidenceArtifactId ?? getOrCreateManualArtifact(db);\r\n const { edge, created } = edgeRepo.upsert(\r\n edgeKind as EdgeKind,\r\n sourceId,\r\n targetId,\r\n artifactId,\r\n );\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify({ ...edge, created }, null, 2) }],\r\n };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // update_node\r\n // ----------------------------------------------------------------\r\n case 'update_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter is required for update_node' }],\r\n isError: true,\r\n };\r\n }\r\n if (!props) {\r\n return {\r\n content: [{ type: 'text', text: 'propsJson parameter is required for update_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Find existing node to get its kind and current props\r\n const existing = nodeRepo.findById(id);\r\n if (!existing) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Merge existing props with the partial update\r\n const existingProps = JSON.parse(existing.propsJson) as Record<string, unknown>;\r\n const mergedProps = { ...existingProps, ...props };\r\n\r\n // Validate merged props\r\n const validation = validateProps(existing.kind, mergedProps);\r\n if (!validation.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Props validation failed for kind=\"${existing.kind}\": ${validation.error}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n const updated = nodeRepo.updateProps(id, mergedProps);\r\n if (!updated) {\r\n return {\r\n content: [{ type: 'text', text: `Failed to update node: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n const result = { ...updated, props: JSON.parse(updated.propsJson) };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // delete_node\r\n // ----------------------------------------------------------------\r\n case 'delete_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter is required for delete_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n const deleted = nodeRepo.delete(id);\r\n if (!deleted) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n return {\r\n content: [{ type: 'text', text: `Node ${id} deleted successfully.` }],\r\n };\r\n }\r\n }\r\n },\r\n );\r\n}\r\n","/**\n * sonobat — MCP Ingest Tool\n *\n * Tool for ingesting tool output files into the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { ingest } from '../../engine/ingest.js';\n\nexport function registerIngestTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'ingest_file',\n 'Ingest a tool output file (nmap XML, ffuf JSON, nuclei JSONL) into the AttackDataGraph',\n {\n path: z.string().describe('Absolute path to the tool output file'),\n tool: z.enum(['nmap', 'ffuf', 'nuclei']).describe('Tool that produced the output'),\n },\n async ({ path, tool }) => {\n try {\n const result = ingest(db, { path, tool });\n const nr = result.normalizeResult;\n const summary = [\n `Ingested ${tool} output from ${path}`,\n `Artifact ID: ${result.artifactId}`,\n `Created: ${nr.hostsCreated} hosts, ${nr.servicesCreated} services, ${nr.httpEndpointsCreated} endpoints, ${nr.inputsCreated} inputs, ${nr.observationsCreated} observations, ${nr.vulnerabilitiesCreated} vulnerabilities, ${nr.cvesCreated} CVEs`,\n ].join('\\n');\n return { content: [{ type: 'text', text: summary }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: 'text', text: `Ingest failed: ${message}` }], isError: true };\n }\n },\n );\n}\n","/**\r\n * sonobat — Ingest Engine\r\n *\r\n * ツール出力ファイルを読み込み、パース・正規化してDBに格納する。\r\n * ingestContent() はコアロジック(ファイルシステム非依存・テスト可能)。\r\n * ingest() はファイル読み込みの薄いラッパー。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport crypto from 'node:crypto';\r\nimport path from 'node:path';\r\nimport type { IngestInput, IngestResult } from '../types/engine.js';\r\nimport { parseNmapXml } from '../parser/nmap-parser.js';\r\nimport { parseFfufJson } from '../parser/ffuf-parser.js';\r\nimport { parseNucleiJsonl } from '../parser/nuclei-parser.js';\r\nimport { normalize } from './normalizer.js';\r\n\r\n/**\r\n * ツール出力の文字列を直接受け取り、パース・正規化してDBに格納する。\r\n * ファイルシステムに依存しないため、テストから直接呼び出せる。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param tool ツール種別('nmap' | 'ffuf' | 'nuclei')\r\n * @param content ツール出力の文字列\r\n * @param filePath Artifact に記録するファイルパス\r\n * @returns IngestResult(artifactId + normalizeResult)\r\n */\r\nexport function ingestContent(\r\n db: Database.Database,\r\n tool: 'nmap' | 'ffuf' | 'nuclei',\r\n content: string,\r\n filePath: string,\r\n): IngestResult {\r\n // 1. SHA-256 ハッシュを計算\r\n const sha256 = crypto.createHash('sha256').update(content).digest('hex');\r\n\r\n // 2. Artifact を作成 (direct SQL — ArtifactRepository は廃止)\r\n const artifactId = crypto.randomUUID();\r\n const capturedAt = new Date().toISOString();\r\n db.prepare(\r\n 'INSERT INTO artifacts (id, tool, kind, path, sha256, captured_at) VALUES (?, ?, ?, ?, ?, ?)',\r\n ).run(artifactId, tool, 'tool_output', filePath, sha256, capturedAt);\r\n\r\n // 3. ツール種別に応じてパース\r\n let parseResult;\r\n switch (tool) {\r\n case 'nmap':\r\n parseResult = parseNmapXml(content);\r\n break;\r\n case 'ffuf':\r\n parseResult = parseFfufJson(content);\r\n break;\r\n case 'nuclei':\r\n parseResult = parseNucleiJsonl(content);\r\n break;\r\n default: {\r\n // never 型による網羅性チェック — 未知の tool が渡された場合はコンパイルエラー\r\n const _exhaustive: never = tool;\r\n throw new Error(`Unknown tool: ${String(_exhaustive)}`);\r\n }\r\n }\r\n\r\n // 4. 正規化してDBに書き込む\r\n const normalizeResult = normalize(db, artifactId, parseResult);\r\n\r\n // 5. 結果を返す\r\n return {\r\n artifactId,\r\n normalizeResult,\r\n };\r\n}\r\n\r\n/**\r\n * ファイルパスからツール出力を読み込み、インジェストする。\r\n * ingestContent() の薄いラッパー。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param input IngestInput(path + tool)\r\n * @returns IngestResult\r\n */\r\nexport function ingest(db: Database.Database, input: IngestInput): IngestResult {\r\n const resolved = path.resolve(input.path);\r\n const content = fs.readFileSync(resolved, 'utf-8');\r\n return ingestContent(db, input.tool, content, resolved);\r\n}\r\n","/**\n * sonobat — Nmap XML パーサー\n *\n * nmap の XML 出力を解析し、ParseResult 中間表現を返す。\n * fast-xml-parser を使用して XML をパースする。\n */\n\nimport { XMLParser } from 'fast-xml-parser';\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedServiceObservation,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// XML パース後の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NmapAddress {\n '@_addr': string;\n '@_addrtype': string;\n}\n\ninterface NmapHostname {\n '@_name': string;\n '@_type': string;\n}\n\ninterface NmapPortState {\n '@_state': string;\n '@_reason'?: string;\n}\n\ninterface NmapServiceAttr {\n '@_name'?: string;\n '@_product'?: string;\n '@_version'?: string;\n '@_extrainfo'?: string;\n '@_tunnel'?: string;\n '@_conf'?: string;\n}\n\ninterface NmapPort {\n '@_protocol': string;\n '@_portid': string;\n state: NmapPortState;\n service?: NmapServiceAttr;\n}\n\ninterface NmapOsMatch {\n '@_name': string;\n '@_accuracy': string;\n}\n\ninterface NmapHost {\n address: NmapAddress | NmapAddress[];\n hostnames?: {\n hostname?: NmapHostname | NmapHostname[];\n };\n ports?: {\n port?: NmapPort | NmapPort[];\n };\n os?: {\n osmatch?: NmapOsMatch | NmapOsMatch[];\n };\n}\n\ninterface NmapRun {\n nmaprun: {\n host?: NmapHost | NmapHost[];\n };\n}\n\n// ============================================================\n// ユーティリティ\n// ============================================================\n\n/** 値を配列に正規化する。undefined/null は空配列を返す。 */\nfunction ensureArray<T>(value: T | T[] | undefined | null): T[] {\n if (value === undefined || value === null) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\n/** nmap の conf 属性から protoConfidence を決定する */\nfunction toProtoConfidence(conf: string | undefined): string {\n const n = conf !== undefined ? Number(conf) : 0;\n if (n === 10) return 'high';\n if (n >= 7) return 'medium';\n return 'low';\n}\n\n/** OS accuracy から confidence を決定する */\nfunction toOsConfidence(accuracy: string): string {\n const n = Number(accuracy);\n if (n >= 90) return 'high';\n if (n >= 50) return 'medium';\n return 'low';\n}\n\n/** サービス名が HTTPS を示すかどうかを判定する */\nfunction isHttps(service: NmapServiceAttr): boolean {\n return service['@_name'] === 'https' || service['@_tunnel'] === 'ssl';\n}\n\n/** product, version, extrainfo からバナー文字列を合成する */\nfunction buildBanner(service: NmapServiceAttr): string | undefined {\n const parts: string[] = [];\n if (service['@_product']) parts.push(service['@_product']);\n if (service['@_version']) parts.push(service['@_version']);\n if (service['@_extrainfo']) parts.push(service['@_extrainfo']);\n return parts.length > 0 ? parts.join(' ') : undefined;\n}\n\n/** IPv4 アドレスを address 配列から取得する */\nfunction getIpv4Address(addresses: NmapAddress[]): string | undefined {\n const ipv4 = addresses.find((a) => a['@_addrtype'] === 'ipv4');\n return ipv4?.['@_addr'];\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nmap XML 出力をパースし、ParseResult を返す。\n *\n * @param xml - nmap の XML 出力文字列\n * @returns ParseResult 中間表現\n */\nexport function parseNmapXml(xml: string): ParseResult {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n allowBooleanAttributes: true,\n });\n\n const parsed: unknown = parser.parse(xml);\n const nmapRun = parsed as NmapRun;\n\n const result = emptyParseResult();\n\n const hosts = ensureArray(nmapRun.nmaprun?.host);\n\n for (const host of hosts) {\n processHost(host, result);\n }\n\n return result;\n}\n\n/**\n * 単一の host 要素を処理し、結果に追加する。\n */\nfunction processHost(host: NmapHost, result: ParseResult): void {\n // --- ホスト情報 ---\n const addresses = ensureArray(host.address);\n const authority = getIpv4Address(addresses);\n if (authority === undefined) {\n return; // IPv4 アドレスがないホストはスキップ\n }\n\n const parsedHost: ParsedHost = {\n authority,\n authorityKind: 'IP',\n };\n result.hosts.push(parsedHost);\n\n // --- サービス情報 ---\n const ports = ensureArray(host.ports?.port);\n const services: ParsedService[] = [];\n\n for (const port of ports) {\n const service = processPort(port, authority);\n services.push(service);\n result.services.push(service);\n }\n\n // --- OS 情報 ---\n const osMatches = ensureArray(host.os?.osmatch);\n if (osMatches.length > 0) {\n processOsMatches(osMatches, authority, services, result);\n }\n}\n\n/**\n * 単一の port 要素を処理し、ParsedService を返す。\n */\nfunction processPort(port: NmapPort, hostAuthority: string): ParsedService {\n const service = port.service;\n const serviceName = service?.['@_name'] ?? '';\n\n // appProto の決定: https or tunnel=ssl -> 'https'\n const appProto = service !== undefined && isHttps(service) ? 'https' : serviceName;\n\n const banner = service !== undefined ? buildBanner(service) : undefined;\n const protoConfidence = toProtoConfidence(service?.['@_conf']);\n\n return {\n hostAuthority,\n transport: port['@_protocol'],\n port: Number(port['@_portid']),\n appProto,\n protoConfidence,\n banner,\n product: service?.['@_product'],\n version: service?.['@_version'],\n state: port.state['@_state'],\n };\n}\n\n/**\n * OS マッチ情報を serviceObservations に追加する。\n * 最初のサービスの transport/port を使う。サービスがない場合は port=0, transport='tcp' を使う。\n */\nfunction processOsMatches(\n osMatches: NmapOsMatch[],\n hostAuthority: string,\n services: ParsedService[],\n result: ParseResult,\n): void {\n const firstService = services.length > 0 ? services[0] : undefined;\n const transport = firstService?.transport ?? 'tcp';\n const port = firstService?.port ?? 0;\n\n for (const osMatch of osMatches) {\n const observation: ParsedServiceObservation = {\n hostAuthority,\n transport,\n port,\n key: 'os',\n value: osMatch['@_name'],\n confidence: toOsConfidence(osMatch['@_accuracy']),\n };\n result.serviceObservations.push(observation);\n }\n}\n","/**\n * sonobat — Parser intermediate types\n *\n * パーサーは DB の ID を持たない中間表現を返す。\n * Normalizer が自然キー(authority, port 等)で DB を検索・upsert する。\n */\n\n// ============================================================\n// 中間表現(DB ID を持たない)\n// ============================================================\n\n/** ホストの中間表現 */\nexport interface ParsedHost {\n authority: string;\n authorityKind: 'IP' | 'DOMAIN';\n resolvedIps?: string[];\n}\n\n/** サービスの中間表現 */\nexport interface ParsedService {\n hostAuthority: string;\n transport: string;\n port: number;\n appProto: string;\n protoConfidence: string;\n banner?: string;\n product?: string;\n version?: string;\n state: string;\n}\n\n/** サービス観測の中間表現 */\nexport interface ParsedServiceObservation {\n hostAuthority: string;\n transport: string;\n port: number;\n key: string;\n value: string;\n confidence: string;\n}\n\n/** HTTP エンドポイントの中間表現 */\nexport interface ParsedHttpEndpoint {\n hostAuthority: string;\n port: number;\n baseUri: string;\n method: string;\n path: string;\n statusCode?: number;\n contentLength?: number;\n words?: number;\n lines?: number;\n}\n\n/** 入力パラメータの中間表現 */\nexport interface ParsedInput {\n hostAuthority: string;\n port: number;\n location: string;\n name: string;\n typeHint?: string;\n}\n\n/** エンドポイント ↔ 入力の紐づけ中間表現 */\nexport interface ParsedEndpointInput {\n hostAuthority: string;\n port: number;\n method: string;\n path: string;\n inputLocation: string;\n inputName: string;\n}\n\n/** 観測値の中間表現 */\nexport interface ParsedObservation {\n hostAuthority: string;\n port: number;\n inputLocation: string;\n inputName: string;\n rawValue: string;\n normValue: string;\n source: string;\n confidence: string;\n}\n\n/** 脆弱性の中間表現 */\nexport interface ParsedVulnerability {\n hostAuthority: string;\n port: number;\n method?: string;\n path?: string;\n vulnType: string;\n title: string;\n description?: string;\n severity: string;\n confidence: string;\n}\n\n/** CVE の中間表現 */\nexport interface ParsedCve {\n /** 対応する脆弱性の title(紐づけ用) */\n vulnerabilityTitle: string;\n cveId: string;\n description?: string;\n cvssScore?: number;\n cvssVector?: string;\n referenceUrl?: string;\n}\n\n// ============================================================\n// パース結果\n// ============================================================\n\n/** パーサーが返す統一的な結果型 */\nexport interface ParseResult {\n hosts: ParsedHost[];\n services: ParsedService[];\n serviceObservations: ParsedServiceObservation[];\n httpEndpoints: ParsedHttpEndpoint[];\n inputs: ParsedInput[];\n endpointInputs: ParsedEndpointInput[];\n observations: ParsedObservation[];\n vulnerabilities: ParsedVulnerability[];\n cves: ParsedCve[];\n}\n\n/** 空の ParseResult を生成するユーティリティ */\nexport function emptyParseResult(): ParseResult {\n return {\n hosts: [],\n services: [],\n serviceObservations: [],\n httpEndpoints: [],\n inputs: [],\n endpointInputs: [],\n observations: [],\n vulnerabilities: [],\n cves: [],\n };\n}\n","/**\r\n * sonobat — ffuf JSON output parser\r\n *\r\n * ffuf の JSON 出力をパースし、ParseResult に変換する。\r\n * パスディスカバリ、パラメータファジングの両方に対応。\r\n */\r\n\r\nimport type {\r\n ParseResult,\r\n ParsedHost,\r\n ParsedService,\r\n ParsedHttpEndpoint,\r\n ParsedInput,\r\n ParsedEndpointInput,\r\n ParsedObservation,\r\n} from '../types/parser.js';\r\nimport { emptyParseResult } from '../types/parser.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// 内部型: ffuf JSON 構造のバリデーション用\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface FfufConfig {\r\n url: string;\r\n method: string;\r\n}\r\n\r\ninterface FfufResult {\r\n input: Record<string, string>;\r\n status: number;\r\n length: number;\r\n words: number;\r\n lines: number;\r\n url: string;\r\n host: string;\r\n}\r\n\r\ninterface FfufJson {\r\n commandline: string;\r\n config: FfufConfig;\r\n results: FfufResult[];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// バリデーション\r\n// ---------------------------------------------------------------------------\r\n\r\nconst IP_REGEX = /^\\d{1,3}(\\.\\d{1,3}){3}$/;\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction validateFfufJson(raw: unknown): FfufJson {\r\n if (!isRecord(raw)) {\r\n throw new Error('ffuf JSON: root must be an object');\r\n }\r\n\r\n if (typeof raw['commandline'] !== 'string') {\r\n throw new Error('ffuf JSON: commandline must be a string');\r\n }\r\n\r\n const config = raw['config'];\r\n if (!isRecord(config)) {\r\n throw new Error('ffuf JSON: config must be an object');\r\n }\r\n if (typeof config['url'] !== 'string' || typeof config['method'] !== 'string') {\r\n throw new Error('ffuf JSON: config.url and config.method must be strings');\r\n }\r\n\r\n if (!Array.isArray(raw['results'])) {\r\n throw new Error('ffuf JSON: results must be an array');\r\n }\r\n\r\n const results: FfufResult[] = [];\r\n for (const item of raw['results'] as unknown[]) {\r\n if (!isRecord(item)) {\r\n throw new Error('ffuf JSON: each result must be an object');\r\n }\r\n results.push({\r\n input: isRecord(item['input'])\r\n ? Object.fromEntries(\r\n Object.entries(item['input'] as Record<string, unknown>).map(([k, v]) => [\r\n k,\r\n String(v),\r\n ]),\r\n )\r\n : {},\r\n status: typeof item['status'] === 'number' ? item['status'] : 0,\r\n length: typeof item['length'] === 'number' ? item['length'] : 0,\r\n words: typeof item['words'] === 'number' ? item['words'] : 0,\r\n lines: typeof item['lines'] === 'number' ? item['lines'] : 0,\r\n url: typeof item['url'] === 'string' ? item['url'] : '',\r\n host: typeof item['host'] === 'string' ? item['host'] : '',\r\n });\r\n }\r\n\r\n return {\r\n commandline: raw['commandline'] as string,\r\n config: {\r\n url: config['url'] as string,\r\n method: config['method'] as string,\r\n },\r\n results,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// URL ユーティリティ\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedUrl {\r\n scheme: string;\r\n hostname: string;\r\n port: number;\r\n pathname: string;\r\n searchParams: URLSearchParams;\r\n}\r\n\r\nfunction parseUrl(urlStr: string): ParsedUrl {\r\n const parsed = new URL(urlStr);\r\n const scheme = parsed.protocol.replace(':', '');\r\n\r\n let port: number;\r\n if (parsed.port !== '') {\r\n port = Number(parsed.port);\r\n } else {\r\n port = scheme === 'https' ? 443 : 80;\r\n }\r\n\r\n return {\r\n scheme,\r\n hostname: parsed.hostname,\r\n port,\r\n pathname: parsed.pathname,\r\n searchParams: parsed.searchParams,\r\n };\r\n}\r\n\r\nfunction determineAuthorityKind(hostname: string): 'IP' | 'DOMAIN' {\r\n return IP_REGEX.test(hostname) ? 'IP' : 'DOMAIN';\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// メインパーサー\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * ffuf の JSON 出力文字列をパースし、ParseResult を返す。\r\n *\r\n * @param jsonContent - ffuf が `-of json` で出力した JSON 文字列\r\n * @returns ParseResult\r\n */\r\nexport function parseFfufJson(jsonContent: string): ParseResult {\r\n const raw: unknown = JSON.parse(jsonContent);\r\n const ffuf = validateFfufJson(raw);\r\n const method = ffuf.config.method;\r\n\r\n // results が空なら全て空配列\r\n if (ffuf.results.length === 0) {\r\n return emptyParseResult();\r\n }\r\n\r\n // ----- 集約用 Map -----\r\n // hosts: authority -> ParsedHost\r\n const hostsMap = new Map<string, ParsedHost>();\r\n // services: \"authority:port\" -> ParsedService\r\n const servicesMap = new Map<string, ParsedService>();\r\n // httpEndpoints: \"method:path\" -> ParsedHttpEndpoint\r\n const endpointsMap = new Map<string, ParsedHttpEndpoint>();\r\n // inputs: \"name\" -> ParsedInput (クエリパラメータ名で重複排除)\r\n const inputsMap = new Map<string, ParsedInput>();\r\n // endpointInputs: \"method:path:location:name\" -> ParsedEndpointInput\r\n const endpointInputsMap = new Map<string, ParsedEndpointInput>();\r\n // observations: \"location:name:rawValue\" -> ParsedObservation\r\n const observationsMap = new Map<string, ParsedObservation>();\r\n\r\n for (const result of ffuf.results) {\r\n if (result.url === '') {\r\n continue;\r\n }\r\n\r\n const parsed = parseUrl(result.url);\r\n const { scheme, hostname, port, pathname, searchParams } = parsed;\r\n const authorityKind = determineAuthorityKind(hostname);\r\n const baseUri = `${scheme}://${hostname}:${port}`;\r\n\r\n // --- Host ---\r\n if (!hostsMap.has(hostname)) {\r\n hostsMap.set(hostname, {\r\n authority: hostname,\r\n authorityKind,\r\n });\r\n }\r\n\r\n // --- Service ---\r\n const serviceKey = `${hostname}:${port}`;\r\n if (!servicesMap.has(serviceKey)) {\r\n servicesMap.set(serviceKey, {\r\n hostAuthority: hostname,\r\n transport: 'tcp',\r\n port,\r\n appProto: scheme,\r\n protoConfidence: 'high',\r\n state: 'open',\r\n });\r\n }\r\n\r\n // --- HTTP Endpoint (method + path で重複排除) ---\r\n const endpointKey = `${hostname}:${port}:${method}:${pathname}`;\r\n if (!endpointsMap.has(endpointKey)) {\r\n endpointsMap.set(endpointKey, {\r\n hostAuthority: hostname,\r\n port,\r\n baseUri,\r\n method,\r\n path: pathname,\r\n statusCode: result.status,\r\n contentLength: result.length,\r\n words: result.words,\r\n lines: result.lines,\r\n });\r\n }\r\n\r\n // --- Query Parameters -> inputs, observations, endpointInputs ---\r\n for (const [paramName, paramValue] of searchParams.entries()) {\r\n // Input (パラメータ名で重複排除)\r\n const inputKey = `${hostname}:${port}:query:${paramName}`;\r\n if (!inputsMap.has(inputKey)) {\r\n inputsMap.set(inputKey, {\r\n hostAuthority: hostname,\r\n port,\r\n location: 'query',\r\n name: paramName,\r\n });\r\n }\r\n\r\n // EndpointInput (endpoint + input の組み合わせで重複排除)\r\n const eiKey = `${hostname}:${port}:${method}:${pathname}:query:${paramName}`;\r\n if (!endpointInputsMap.has(eiKey)) {\r\n endpointInputsMap.set(eiKey, {\r\n hostAuthority: hostname,\r\n port,\r\n method,\r\n path: pathname,\r\n inputLocation: 'query',\r\n inputName: paramName,\r\n });\r\n }\r\n\r\n // Observation (パラメータ名 + 値で重複排除)\r\n const obsKey = `${hostname}:${port}:query:${paramName}:${paramValue}`;\r\n if (!observationsMap.has(obsKey)) {\r\n observationsMap.set(obsKey, {\r\n hostAuthority: hostname,\r\n port,\r\n inputLocation: 'query',\r\n inputName: paramName,\r\n rawValue: paramValue,\r\n normValue: paramValue,\r\n source: 'ffuf_url',\r\n confidence: 'high',\r\n });\r\n }\r\n }\r\n }\r\n\r\n return {\r\n hosts: [...hostsMap.values()],\r\n services: [...servicesMap.values()],\r\n serviceObservations: [],\r\n httpEndpoints: [...endpointsMap.values()],\r\n inputs: [...inputsMap.values()],\r\n endpointInputs: [...endpointInputsMap.values()],\r\n observations: [...observationsMap.values()],\r\n vulnerabilities: [],\r\n cves: [],\r\n };\r\n}\r\n","/**\n * sonobat — Nuclei JSONL パーサー\n *\n * nuclei の JSONL 出力を解析し、ParseResult 中間表現を返す。\n * 各行は独立した JSON オブジェクト(nuclei finding)として処理する。\n */\n\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedHttpEndpoint,\n ParsedVulnerability,\n ParsedCve,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// nuclei finding の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NucleiClassification {\n 'cve-id'?: string[];\n 'cvss-metrics'?: string;\n 'cvss-score'?: number;\n}\n\ninterface NucleiInfo {\n name: string;\n severity: string;\n tags: string[];\n classification?: NucleiClassification;\n}\n\ninterface NucleiFinding {\n 'template-id': string;\n info: NucleiInfo;\n type: string;\n host: string;\n 'matched-at': string;\n ip: string;\n port: string;\n scheme: string;\n url: string;\n}\n\n// ============================================================\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 isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string');\n}\n\nfunction isNucleiClassification(value: unknown): value is NucleiClassification {\n if (!isRecord(value)) return false;\n // classification は空オブジェクトでも許容する\n if ('cve-id' in value && !isStringArray(value['cve-id'])) return false;\n if (\n 'cvss-metrics' in value &&\n typeof value['cvss-metrics'] !== 'string' &&\n value['cvss-metrics'] !== undefined\n )\n return false;\n if (\n 'cvss-score' in value &&\n typeof value['cvss-score'] !== 'number' &&\n value['cvss-score'] !== undefined\n )\n return false;\n return true;\n}\n\nfunction isNucleiInfo(value: unknown): value is NucleiInfo {\n if (!isRecord(value)) return false;\n if (typeof value.name !== 'string') return false;\n if (typeof value.severity !== 'string') return false;\n if (!isStringArray(value.tags)) return false;\n if (\n 'classification' in value &&\n value.classification !== undefined &&\n !isNucleiClassification(value.classification)\n )\n return false;\n return true;\n}\n\nfunction isNucleiFinding(value: unknown): value is NucleiFinding {\n if (!isRecord(value)) return false;\n if (typeof value['template-id'] !== 'string') return false;\n if (!isNucleiInfo(value.info)) return false;\n if (typeof value.type !== 'string') return false;\n if (typeof value.host !== 'string') return false;\n if (typeof value['matched-at'] !== 'string') return false;\n if (typeof value.ip !== 'string') return false;\n if (typeof value.port !== 'string') return false;\n if (typeof value.scheme !== 'string') return false;\n if (typeof value.url !== 'string') return false;\n return true;\n}\n\n// ============================================================\n// URL パス抽出(生文字列から、デコードせずに抽出する)\n// ============================================================\n\n/**\n * URL 文字列から pathname 部分を生文字列のまま抽出する。\n * Node.js の URL クラスは %2e を . にデコードしてパスを正規化してしまうため、\n * パストラバーサル系のペイロードを保存するには raw なパスが必要。\n */\nfunction extractRawPathname(urlStr: string): string {\n // scheme://authority の後のパス部分を取り出す\n // authority の終わり = 3つ目の / の位置(scheme://host:port/path...)\n const schemeEnd = urlStr.indexOf('://');\n if (schemeEnd === -1) {\n return '/';\n }\n const afterAuthority = urlStr.indexOf('/', schemeEnd + 3);\n if (afterAuthority === -1) {\n return '/';\n }\n // パスの終わり = ? または # の最初の出現位置\n const queryStart = urlStr.indexOf('?', afterAuthority);\n const fragmentStart = urlStr.indexOf('#', afterAuthority);\n let pathEnd = urlStr.length;\n if (queryStart !== -1 && queryStart < pathEnd) {\n pathEnd = queryStart;\n }\n if (fragmentStart !== -1 && fragmentStart < pathEnd) {\n pathEnd = fragmentStart;\n }\n return urlStr.substring(afterAuthority, pathEnd);\n}\n\n// ============================================================\n// vulnType 推定\n// ============================================================\n\n/** タグ配列から vulnType を推定する。優先度順に判定する。 */\nfunction inferVulnType(tags: string[]): string {\n const priorityTags = ['sqli', 'xss', 'rce', 'lfi', 'ssrf'] as const;\n for (const tag of priorityTags) {\n if (tags.includes(tag)) {\n return tag;\n }\n }\n return 'other';\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nuclei JSONL 出力をパースし、ParseResult を返す。\n *\n * @param jsonl - nuclei の JSONL 出力文字列(1行1JSON)\n * @returns ParseResult 中間表現\n */\nexport function parseNucleiJsonl(jsonl: string): ParseResult {\n const result = emptyParseResult();\n\n if (jsonl.trim() === '') {\n return result;\n }\n\n const lines = jsonl.split('\\n');\n const seenHosts = new Set<string>();\n const seenServices = new Set<string>();\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === '') {\n continue;\n }\n\n const parsed: unknown = JSON.parse(trimmed);\n if (!isNucleiFinding(parsed)) {\n continue;\n }\n\n processFinding(parsed, result, seenHosts, seenServices);\n }\n\n return result;\n}\n\n/**\n * 単一の nuclei finding を処理し、結果に追加する。\n */\nfunction processFinding(\n finding: NucleiFinding,\n result: ParseResult,\n seenHosts: Set<string>,\n seenServices: Set<string>,\n): void {\n const ip = finding.ip;\n const port = Number(finding.port);\n const scheme = finding.scheme;\n const matchedAt = finding['matched-at'];\n\n // --- ホスト(重複排除) ---\n if (!seenHosts.has(ip)) {\n seenHosts.add(ip);\n const host: ParsedHost = {\n authority: ip,\n authorityKind: 'IP',\n };\n result.hosts.push(host);\n }\n\n // --- サービス(authority+port で重複排除) ---\n const serviceKey = `${ip}:${port}`;\n if (!seenServices.has(serviceKey)) {\n seenServices.add(serviceKey);\n const service: ParsedService = {\n hostAuthority: ip,\n transport: 'tcp',\n port,\n appProto: scheme,\n protoConfidence: 'high',\n state: 'open',\n };\n result.services.push(service);\n }\n\n // --- HTTP エンドポイント ---\n const rawPath = extractRawPathname(matchedAt);\n const baseUri = `${scheme}://${ip}:${port}`;\n\n const endpoint: ParsedHttpEndpoint = {\n hostAuthority: ip,\n port,\n baseUri,\n method: 'GET',\n path: rawPath,\n };\n result.httpEndpoints.push(endpoint);\n\n // --- 脆弱性 ---\n const info = finding.info;\n const vulnerability: ParsedVulnerability = {\n hostAuthority: ip,\n port,\n method: 'GET',\n path: rawPath,\n vulnType: inferVulnType(info.tags),\n title: info.name,\n severity: info.severity,\n confidence: 'high',\n };\n result.vulnerabilities.push(vulnerability);\n\n // --- CVE ---\n const classification = info.classification;\n if (\n classification !== undefined &&\n isRecord(classification) &&\n 'cve-id' in classification &&\n isStringArray(classification['cve-id']) &&\n classification['cve-id'].length > 0\n ) {\n for (const cveId of classification['cve-id']) {\n const cve: ParsedCve = {\n vulnerabilityTitle: info.name,\n cveId,\n cvssScore:\n typeof classification['cvss-score'] === 'number'\n ? classification['cvss-score']\n : undefined,\n cvssVector:\n typeof classification['cvss-metrics'] === 'string'\n ? classification['cvss-metrics']\n : undefined,\n };\n result.cves.push(cve);\n }\n }\n}\n","/**\r\n * sonobat — Normalizer (Graph-native)\r\n *\r\n * ParseResult(パーサーの中間表現)を受け取り、\r\n * NodeRepository / EdgeRepository を使ってグラフ DB に書き込む。\r\n * 全操作はトランザクションでラップし、アトミックに実行する。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { ParseResult } from '../types/parser.js';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\n\r\n// ============================================================\r\n// 結果型\r\n// ============================================================\r\n\r\n/** normalize() の実行結果。各エンティティの新規作成数を返す。 */\r\nexport interface NormalizeResult {\r\n hostsCreated: number;\r\n servicesCreated: number;\r\n serviceObservationsCreated: number;\r\n httpEndpointsCreated: number;\r\n inputsCreated: number;\r\n endpointInputsCreated: number;\r\n observationsCreated: number;\r\n vulnerabilitiesCreated: number;\r\n cvesCreated: number;\r\n}\r\n\r\n// ============================================================\r\n// normalize\r\n// ============================================================\r\n\r\n/**\r\n * ParseResult を DB に正規化して書き込む(Graph-native 版)。\r\n *\r\n * - NodeRepository.upsert() で自然キーによる重複排除を行い、\r\n * EdgeRepository.upsert() でリレーションを作成する。\r\n * - 全操作は 1 トランザクション内で実行される。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param artifactId 根拠となる Artifact の ID(evidence_artifact_id に設定)\r\n * @param parseResult パーサーが返した中間表現\r\n * @returns 各エンティティの新規作成数\r\n */\r\nexport function normalize(\r\n db: Database.Database,\r\n artifactId: string,\r\n parseResult: ParseResult,\r\n): NormalizeResult {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n const run = db.transaction((): NormalizeResult => {\r\n const result: NormalizeResult = {\r\n hostsCreated: 0,\r\n servicesCreated: 0,\r\n serviceObservationsCreated: 0,\r\n httpEndpointsCreated: 0,\r\n inputsCreated: 0,\r\n endpointInputsCreated: 0,\r\n observationsCreated: 0,\r\n vulnerabilitiesCreated: 0,\r\n cvesCreated: 0,\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // 自然キー → ノード ID のマッピング\r\n // ---------------------------------------------------------\r\n const hostIdByAuthority = new Map<string, string>();\r\n // key: \"hostNodeId:transport:port\"\r\n const serviceIdByKey = new Map<string, string>();\r\n // key: \"serviceNodeId:method:path\"\r\n const endpointIdByKey = new Map<string, string>();\r\n // key: \"serviceNodeId:location:name\"\r\n const inputIdByKey = new Map<string, string>();\r\n // key: vulnerability title → node ID\r\n const vulnIdByTitle = new Map<string, string>();\r\n\r\n // ---------------------------------------------------------\r\n // 1. Upsert hosts\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.hosts) {\r\n const { node, created } = nodeRepo.upsert('host', {\r\n authorityKind: parsed.authorityKind,\r\n authority: parsed.authority,\r\n resolvedIpsJson: JSON.stringify(parsed.resolvedIps ?? []),\r\n });\r\n\r\n hostIdByAuthority.set(parsed.authority, node.id);\r\n if (created) {\r\n result.hostsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 2. Upsert services + HOST_SERVICE edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.services) {\r\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\r\n if (!hostId) continue;\r\n\r\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\r\n if (serviceIdByKey.has(svcKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'service',\r\n {\r\n transport: parsed.transport,\r\n port: parsed.port,\r\n appProto: parsed.appProto,\r\n protoConfidence: parsed.protoConfidence,\r\n banner: parsed.banner,\r\n product: parsed.product,\r\n version: parsed.version,\r\n state: parsed.state,\r\n },\r\n artifactId,\r\n hostId,\r\n );\r\n\r\n serviceIdByKey.set(svcKey, node.id);\r\n\r\n // HOST_SERVICE edge\r\n edgeRepo.upsert('HOST_SERVICE', hostId, node.id, artifactId);\r\n\r\n if (created) {\r\n result.servicesCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // ヘルパー: hostAuthority + port → serviceId を解決\r\n // HTTP 系エンティティは transport が常に tcp\r\n // ---------------------------------------------------------\r\n function resolveServiceId(hostAuthority: string, port: number): string | undefined {\r\n const hostId = hostIdByAuthority.get(hostAuthority);\r\n if (!hostId) return undefined;\r\n return serviceIdByKey.get(`${hostId}:tcp:${port}`);\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 3. Service observations (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.serviceObservations) {\r\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\r\n if (!hostId) continue;\r\n\r\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\r\n const serviceId = serviceIdByKey.get(svcKey);\r\n if (!serviceId) continue;\r\n\r\n const obsNode = nodeRepo.create(\r\n 'svc_observation',\r\n {\r\n key: parsed.key,\r\n value: parsed.value,\r\n confidence: parsed.confidence,\r\n },\r\n artifactId,\r\n );\r\n\r\n edgeRepo.create('SERVICE_OBSERVATION', serviceId, obsNode.id, artifactId);\r\n result.serviceObservationsCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 4. Upsert HTTP endpoints + SERVICE_ENDPOINT edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.httpEndpoints) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n if (endpointIdByKey.has(epKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'endpoint',\r\n {\r\n baseUri: parsed.baseUri,\r\n method: parsed.method,\r\n path: parsed.path,\r\n statusCode: parsed.statusCode,\r\n contentLength: parsed.contentLength,\r\n words: parsed.words,\r\n lines: parsed.lines,\r\n },\r\n artifactId,\r\n serviceId,\r\n );\r\n\r\n endpointIdByKey.set(epKey, node.id);\r\n\r\n // SERVICE_ENDPOINT edge\r\n edgeRepo.upsert('SERVICE_ENDPOINT', serviceId, node.id, artifactId);\r\n\r\n if (created) {\r\n result.httpEndpointsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 5. Upsert inputs + SERVICE_INPUT edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.inputs) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.location}:${parsed.name}`;\r\n if (inputIdByKey.has(inKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'input',\r\n {\r\n location: parsed.location,\r\n name: parsed.name,\r\n typeHint: parsed.typeHint,\r\n },\r\n undefined,\r\n serviceId,\r\n );\r\n\r\n inputIdByKey.set(inKey, node.id);\r\n\r\n // SERVICE_INPUT edge\r\n edgeRepo.upsert('SERVICE_INPUT', serviceId, node.id);\r\n\r\n if (created) {\r\n result.inputsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 6. Upsert endpoint_inputs (ENDPOINT_INPUT edges)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.endpointInputs) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n const endpointId = endpointIdByKey.get(epKey);\r\n if (!endpointId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\r\n const inputId = inputIdByKey.get(inKey);\r\n if (!inputId) continue;\r\n\r\n const { created } = edgeRepo.upsert('ENDPOINT_INPUT', endpointId, inputId, artifactId);\r\n if (created) {\r\n result.endpointInputsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 7. Observations (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.observations) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\r\n const inputId = inputIdByKey.get(inKey);\r\n if (!inputId) continue;\r\n\r\n const obsNode = nodeRepo.create(\r\n 'observation',\r\n {\r\n rawValue: parsed.rawValue,\r\n normValue: parsed.normValue,\r\n source: parsed.source,\r\n confidence: parsed.confidence,\r\n observedAt: new Date().toISOString(),\r\n },\r\n artifactId,\r\n );\r\n\r\n edgeRepo.create('INPUT_OBSERVATION', inputId, obsNode.id, artifactId);\r\n result.observationsCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 8. Vulnerabilities (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.vulnerabilities) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n // endpoint への紐づけ(任意)\r\n let endpointId: string | undefined;\r\n if (parsed.method && parsed.path) {\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n endpointId = endpointIdByKey.get(epKey);\r\n }\r\n\r\n const vulnNode = nodeRepo.create(\r\n 'vulnerability',\r\n {\r\n vulnType: parsed.vulnType,\r\n title: parsed.title,\r\n description: parsed.description,\r\n severity: parsed.severity,\r\n confidence: parsed.confidence,\r\n },\r\n artifactId,\r\n );\r\n\r\n vulnIdByTitle.set(parsed.title, vulnNode.id);\r\n\r\n // SERVICE_VULNERABILITY edge\r\n edgeRepo.create('SERVICE_VULNERABILITY', serviceId, vulnNode.id, artifactId);\r\n\r\n // optional ENDPOINT_VULNERABILITY edge\r\n if (endpointId) {\r\n edgeRepo.create('ENDPOINT_VULNERABILITY', endpointId, vulnNode.id, artifactId);\r\n }\r\n\r\n result.vulnerabilitiesCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 9. CVEs (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.cves) {\r\n const vulnId = vulnIdByTitle.get(parsed.vulnerabilityTitle);\r\n if (!vulnId) continue;\r\n\r\n const cveNode = nodeRepo.create(\r\n 'cve',\r\n {\r\n cveId: parsed.cveId,\r\n description: parsed.description,\r\n cvssScore: parsed.cvssScore,\r\n cvssVector: parsed.cvssVector,\r\n referenceUrl: parsed.referenceUrl,\r\n },\r\n undefined,\r\n vulnId,\r\n );\r\n\r\n edgeRepo.create('VULNERABILITY_CVE', vulnId, cveNode.id);\r\n result.cvesCreated++;\r\n }\r\n\r\n return result;\r\n });\r\n\r\n return run();\r\n}\r\n","/**\n * sonobat — MCP Propose Tool\n *\n * Tool for generating next-step action proposals based on missing data.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { propose } from '../../engine/proposer.js';\n\nexport function registerProposeTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'propose',\n 'Analyze the AttackDataGraph for missing data and propose next-step actions',\n {\n hostId: z.string().optional().describe('Limit proposals to a specific host (optional)'),\n },\n async ({ hostId }) => {\n const actions = propose(db, hostId);\n if (actions.length === 0) {\n return {\n content: [\n { type: 'text', text: 'No actions proposed. All discovered data appears complete.' },\n ],\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(actions, null, 2) }] };\n },\n );\n}\n","/**\r\n * sonobat — Proposer engine (graph-native)\r\n *\r\n * Analyzes the AttackDataGraph stored in SQLite and proposes\r\n * next-step actions (scans, discovery, etc.) based on missing data.\r\n *\r\n * Uses NodeRepository + EdgeRepository instead of entity-specific repositories.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Action } from '../types/engine.js';\r\nimport type { GraphNode } from '../types/graph.js';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\n\r\n/** Helper: parse propsJson safely */\r\nfunction parseProps(node: GraphNode): Record<string, unknown> {\r\n return JSON.parse(node.propsJson) as Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Analyze the database for missing reconnaissance data and return\r\n * a list of proposed actions to fill the gaps.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param hostId - Optional: limit analysis to a single host\r\n * @returns Array of proposed actions\r\n */\r\nexport function propose(db: Database.Database, hostId?: string): Action[] {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n const actions: Action[] = [];\r\n\r\n // Determine target hosts\r\n let hosts: GraphNode[];\r\n if (hostId !== undefined) {\r\n const host = nodeRepo.findById(hostId);\r\n if (host === undefined) {\r\n return [];\r\n }\r\n hosts = [host];\r\n } else {\r\n hosts = nodeRepo.findByKind('host');\r\n }\r\n\r\n for (const host of hosts) {\r\n const hostProps = parseProps(host);\r\n const authority = hostProps.authority as string;\r\n\r\n // Find services: edges from host where kind='HOST_SERVICE' -> target nodes\r\n const serviceEdges = edgeRepo.findBySource(host.id, 'HOST_SERVICE');\r\n\r\n // (a) No services at all -> suggest nmap scan\r\n if (serviceEdges.length === 0) {\r\n actions.push({\r\n kind: 'nmap_scan',\r\n description: `Port scan ${authority} to discover services`,\r\n command: `nmap -p- -sV ${authority}`,\r\n params: { hostId: host.id },\r\n });\r\n continue;\r\n }\r\n\r\n // (b) For each HTTP/HTTPS service, check for missing data\r\n for (const serviceEdge of serviceEdges) {\r\n const serviceNode = nodeRepo.findById(serviceEdge.targetId);\r\n if (serviceNode === undefined) {\r\n continue;\r\n }\r\n\r\n const serviceProps = parseProps(serviceNode);\r\n const appProto = serviceProps.appProto as string;\r\n const port = serviceProps.port as number;\r\n const state = serviceProps.state as string | undefined;\r\n\r\n if (appProto !== 'http' && appProto !== 'https') {\r\n continue;\r\n }\r\n if (state !== undefined && state !== 'open') {\r\n continue;\r\n }\r\n\r\n const baseUri = `${appProto}://${authority}:${port}`;\r\n\r\n proposeForHttpService(actions, host, serviceNode, authority, baseUri, nodeRepo, edgeRepo);\r\n }\r\n }\r\n\r\n return actions;\r\n}\r\n\r\n/**\r\n * Check a single HTTP/HTTPS service for missing data and push\r\n * proposed actions into the actions array.\r\n */\r\nfunction proposeForHttpService(\r\n actions: Action[],\r\n host: GraphNode,\r\n service: GraphNode,\r\n authority: string,\r\n baseUri: string,\r\n nodeRepo: NodeRepository,\r\n edgeRepo: EdgeRepository,\r\n): void {\r\n // Find endpoints: edges from service where kind='SERVICE_ENDPOINT' -> target nodes\r\n const endpointEdges = edgeRepo.findBySource(service.id, 'SERVICE_ENDPOINT');\r\n\r\n // No endpoints -> suggest directory/file discovery\r\n if (endpointEdges.length === 0) {\r\n actions.push({\r\n kind: 'ffuf_discovery',\r\n description: `Discover endpoints on ${baseUri}`,\r\n command: `ffuf -u ${baseUri}/FUZZ -w /usr/share/wordlists/dirb/common.txt`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities at service level (used for value_fuzz decision)\r\n // Filter out false_positive vulnerabilities — they should not prevent\r\n // the proposer from suggesting further testing actions.\r\n const vulnEdges = edgeRepo.findBySource(service.id, 'SERVICE_VULNERABILITY');\r\n const activeVulns = vulnEdges.filter((ve) => {\r\n const vulnNode = nodeRepo.findById(ve.targetId);\r\n if (vulnNode === undefined) {\r\n return false;\r\n }\r\n const vulnProps = parseProps(vulnNode);\r\n return vulnProps.status !== 'false_positive';\r\n });\r\n\r\n // For each endpoint: check inputs via ENDPOINT_INPUT edges (per-endpoint)\r\n for (const endpointEdge of endpointEdges) {\r\n const endpointNode = nodeRepo.findById(endpointEdge.targetId);\r\n if (endpointNode === undefined) {\r\n continue;\r\n }\r\n\r\n const endpointProps = parseProps(endpointNode);\r\n const endpointPath = endpointProps.path as string;\r\n\r\n // Find endpoint_input edges: edges from endpoint where kind='ENDPOINT_INPUT' -> input nodes\r\n const inputEdges = edgeRepo.findBySource(endpointNode.id, 'ENDPOINT_INPUT');\r\n\r\n if (inputEdges.length === 0) {\r\n actions.push({\r\n kind: 'parameter_discovery',\r\n description: `Discover input parameters for ${baseUri}${endpointPath}`,\r\n params: { hostId: host.id, serviceId: service.id, endpointId: endpointNode.id },\r\n });\r\n }\r\n\r\n // For each linked input: check observations\r\n for (const inputEdge of inputEdges) {\r\n const inputNode = nodeRepo.findById(inputEdge.targetId);\r\n if (inputNode === undefined) {\r\n continue;\r\n }\r\n\r\n const inputProps = parseProps(inputNode);\r\n const inputName = inputProps.name as string;\r\n const inputLocation = inputProps.location as string;\r\n\r\n // Find observations: edges from input where kind='INPUT_OBSERVATION' -> target nodes\r\n const observationEdges = edgeRepo.findBySource(inputNode.id, 'INPUT_OBSERVATION');\r\n\r\n if (observationEdges.length === 0) {\r\n actions.push({\r\n kind: 'value_collection',\r\n description: `Collect observed values for input \"${inputName}\" (${inputLocation})`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpointNode.id,\r\n inputId: inputNode.id,\r\n },\r\n });\r\n } else if (activeVulns.length === 0) {\r\n // Has input + observations, but no active vulnerabilities -> suggest fuzzing\r\n actions.push({\r\n kind: 'value_fuzz',\r\n description: `Fuzz input \"${inputName}\" (${inputLocation}) on ${baseUri}${endpointPath}`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpointNode.id,\r\n inputId: inputNode.id,\r\n },\r\n });\r\n }\r\n }\r\n }\r\n\r\n // Check vhosts: edges from host where kind='HOST_VHOST'\r\n const vhostEdges = edgeRepo.findBySource(host.id, 'HOST_VHOST');\r\n if (vhostEdges.length === 0) {\r\n actions.push({\r\n kind: 'vhost_discovery',\r\n description: `Discover virtual hosts for ${authority}`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities -> suggest nuclei scan if no active vulnerabilities\r\n if (activeVulns.length === 0) {\r\n actions.push({\r\n kind: 'nuclei_scan',\r\n description: `Scan ${baseUri} for known vulnerabilities`,\r\n command: `nuclei -u ${baseUri} -jsonl`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Knowledge Base Tools\r\n *\r\n * Tools for searching technique documentation (HackTricks knowledge base)\r\n * and managing the FTS5 index.\r\n *\r\n * Features:\r\n * - search_kb: Full-text search with BM25 ranking\r\n * - index_kb: Auto-clone/pull HackTricks + incremental indexing\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport { z } from 'zod';\r\nimport { TechniqueDocRepository } from '../../db/repository/technique-doc-repository.js';\r\nimport { indexHacktricks } from '../../engine/indexer.js';\r\nimport { getHacktricksDir, ensureDataDir } from '../../engine/data-dir.js';\r\nimport { cloneHacktricks, pullHacktricks, isGitAvailable } from '../../engine/git-ops.js';\r\nimport type { IndexResult } from '../../engine/indexer.js';\r\n\r\n/**\r\n * Format an IndexResult into a human-readable message.\r\n */\r\nfunction formatIndexResult(result: IndexResult, dir: string): string {\r\n const lines: string[] = [\r\n `Successfully indexed from ${dir}.`,\r\n ` Chunks inserted: ${result.totalChunks}`,\r\n ` New files: ${result.newFiles}`,\r\n ` Updated files: ${result.updatedFiles}`,\r\n ` Deleted files: ${result.deletedFiles}`,\r\n ` Skipped (unchanged): ${result.skippedFiles}`,\r\n ];\r\n return lines.join('\\n');\r\n}\r\n\r\nexport function registerKbTools(server: McpServer, db: Database.Database): void {\r\n const repo = new TechniqueDocRepository(db);\r\n\r\n // 1. search_kb\r\n server.tool(\r\n 'search_kb',\r\n 'Search the technique knowledge base (HackTricks) using full-text search. Returns relevant technique documentation chunks ranked by relevance.',\r\n {\r\n query: z.string().describe('Search query (e.g. \"docker breakout\", \"SQL injection\")'),\r\n category: z\r\n .string()\r\n .optional()\r\n .describe('Filter by category (e.g. \"linux-hardening\", \"web\")'),\r\n limit: z.number().optional().describe('Maximum number of results (default: 10)'),\r\n },\r\n async ({ query, category, limit }) => {\r\n const results = repo.search(query, { limit: limit ?? 10, category });\r\n\r\n if (results.length === 0) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `No results found for \"${query}\".${repo.count() === 0 ? ' The technique index is empty. Run index_kb to populate it.' : ''}`,\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const formatted = results.map((r) => ({\r\n title: r.title,\r\n category: r.category,\r\n filePath: r.filePath,\r\n chunkIndex: r.chunkIndex,\r\n score: r.score,\r\n content: r.content,\r\n }));\r\n\r\n return { content: [{ type: 'text', text: JSON.stringify(formatted, null, 2) }] };\r\n },\r\n );\r\n\r\n // 2. index_kb (with auto-clone/pull)\r\n server.tool(\r\n 'index_kb',\r\n 'Index or re-index the HackTricks documentation into the full-text search database. When called without a path, automatically clones or updates HackTricks from GitHub. Uses incremental indexing to skip unchanged files.',\r\n {\r\n path: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Path to the HackTricks repository directory. If omitted, auto-clones to ~/.sonobat/data/hacktricks/',\r\n ),\r\n update: z\r\n .boolean()\r\n .optional()\r\n .describe(\r\n 'Whether to pull latest changes before indexing (default: true). Set to false to skip git pull.',\r\n ),\r\n },\r\n async ({ path: hacktricksPath, update }) => {\r\n try {\r\n let dir: string;\r\n const messages: string[] = [];\r\n\r\n if (hacktricksPath) {\r\n // Explicit path provided — use as-is\r\n dir = hacktricksPath;\r\n } else {\r\n // Auto-clone/pull flow\r\n dir = getHacktricksDir();\r\n ensureDataDir();\r\n\r\n if (!fs.existsSync(dir)) {\r\n // Directory doesn't exist → clone\r\n const gitAvailable = await isGitAvailable();\r\n if (!gitAvailable) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: 'git is not installed or not in PATH. Please install git and try again, or provide a path to an existing HackTricks directory.',\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n messages.push(`Cloning HackTricks to ${dir}...`);\r\n const cloneResult = await cloneHacktricks(dir);\r\n if (!cloneResult.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Failed to clone HackTricks: ${cloneResult.error.message}${cloneResult.error.cause ? ` (${cloneResult.error.cause})` : ''}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n messages.push('Clone completed.');\r\n } else if (update !== false) {\r\n // Directory exists → pull (failure is a warning, not an error)\r\n const pullResult = await pullHacktricks(dir);\r\n if (pullResult.ok) {\r\n messages.push(`Updated: ${pullResult.message}`);\r\n } else {\r\n messages.push(\r\n `Warning: git pull failed (${pullResult.error.message}). Continuing with existing data.`,\r\n );\r\n }\r\n }\r\n }\r\n\r\n const result = indexHacktricks(db, dir);\r\n messages.push(formatIndexResult(result, dir));\r\n\r\n return {\r\n content: [{ type: 'text', text: messages.join('\\n') }],\r\n };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n return {\r\n content: [{ type: 'text', text: `Failed to index HackTricks: ${message}` }],\r\n isError: true,\r\n };\r\n }\r\n },\r\n );\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\n\r\n/** Technique documentation entity (camelCase). */\r\nexport interface TechniqueDoc {\r\n id: string;\r\n source: string;\r\n filePath: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunkIndex: number;\r\n indexedAt: string;\r\n fileMtime: string | null;\r\n}\r\n\r\n/** Input for creating a new technique document (no id/indexedAt). */\r\nexport interface CreateTechniqueDocInput {\r\n source: string;\r\n filePath: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunkIndex: number;\r\n fileMtime?: string;\r\n}\r\n\r\n/**\r\n * Raw row shape returned by better-sqlite3 for the `technique_docs` table.\r\n */\r\ninterface TechniqueDocRow {\r\n id: string;\r\n source: string;\r\n file_path: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunk_index: number;\r\n indexed_at: string;\r\n file_mtime: string | null;\r\n}\r\n\r\n/**\r\n * Row shape for FTS5 search results with rank score.\r\n */\r\ninterface TechniqueDocSearchRow extends TechniqueDocRow {\r\n rank: number;\r\n}\r\n\r\n/** Maps a snake_case DB row to a camelCase TechniqueDoc entity. */\r\nfunction rowToTechniqueDoc(row: TechniqueDocRow): TechniqueDoc {\r\n return {\r\n id: row.id,\r\n source: row.source,\r\n filePath: row.file_path,\r\n title: row.title,\r\n category: row.category,\r\n content: row.content,\r\n chunkIndex: row.chunk_index,\r\n indexedAt: row.indexed_at,\r\n fileMtime: row.file_mtime,\r\n };\r\n}\r\n\r\n/** TechniqueDoc with search relevance score. */\r\nexport interface TechniqueDocSearchResult extends TechniqueDoc {\r\n score: number;\r\n}\r\n\r\n/** Options for FTS5 search. */\r\nexport interface SearchOptions {\r\n limit?: number;\r\n category?: string;\r\n}\r\n\r\nconst BATCH_SIZE = 100;\r\n\r\n/**\r\n * Repository for the `technique_docs` table with FTS5 full-text search.\r\n *\r\n * All queries use prepared statements to prevent SQL injection.\r\n */\r\nexport class TechniqueDocRepository {\r\n private readonly db: Database.Database;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n }\r\n\r\n /**\r\n * Bulk-insert technique documents. Runs in batches within transactions.\r\n * Returns the number of inserted documents.\r\n */\r\n index(docs: CreateTechniqueDocInput[]): number {\r\n if (docs.length === 0) return 0;\r\n\r\n const now = new Date().toISOString();\r\n const stmt = this.db.prepare(\r\n `INSERT INTO technique_docs (id, source, file_path, title, category, content, chunk_index, indexed_at, file_mtime)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\r\n );\r\n\r\n let inserted = 0;\r\n\r\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\r\n const batch = docs.slice(i, i + BATCH_SIZE);\r\n const insertBatch = this.db.transaction(() => {\r\n for (const doc of batch) {\r\n const id = crypto.randomUUID();\r\n stmt.run(\r\n id,\r\n doc.source,\r\n doc.filePath,\r\n doc.title,\r\n doc.category,\r\n doc.content,\r\n doc.chunkIndex,\r\n now,\r\n doc.fileMtime ?? null,\r\n );\r\n inserted++;\r\n }\r\n });\r\n insertBatch();\r\n }\r\n\r\n return inserted;\r\n }\r\n\r\n /**\r\n * Full-text search using FTS5 BM25 ranking.\r\n * User input is wrapped in double quotes for literal matching to prevent FTS5 syntax errors.\r\n */\r\n search(query: string, options: SearchOptions = {}): TechniqueDocSearchResult[] {\r\n const { limit = 20, category } = options;\r\n\r\n // Split query into individual terms, wrap each in double quotes for safety,\r\n // and join with spaces (FTS5 implicit AND). This prevents FTS5 syntax errors\r\n // while allowing flexible multi-word matching.\r\n const terms = query\r\n .trim()\r\n .split(/\\s+/)\r\n .filter((t) => t.length > 0);\r\n const safeQuery = terms.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(' ');\r\n\r\n let sql: string;\r\n const params: unknown[] = [safeQuery];\r\n\r\n if (category) {\r\n sql = `SELECT td.*, fts.rank\r\n FROM technique_docs td\r\n JOIN technique_docs_fts fts ON td.rowid = fts.rowid\r\n WHERE technique_docs_fts MATCH ?\r\n AND td.category = ?\r\n ORDER BY fts.rank\r\n LIMIT ?`;\r\n params.push(category, limit);\r\n } else {\r\n sql = `SELECT td.*, fts.rank\r\n FROM technique_docs td\r\n JOIN technique_docs_fts fts ON td.rowid = fts.rowid\r\n WHERE technique_docs_fts MATCH ?\r\n ORDER BY fts.rank\r\n LIMIT ?`;\r\n params.push(limit);\r\n }\r\n\r\n const rows = this.db.prepare(sql).all(...params) as TechniqueDocSearchRow[];\r\n\r\n return rows.map((row) => ({\r\n ...rowToTechniqueDoc(row),\r\n score: row.rank * -1,\r\n }));\r\n }\r\n\r\n /** Return all unique categories. */\r\n listCategories(): string[] {\r\n const rows = this.db\r\n .prepare('SELECT DISTINCT category FROM technique_docs ORDER BY category')\r\n .all() as Array<{ category: string }>;\r\n return rows.map((r) => r.category);\r\n }\r\n\r\n /** Return all documents in a given category. */\r\n findByCategory(category: string): TechniqueDoc[] {\r\n const rows = this.db\r\n .prepare<[string], TechniqueDocRow>(\r\n `SELECT id, source, file_path, title, category, content, chunk_index, indexed_at, file_mtime\r\n FROM technique_docs\r\n WHERE category = ?\r\n ORDER BY file_path, chunk_index`,\r\n )\r\n .all(category);\r\n return rows.map(rowToTechniqueDoc);\r\n }\r\n\r\n /**\r\n * Delete all documents from a given source.\r\n * Returns the number of deleted documents.\r\n */\r\n deleteBySource(source: string): number {\r\n const result = this.db.prepare('DELETE FROM technique_docs WHERE source = ?').run(source);\r\n return result.changes;\r\n }\r\n\r\n /** Return the total number of indexed documents. */\r\n count(): number {\r\n const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM technique_docs').get() as {\r\n cnt: number;\r\n };\r\n return row.cnt;\r\n }\r\n\r\n /**\r\n * Get a map of file_path → file_mtime for all documents from a given source.\r\n * Returns one entry per unique file_path (uses the first mtime found).\r\n * Used for incremental indexing to detect changed/new/deleted files.\r\n */\r\n findMtimesBySource(source: string): Map<string, string | null> {\r\n const rows = this.db\r\n .prepare(\r\n `SELECT DISTINCT file_path, file_mtime FROM technique_docs WHERE source = ? GROUP BY file_path`,\r\n )\r\n .all(source) as Array<{ file_path: string; file_mtime: string | null }>;\r\n\r\n const map = new Map<string, string | null>();\r\n for (const row of rows) {\r\n map.set(row.file_path, row.file_mtime);\r\n }\r\n return map;\r\n }\r\n\r\n /**\r\n * Delete documents from a given source whose file_path is in the provided list.\r\n * Returns the number of deleted documents.\r\n * Used for incremental indexing to remove changed/deleted files before re-inserting.\r\n */\r\n deleteBySourceAndFilePaths(source: string, filePaths: string[]): number {\r\n if (filePaths.length === 0) return 0;\r\n\r\n // Use parameterized IN clause to prevent SQL injection\r\n const placeholders = filePaths.map(() => '?').join(', ');\r\n const stmt = this.db.prepare(\r\n `DELETE FROM technique_docs WHERE source = ? AND file_path IN (${placeholders})`,\r\n );\r\n\r\n const result = stmt.run(source, ...filePaths);\r\n return result.changes;\r\n }\r\n}\r\n","/**\r\n * sonobat — HackTricks Indexer Engine\r\n *\r\n * Parses Markdown files into searchable chunks and indexes them into\r\n * the technique_docs table with FTS5 full-text search.\r\n * Supports incremental indexing by comparing file mtimes.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { TechniqueDocRepository } from '../db/repository/technique-doc-repository.js';\r\nimport type { CreateTechniqueDocInput } from '../db/repository/technique-doc-repository.js';\r\n\r\n/** A parsed chunk from a Markdown file. */\r\nexport interface MarkdownChunk {\r\n title: string;\r\n content: string;\r\n chunkIndex: number;\r\n}\r\n\r\n/** Result of an indexing operation. */\r\nexport interface IndexResult {\r\n totalChunks: number;\r\n newFiles: number;\r\n updatedFiles: number;\r\n deletedFiles: number;\r\n skippedFiles: number;\r\n}\r\n\r\n/**\r\n * Parse a Markdown file into chunks split by H2 boundaries.\r\n *\r\n * - H1 (`# Title`) is extracted as the document title (falls back to filename).\r\n * - Content is split at H2 (`## Section`) boundaries.\r\n * - Each chunk is an independent search unit.\r\n * - If no H2 is present, the entire content is one chunk.\r\n * - Empty/whitespace-only content returns an empty array.\r\n */\r\nexport function parseMarkdownChunks(markdown: string, fileName: string): MarkdownChunk[] {\r\n const trimmed = markdown.trim();\r\n if (trimmed.length === 0) return [];\r\n\r\n const lines = trimmed.split('\\n');\r\n\r\n // Extract H1 title\r\n let title: string | undefined;\r\n let contentStartIndex = 0;\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const match = lines[i].match(/^#\\s+(.+)$/);\r\n if (match) {\r\n title = match[1].trim();\r\n contentStartIndex = i + 1;\r\n break;\r\n }\r\n }\r\n\r\n if (!title) {\r\n // Use filename without extension as title\r\n title = path.basename(fileName, path.extname(fileName));\r\n contentStartIndex = 0;\r\n }\r\n\r\n const contentLines = lines.slice(contentStartIndex);\r\n\r\n // Split by H2 boundaries\r\n const sections: Array<{ heading: string | null; lines: string[] }> = [];\r\n let currentSection: { heading: string | null; lines: string[] } = { heading: null, lines: [] };\r\n\r\n for (const line of contentLines) {\r\n const h2Match = line.match(/^##\\s+(.+)$/);\r\n if (h2Match) {\r\n // Save current section if it has content\r\n sections.push(currentSection);\r\n currentSection = { heading: h2Match[1].trim(), lines: [] };\r\n } else {\r\n currentSection.lines.push(line);\r\n }\r\n }\r\n sections.push(currentSection);\r\n\r\n // Build chunks\r\n const chunks: MarkdownChunk[] = [];\r\n let chunkIndex = 0;\r\n\r\n for (const section of sections) {\r\n const sectionContent = section.heading\r\n ? `## ${section.heading}\\n\\n${section.lines.join('\\n').trim()}`\r\n : section.lines.join('\\n').trim();\r\n\r\n if (sectionContent.length === 0) continue;\r\n\r\n chunks.push({\r\n title,\r\n content: sectionContent,\r\n chunkIndex,\r\n });\r\n chunkIndex++;\r\n }\r\n\r\n return chunks;\r\n}\r\n\r\n/**\r\n * Extract category from a file path (relative to the root directory).\r\n * Category is the directory portion of the path using forward slashes.\r\n *\r\n * Examples:\r\n * \"linux-hardening/privilege-escalation/docker-breakout.md\" → \"linux-hardening/privilege-escalation\"\r\n * \"web/sql-injection.md\" → \"web\"\r\n * \"README.md\" → \"\"\r\n */\r\nexport function extractCategory(filePath: string): string {\r\n // Normalize Windows backslashes to forward slashes\r\n const normalized = filePath.replace(/\\\\/g, '/');\r\n const dir = path.posix.dirname(normalized);\r\n return dir === '.' ? '' : dir;\r\n}\r\n\r\n/**\r\n * Recursively collect all .md files from a directory.\r\n */\r\nfunction collectMarkdownFiles(dir: string, baseDir: string): string[] {\r\n const files: string[] = [];\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n files.push(...collectMarkdownFiles(fullPath, baseDir));\r\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\r\n // Skip README files\r\n if (entry.name.toLowerCase() === 'readme.md') continue;\r\n files.push(fullPath);\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\nconst SOURCE_NAME = 'hacktricks';\r\n\r\n/**\r\n * Index all Markdown files from a HackTricks directory into the database.\r\n *\r\n * Uses incremental indexing:\r\n * 1. Fetches existing file_path → file_mtime map from DB\r\n * 2. Compares with disk files to classify: new, updated, deleted, unchanged\r\n * 3. Only processes new/updated files; deletes removed files from DB\r\n *\r\n * Returns an IndexResult with statistics about what was processed.\r\n */\r\nexport function indexHacktricks(db: Database.Database, hacktricksDir: string): IndexResult {\r\n const repo = new TechniqueDocRepository(db);\r\n\r\n // Step 1: Get existing mtimes from DB\r\n const existingMtimes = repo.findMtimesBySource(SOURCE_NAME);\r\n\r\n // Step 2: Collect current files on disk and their mtimes\r\n const mdFiles = collectMarkdownFiles(hacktricksDir, hacktricksDir);\r\n const diskFiles = new Map<string, string>(); // relativePath → mtime ISO string\r\n\r\n for (const filePath of mdFiles) {\r\n const relativePath = path.relative(hacktricksDir, filePath).replace(/\\\\/g, '/');\r\n const stat = fs.statSync(filePath);\r\n diskFiles.set(relativePath, stat.mtime.toISOString());\r\n }\r\n\r\n // Step 3: Classify files\r\n const newFiles: string[] = [];\r\n const updatedFiles: string[] = [];\r\n const skippedFiles: string[] = [];\r\n\r\n for (const [relativePath, diskMtime] of diskFiles) {\r\n const existingMtime = existingMtimes.get(relativePath);\r\n if (existingMtime === undefined) {\r\n // Not in DB → new file\r\n newFiles.push(relativePath);\r\n } else if (existingMtime !== diskMtime) {\r\n // In DB but mtime changed → updated file\r\n updatedFiles.push(relativePath);\r\n } else {\r\n // Same mtime → skip\r\n skippedFiles.push(relativePath);\r\n }\r\n }\r\n\r\n // Deleted files: in DB but not on disk\r\n const deletedFiles: string[] = [];\r\n for (const existingPath of existingMtimes.keys()) {\r\n if (!diskFiles.has(existingPath)) {\r\n deletedFiles.push(existingPath);\r\n }\r\n }\r\n\r\n // Step 4: Delete changed/removed files from DB\r\n const filesToDelete = [...updatedFiles, ...deletedFiles];\r\n if (filesToDelete.length > 0) {\r\n repo.deleteBySourceAndFilePaths(SOURCE_NAME, filesToDelete);\r\n }\r\n\r\n // Step 5: Parse and insert new/updated files\r\n const filesToInsert = [...newFiles, ...updatedFiles];\r\n const allDocs: CreateTechniqueDocInput[] = [];\r\n\r\n for (const relativePath of filesToInsert) {\r\n const fullPath = path.join(hacktricksDir, relativePath);\r\n const content = fs.readFileSync(fullPath, 'utf-8');\r\n const category = extractCategory(relativePath);\r\n const chunks = parseMarkdownChunks(content, path.basename(fullPath));\r\n const fileMtime = diskFiles.get(relativePath)!;\r\n\r\n for (const chunk of chunks) {\r\n allDocs.push({\r\n source: SOURCE_NAME,\r\n filePath: relativePath,\r\n title: chunk.title,\r\n category,\r\n content: chunk.content,\r\n chunkIndex: chunk.chunkIndex,\r\n fileMtime,\r\n });\r\n }\r\n }\r\n\r\n const insertedChunks = repo.index(allDocs);\r\n\r\n return {\r\n totalChunks: insertedChunks,\r\n newFiles: newFiles.length,\r\n updatedFiles: updatedFiles.length,\r\n deletedFiles: deletedFiles.length,\r\n skippedFiles: skippedFiles.length,\r\n };\r\n}\r\n","/**\r\n * sonobat — Data Directory Utilities\r\n *\r\n * Manages the default data directory (~/.sonobat/data/) where HackTricks\r\n * and other data sources are stored. Supports overriding via environment\r\n * variable SONOBAT_DATA_DIR for testing and custom setups.\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport os from 'node:os';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Get the root data directory path.\r\n * Default: ~/.sonobat/data/\r\n * Override: set SONOBAT_DATA_DIR environment variable.\r\n */\r\nexport function getDataDir(): string {\r\n const envDir = process.env['SONOBAT_DATA_DIR'];\r\n if (envDir) return envDir;\r\n return path.join(os.homedir(), '.sonobat', 'data');\r\n}\r\n\r\n/**\r\n * Get the HackTricks repository directory path.\r\n * Returns: <dataDir>/hacktricks/\r\n */\r\nexport function getHacktricksDir(): string {\r\n return path.join(getDataDir(), 'hacktricks');\r\n}\r\n\r\n/**\r\n * Ensure the data directory exists, creating it recursively if needed.\r\n */\r\nexport function ensureDataDir(): void {\r\n fs.mkdirSync(getDataDir(), { recursive: true });\r\n}\r\n","/**\r\n * sonobat — Git Operations for HackTricks\r\n *\r\n * Provides git clone/pull operations for managing the HackTricks repository.\r\n * Uses execFile (not shell) to prevent command injection.\r\n */\r\n\r\nimport { execFile as execFileCb } from 'node:child_process';\r\nimport { promisify } from 'node:util';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\nconst execFile = promisify(execFileCb);\r\n\r\nconst HACKTRICKS_REPO = 'https://github.com/HackTricks-wiki/hacktricks.git';\r\nconst TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\r\n\r\n/** Error kinds for git operations. */\r\nexport type GitErrorKind =\r\n | 'git_not_found'\r\n | 'clone_failed'\r\n | 'pull_failed'\r\n | 'permission_denied'\r\n | 'network_error'\r\n | 'directory_not_found'\r\n | 'unknown';\r\n\r\n/** Result type for git operations. */\r\nexport type GitResult =\r\n | { ok: true; message: string }\r\n | { ok: false; error: { kind: GitErrorKind; message: string; cause?: string } };\r\n\r\n/**\r\n * Check if git is available on the system PATH.\r\n */\r\nexport async function isGitAvailable(): Promise<boolean> {\r\n try {\r\n await execFile('git', ['--version'], { timeout: 10_000 });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Classify an error from execFile into a GitErrorKind.\r\n */\r\nfunction classifyError(err: unknown, operation: 'clone' | 'pull'): GitResult {\r\n const errorMessage = err instanceof Error ? err.message : String(err);\r\n const stderr =\r\n err !== null && typeof err === 'object' && 'stderr' in err\r\n ? String((err as { stderr: unknown }).stderr)\r\n : '';\r\n\r\n const combined = `${errorMessage} ${stderr}`.toLowerCase();\r\n\r\n if (combined.includes('enoent') || combined.includes('not recognized') || combined.includes('not found')) {\r\n return {\r\n ok: false,\r\n error: { kind: 'git_not_found', message: 'git is not installed or not in PATH', cause: errorMessage },\r\n };\r\n }\r\n\r\n if (combined.includes('permission denied') || combined.includes('access denied')) {\r\n return {\r\n ok: false,\r\n error: { kind: 'permission_denied', message: `Permission denied during ${operation}`, cause: errorMessage },\r\n };\r\n }\r\n\r\n if (\r\n combined.includes('could not resolve') ||\r\n combined.includes('unable to access') ||\r\n combined.includes('network') ||\r\n combined.includes('timed out')\r\n ) {\r\n return {\r\n ok: false,\r\n error: { kind: 'network_error', message: `Network error during ${operation}`, cause: errorMessage },\r\n };\r\n }\r\n\r\n return {\r\n ok: false,\r\n error: {\r\n kind: operation === 'clone' ? 'clone_failed' : 'pull_failed',\r\n message: `git ${operation} failed`,\r\n cause: errorMessage,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Clone the HackTricks repository with --depth 1 (shallow clone).\r\n * The targetDir is the destination directory for the clone.\r\n * The parent directory of targetDir must exist.\r\n */\r\nexport async function cloneHacktricks(targetDir: string): Promise<GitResult> {\r\n // Check parent directory exists before attempting clone\r\n const parentDir = path.dirname(targetDir);\r\n\r\n if (!fs.existsSync(parentDir)) {\r\n return {\r\n ok: false,\r\n error: {\r\n kind: 'clone_failed',\r\n message: `Parent directory does not exist: ${parentDir}`,\r\n },\r\n };\r\n }\r\n\r\n try {\r\n const { stdout } = await execFile(\r\n 'git',\r\n ['clone', '--depth', '1', HACKTRICKS_REPO, targetDir],\r\n { timeout: TIMEOUT_MS },\r\n );\r\n return { ok: true, message: stdout.trim() || 'Clone completed successfully' };\r\n } catch (err) {\r\n return classifyError(err, 'clone');\r\n }\r\n}\r\n\r\n/**\r\n * Pull latest changes in an existing HackTricks repository.\r\n * Uses --ff-only to avoid merge conflicts.\r\n */\r\nexport async function pullHacktricks(repoDir: string): Promise<GitResult> {\r\n // Check if directory exists\r\n if (!fs.existsSync(repoDir)) {\r\n return {\r\n ok: false,\r\n error: {\r\n kind: 'directory_not_found',\r\n message: `Directory not found: ${repoDir}`,\r\n },\r\n };\r\n }\r\n\r\n try {\r\n const { stdout } = await execFile('git', ['pull', '--ff-only'], {\r\n cwd: repoDir,\r\n timeout: TIMEOUT_MS,\r\n });\r\n return { ok: true, message: stdout.trim() || 'Already up to date' };\r\n } catch (err) {\r\n return classifyError(err, 'pull');\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Resources\r\n *\r\n * Read-only resources for browsing the AttackDataGraph.\r\n * Uses the graph-native nodes/edges schema.\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\nimport { TechniqueDocRepository } from '../db/repository/technique-doc-repository.js';\r\nimport { NODE_KINDS } from '../types/graph.js';\r\n\r\nexport function registerResources(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n const techDocRepo = new TechniqueDocRepository(db);\r\n\r\n // 1. sonobat://nodes?kind=host — Node list (replaces sonobat://hosts)\r\n server.resource(\r\n 'nodes',\r\n 'sonobat://nodes',\r\n {\r\n description:\r\n 'List of all nodes in the AttackDataGraph (optionally filter by kind via query param)',\r\n },\r\n async (uri) => {\r\n const kindParam = uri.searchParams?.get('kind');\r\n let nodes;\r\n if (kindParam && NODE_KINDS.includes(kindParam as (typeof NODE_KINDS)[number])) {\r\n nodes = nodeRepo.findByKind(kindParam as (typeof NODE_KINDS)[number]);\r\n } else {\r\n // Return all nodes (summary view)\r\n nodes = NODE_KINDS.flatMap((k) => nodeRepo.findByKind(k));\r\n }\r\n const result = nodes.map((n) => ({ ...n, props: JSON.parse(n.propsJson) }));\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify(result, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 2. sonobat://nodes/{id} — Node detail (replaces sonobat://hosts/{id})\r\n server.resource(\r\n 'node-detail',\r\n 'sonobat://nodes/{id}',\r\n { description: 'Detailed node with adjacent edges and neighbor nodes' },\r\n async (uri) => {\r\n const nodeId = uri.pathname.split('/').pop() ?? '';\r\n const node = nodeRepo.findById(nodeId);\r\n if (!node) {\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify({ error: `Node not found: ${nodeId}` }),\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const outEdges = edgeRepo.findBySource(node.id);\r\n const inEdges = edgeRepo.findByTarget(node.id);\r\n const adjacentNodeIds = new Set([\r\n ...outEdges.map((e) => e.targetId),\r\n ...inEdges.map((e) => e.sourceId),\r\n ]);\r\n const adjacentNodes = [...adjacentNodeIds]\r\n .map((nid) => nodeRepo.findById(nid))\r\n .filter(Boolean)\r\n .map((n) => ({ ...n!, props: JSON.parse(n!.propsJson) }));\r\n\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n outEdges,\r\n inEdges,\r\n adjacentNodes,\r\n };\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify(result, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 3. sonobat://summary — Statistics summary\r\n server.resource(\r\n 'summary',\r\n 'sonobat://summary',\r\n { description: 'Summary statistics of the AttackDataGraph' },\r\n async () => {\r\n const nodeCounts: Record<string, number> = {};\r\n for (const k of NODE_KINDS) {\r\n nodeCounts[k] = nodeRepo.findByKind(k).length;\r\n }\r\n const edgeCount = (db.prepare('SELECT COUNT(*) AS cnt FROM edges').get() as { cnt: number })\r\n .cnt;\r\n const artifactCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM artifacts').get() as { cnt: number }\r\n ).cnt;\r\n\r\n return {\r\n contents: [\r\n {\r\n uri: 'sonobat://summary',\r\n mimeType: 'application/json',\r\n text: JSON.stringify(\r\n { nodes: nodeCounts, edges: edgeCount, artifacts: artifactCount },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 4. sonobat://techniques/categories — Technique categories\r\n server.resource(\r\n 'technique-categories',\r\n 'sonobat://techniques/categories',\r\n { description: 'List of all technique documentation categories' },\r\n async () => {\r\n const categories = techDocRepo.listCategories();\r\n return {\r\n contents: [\r\n {\r\n uri: 'sonobat://techniques/categories',\r\n mimeType: 'application/json',\r\n text: JSON.stringify(categories, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n}\r\n"],"mappings":"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","names":["db","migration","db","migration","db","migration","db","migration","db","randomUUID","migration","db","db","migration","db","z","crypto","randomUUID","db","crypto","rows","crypto","db","rows","rowToEdge","db","currentId","server","db","z","z","db","server","z","z","crypto","transport","isRecord","db","db","crypto","server","db","z","path","z","db","server","db","z","fs","z","crypto","db","fs","path","path","fs","db","fs","path","fs","path","server","db","z","fs","server","db","db","server"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/db/migrations/v0.ts","../src/db/migrations/v1.ts","../src/db/migrations/v2.ts","../src/db/migrations/v3.ts","../src/db/migrations/v4.ts","../src/db/migrations/v5.ts","../src/db/migrations/index.ts","../src/db/migrate.ts","../src/mcp/server.ts","../src/mcp/tools/query.ts","../src/db/repository/node-repository.ts","../src/types/graph.ts","../src/db/repository/edge-repository.ts","../src/db/repository/graph-query-repository.ts","../src/mcp/tools/mutate.ts","../src/mcp/tools/ingest.ts","../src/engine/ingest.ts","../src/parser/nmap-parser.ts","../src/types/parser.ts","../src/parser/ffuf-parser.ts","../src/parser/nuclei-parser.ts","../src/engine/normalizer.ts","../src/mcp/tools/propose.ts","../src/engine/proposer.ts","../src/mcp/tools/kb.ts","../src/db/repository/technique-doc-repository.ts","../src/engine/indexer.ts","../src/engine/data-dir.ts","../src/engine/git-ops.ts","../src/mcp/tools/ops.ts","../src/db/repository/engagement-repository.ts","../src/db/repository/run-repository.ts","../src/db/repository/action-queue-repository.ts","../src/db/repository/action-execution-repository.ts","../src/mcp/tools/findings.ts","../src/db/repository/finding-repository.ts","../src/db/repository/risk-snapshot-repository.ts","../src/mcp/resources.ts"],"sourcesContent":["/**\r\n * sonobat — AttackDataGraph for autonomous penetration testing\r\n *\r\n * MCP Server エントリポイント。\r\n * stdio トランスポートで LLM Agent と接続する。\r\n */\r\n\r\nimport { readFileSync } from 'node:fs';\r\nimport { dirname, resolve } from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport Database from 'better-sqlite3';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport { migrateDatabase } from './db/migrate.js';\r\nimport { createMcpServer } from './mcp/server.js';\r\n\r\nconst __dirname = dirname(fileURLToPath(import.meta.url));\r\nconst pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8')) as {\r\n version: string;\r\n};\r\n\r\nconst DB_PATH = process.env['SONOBAT_DB_PATH'] ?? 'sonobat.db';\r\nconst db = new Database(DB_PATH);\r\nmigrateDatabase(db);\r\n\r\nconst server = createMcpServer(db, pkg.version);\r\nconst transport = new StdioServerTransport();\r\nawait server.connect(transport);\r\n","/**\r\n * Migration v0: Base schema\r\n *\r\n * 元の sonobat スキーマ(v1/v2/v3 の追加分を除く)。\r\n * fresh DB でマイグレーション順次実行するための基盤。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Migration } from './index.js';\r\n\r\nconst migration: Migration = {\r\n version: 0,\r\n description: 'Base schema (scans, artifacts, hosts, services, endpoints, etc.)',\r\n up(db: Database.Database): void {\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS scans (\r\n id TEXT PRIMARY KEY,\r\n started_at TEXT NOT NULL,\r\n finished_at TEXT,\r\n notes TEXT\r\n );\r\n\r\n CREATE TABLE IF NOT EXISTS artifacts (\r\n id TEXT PRIMARY KEY,\r\n scan_id TEXT,\r\n tool TEXT NOT NULL,\r\n kind TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n sha256 TEXT,\r\n captured_at TEXT NOT NULL,\r\n attrs_json TEXT,\r\n FOREIGN KEY (scan_id) REFERENCES scans(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_tool ON artifacts(tool);\r\n\r\n CREATE TABLE IF NOT EXISTS hosts (\r\n id TEXT PRIMARY KEY,\r\n authority_kind TEXT NOT NULL,\r\n authority TEXT NOT NULL UNIQUE,\r\n resolved_ips_json TEXT NOT NULL DEFAULT '[]',\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n\r\n CREATE TABLE IF NOT EXISTS vhosts (\r\n id TEXT PRIMARY KEY,\r\n host_id TEXT NOT NULL,\r\n hostname TEXT NOT NULL,\r\n source TEXT,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (host_id, hostname),\r\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_vhosts_host ON vhosts(host_id);\r\n\r\n CREATE TABLE IF NOT EXISTS services (\r\n id TEXT PRIMARY KEY,\r\n host_id TEXT NOT NULL,\r\n transport TEXT NOT NULL,\r\n port INTEGER NOT NULL,\r\n app_proto TEXT NOT NULL,\r\n proto_confidence TEXT NOT NULL,\r\n banner TEXT,\r\n product TEXT,\r\n version TEXT,\r\n state TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n UNIQUE (host_id, transport, port),\r\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_services_host ON services(host_id);\r\n\r\n CREATE TABLE IF NOT EXISTS service_observations (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n key TEXT NOT NULL,\r\n value TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_svc_obs_service ON service_observations(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS http_endpoints (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n vhost_id TEXT,\r\n base_uri TEXT NOT NULL,\r\n method TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n status_code INTEGER,\r\n content_length INTEGER,\r\n words INTEGER,\r\n lines INTEGER,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (service_id, method, path),\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (vhost_id) REFERENCES vhosts(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_endpoints_service ON http_endpoints(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS inputs (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n location TEXT NOT NULL,\r\n name TEXT NOT NULL,\r\n type_hint TEXT,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n UNIQUE (service_id, location, name),\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_inputs_service ON inputs(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS endpoint_inputs (\r\n id TEXT PRIMARY KEY,\r\n endpoint_id TEXT NOT NULL,\r\n input_id TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (endpoint_id, input_id),\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE CASCADE,\r\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_ep_inputs_endpoint ON endpoint_inputs(endpoint_id);\r\n CREATE INDEX IF NOT EXISTS idx_ep_inputs_input ON endpoint_inputs(input_id);\r\n\r\n CREATE TABLE IF NOT EXISTS observations (\r\n id TEXT PRIMARY KEY,\r\n input_id TEXT NOT NULL,\r\n raw_value TEXT NOT NULL,\r\n norm_value TEXT NOT NULL,\r\n body_path TEXT,\r\n source TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n observed_at TEXT NOT NULL,\r\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_obs_input ON observations(input_id);\r\n\r\n CREATE TABLE IF NOT EXISTS credentials (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n endpoint_id TEXT,\r\n username TEXT NOT NULL,\r\n secret TEXT NOT NULL,\r\n secret_type TEXT NOT NULL,\r\n source TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_creds_service ON credentials(service_id);\r\n CREATE INDEX IF NOT EXISTS idx_creds_endpoint ON credentials(endpoint_id);\r\n\r\n CREATE TABLE IF NOT EXISTS vulnerabilities (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n endpoint_id TEXT,\r\n vuln_type TEXT NOT NULL,\r\n title TEXT NOT NULL,\r\n description TEXT,\r\n severity TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_vulns_service ON vulnerabilities(service_id);\r\n CREATE INDEX IF NOT EXISTS idx_vulns_endpoint ON vulnerabilities(endpoint_id);\r\n CREATE INDEX IF NOT EXISTS idx_vulns_severity ON vulnerabilities(severity);\r\n\r\n CREATE TABLE IF NOT EXISTS cves (\r\n id TEXT PRIMARY KEY,\r\n vulnerability_id TEXT NOT NULL,\r\n cve_id TEXT NOT NULL,\r\n description TEXT,\r\n cvss_score REAL,\r\n cvss_vector TEXT,\r\n reference_url TEXT,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_cves_vuln ON cves(vulnerability_id);\r\n CREATE INDEX IF NOT EXISTS idx_cves_cveid ON cves(cve_id);\r\n `);\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\n * Migration v1: Add datalog_rules table\n *\n * This migration adds the datalog_rules table for storing\n * custom Datalog rules created by humans or AI agents.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 1,\n description: 'Add datalog_rules table',\n up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS datalog_rules (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n rule_text TEXT NOT NULL,\n generated_by TEXT NOT NULL, -- \"human\" | \"ai\" | \"preset\"\n is_preset INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_datalog_rules_name ON datalog_rules(name);\n `);\n },\n};\n\nexport default migration;\n","/**\n * Migration v2: Add status column to vulnerabilities table\n *\n * Adds a `status` column to track the verification state of vulnerabilities.\n * Values: 'unverified' (default), 'confirmed', 'false_positive', 'not_exploitable'\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 2,\n description: 'Add status column to vulnerabilities table',\n up(db: Database.Database): void {\n db.exec(`\n ALTER TABLE vulnerabilities ADD COLUMN status TEXT NOT NULL DEFAULT 'unverified';\n `);\n },\n};\n\nexport default migration;\n","/**\n * Migration v3: Add technique_docs table with FTS5 full-text search\n *\n * Creates a technique documentation table for indexing penetration testing\n * knowledge (e.g. HackTricks) and a FTS5 virtual table for efficient search.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 3,\n description: 'Add technique_docs table with FTS5 full-text search',\n up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS technique_docs (\n id TEXT PRIMARY KEY,\n source TEXT NOT NULL,\n file_path TEXT NOT NULL,\n title TEXT NOT NULL,\n category TEXT NOT NULL,\n content TEXT NOT NULL,\n chunk_index INTEGER NOT NULL,\n indexed_at TEXT NOT NULL\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS technique_docs_fts USING fts5(\n title, category, content,\n content=technique_docs,\n content_rowid=rowid,\n tokenize='porter unicode61'\n );\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_ai AFTER INSERT ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(rowid, title, category, content)\n VALUES (new.rowid, new.title, new.category, new.content);\n END;\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_ad AFTER DELETE ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(technique_docs_fts, rowid, title, category, content)\n VALUES ('delete', old.rowid, old.title, old.category, old.content);\n END;\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_au AFTER UPDATE ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(technique_docs_fts, rowid, title, category, content)\n VALUES ('delete', old.rowid, old.title, old.category, old.content);\n INSERT INTO technique_docs_fts(rowid, title, category, content)\n VALUES (new.rowid, new.title, new.category, new.content);\n END;\n `);\n },\n};\n\nexport default migration;\n","/**\r\n * Migration v4: Graph-native schema\r\n *\r\n * 12 エンティティテーブル → nodes + edges の 2 テーブルに移行。\r\n * 既存データをマイグレーションし、旧テーブルを DROP する。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Migration } from './index.js';\r\nimport { randomUUID } from 'node:crypto';\r\n\r\nconst migration: Migration = {\r\n version: 4,\r\n description: 'Graph-native schema: nodes + edges tables',\r\n up(db: Database.Database): void {\r\n // --------------------------------------------------\r\n // 1. nodes + edges テーブル作成\r\n // --------------------------------------------------\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS nodes (\r\n id TEXT PRIMARY KEY,\r\n kind TEXT NOT NULL,\r\n natural_key TEXT NOT NULL UNIQUE,\r\n props_json TEXT NOT NULL DEFAULT '{}',\r\n evidence_artifact_id TEXT REFERENCES artifacts(id),\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);\r\n CREATE INDEX IF NOT EXISTS idx_nodes_evidence ON nodes(evidence_artifact_id);\r\n\r\n CREATE TABLE IF NOT EXISTS edges (\r\n id TEXT PRIMARY KEY,\r\n kind TEXT NOT NULL,\r\n source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\r\n target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\r\n props_json TEXT NOT NULL DEFAULT '{}',\r\n evidence_artifact_id TEXT REFERENCES artifacts(id),\r\n created_at TEXT NOT NULL,\r\n UNIQUE(kind, source_id, target_id)\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\r\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\r\n CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);\r\n `);\r\n\r\n // --------------------------------------------------\r\n // 2. 既存データのマイグレーション\r\n // --------------------------------------------------\r\n\r\n // ヘルパー: edge 挿入\r\n const insertEdge = db.prepare(`\r\n INSERT INTO edges (id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at)\r\n VALUES (?, ?, ?, ?, '{}', ?, ?)\r\n `);\r\n\r\n const insertNode = db.prepare(`\r\n INSERT INTO nodes (id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n // テーブル存在チェック\r\n const tableExists = (name: string): boolean => {\r\n const row = db\r\n .prepare(\"SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name=?\")\r\n .get(name) as { cnt: number };\r\n return row.cnt > 0;\r\n };\r\n\r\n // 2a. hosts → nodes (kind=\"host\")\r\n if (tableExists('hosts')) {\r\n const hosts = db.prepare('SELECT * FROM hosts').all() as Array<Record<string, unknown>>;\r\n for (const h of hosts) {\r\n const props = JSON.stringify({\r\n authorityKind: h.authority_kind,\r\n authority: h.authority,\r\n resolvedIpsJson: h.resolved_ips_json ?? '[]',\r\n });\r\n insertNode.run(\r\n h.id,\r\n 'host',\r\n `host:${h.authority}`,\r\n props,\r\n null, // hosts didn't have evidence_artifact_id\r\n h.created_at,\r\n h.updated_at,\r\n );\r\n }\r\n }\r\n\r\n // 2b. vhosts → nodes (kind=\"vhost\") + edge (HOST_VHOST)\r\n if (tableExists('vhosts')) {\r\n const vhosts = db.prepare('SELECT * FROM vhosts').all() as Array<Record<string, unknown>>;\r\n for (const v of vhosts) {\r\n const props = JSON.stringify({\r\n hostname: v.hostname,\r\n source: v.source ?? undefined,\r\n });\r\n insertNode.run(\r\n v.id,\r\n 'vhost',\r\n `vhost:${v.host_id}:${v.hostname}`,\r\n props,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n v.created_at, // vhosts had no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'HOST_VHOST',\r\n v.host_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2c. services → nodes (kind=\"service\") + edge (HOST_SERVICE)\r\n if (tableExists('services')) {\r\n const services = db.prepare('SELECT * FROM services').all() as Array<Record<string, unknown>>;\r\n for (const s of services) {\r\n const props = JSON.stringify({\r\n transport: s.transport,\r\n port: s.port,\r\n appProto: s.app_proto,\r\n protoConfidence: s.proto_confidence,\r\n banner: s.banner ?? undefined,\r\n product: s.product ?? undefined,\r\n version: s.version ?? undefined,\r\n state: s.state,\r\n });\r\n insertNode.run(\r\n s.id,\r\n 'service',\r\n `svc:${s.host_id}:${s.transport}:${s.port}`,\r\n props,\r\n s.evidence_artifact_id,\r\n s.created_at,\r\n s.updated_at,\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'HOST_SERVICE',\r\n s.host_id,\r\n s.id,\r\n s.evidence_artifact_id,\r\n s.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2d. service_observations → nodes (kind=\"svc_observation\") + edge (SERVICE_OBSERVATION)\r\n if (tableExists('service_observations')) {\r\n const svcObs = db.prepare('SELECT * FROM service_observations').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const so of svcObs) {\r\n const props = JSON.stringify({\r\n key: so.key,\r\n value: so.value,\r\n confidence: so.confidence,\r\n });\r\n insertNode.run(\r\n so.id,\r\n 'svc_observation',\r\n `svcobs:${so.id}`,\r\n props,\r\n so.evidence_artifact_id,\r\n so.created_at,\r\n so.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_OBSERVATION',\r\n so.service_id,\r\n so.id,\r\n so.evidence_artifact_id,\r\n so.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2e. http_endpoints → nodes (kind=\"endpoint\") + edge (SERVICE_ENDPOINT) + optional (VHOST_ENDPOINT)\r\n if (tableExists('http_endpoints')) {\r\n const endpoints = db.prepare('SELECT * FROM http_endpoints').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const ep of endpoints) {\r\n const props = JSON.stringify({\r\n baseUri: ep.base_uri,\r\n method: ep.method,\r\n path: ep.path,\r\n statusCode: ep.status_code ?? undefined,\r\n contentLength: ep.content_length ?? undefined,\r\n words: ep.words ?? undefined,\r\n lines: ep.lines ?? undefined,\r\n });\r\n insertNode.run(\r\n ep.id,\r\n 'endpoint',\r\n `ep:${ep.service_id}:${ep.method}:${ep.path}`,\r\n props,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n ep.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_ENDPOINT',\r\n ep.service_id,\r\n ep.id,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n );\r\n // Optional: VHOST_ENDPOINT\r\n if (ep.vhost_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'VHOST_ENDPOINT',\r\n ep.vhost_id,\r\n ep.id,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2f. inputs → nodes (kind=\"input\") + edge (SERVICE_INPUT)\r\n if (tableExists('inputs')) {\r\n const inputs = db.prepare('SELECT * FROM inputs').all() as Array<Record<string, unknown>>;\r\n for (const inp of inputs) {\r\n const props = JSON.stringify({\r\n location: inp.location,\r\n name: inp.name,\r\n typeHint: inp.type_hint ?? undefined,\r\n });\r\n insertNode.run(\r\n inp.id,\r\n 'input',\r\n `in:${inp.service_id}:${inp.location}:${inp.name}`,\r\n props,\r\n null, // inputs had no evidence_artifact_id\r\n inp.created_at,\r\n inp.updated_at,\r\n );\r\n insertEdge.run(randomUUID(), 'SERVICE_INPUT', inp.service_id, inp.id, null, inp.created_at);\r\n }\r\n }\r\n\r\n // 2g. endpoint_inputs → edges (ENDPOINT_INPUT)\r\n if (tableExists('endpoint_inputs')) {\r\n const epInputs = db.prepare('SELECT * FROM endpoint_inputs').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const ei of epInputs) {\r\n insertEdge.run(\r\n ei.id,\r\n 'ENDPOINT_INPUT',\r\n ei.endpoint_id,\r\n ei.input_id,\r\n ei.evidence_artifact_id,\r\n ei.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2h. observations → nodes (kind=\"observation\") + edge (INPUT_OBSERVATION)\r\n if (tableExists('observations')) {\r\n const obs = db.prepare('SELECT * FROM observations').all() as Array<Record<string, unknown>>;\r\n for (const o of obs) {\r\n const props = JSON.stringify({\r\n rawValue: o.raw_value,\r\n normValue: o.norm_value,\r\n bodyPath: o.body_path ?? undefined,\r\n source: o.source,\r\n confidence: o.confidence,\r\n observedAt: o.observed_at,\r\n });\r\n insertNode.run(\r\n o.id,\r\n 'observation',\r\n `obs:${o.id}`,\r\n props,\r\n o.evidence_artifact_id,\r\n o.observed_at,\r\n o.observed_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'INPUT_OBSERVATION',\r\n o.input_id,\r\n o.id,\r\n o.evidence_artifact_id,\r\n o.observed_at,\r\n );\r\n }\r\n }\r\n\r\n // 2i. credentials → nodes (kind=\"credential\") + edge (SERVICE_CREDENTIAL) + optional (ENDPOINT_CREDENTIAL)\r\n if (tableExists('credentials')) {\r\n const creds = db.prepare('SELECT * FROM credentials').all() as Array<Record<string, unknown>>;\r\n for (const c of creds) {\r\n const props = JSON.stringify({\r\n username: c.username,\r\n secret: c.secret,\r\n secretType: c.secret_type,\r\n source: c.source,\r\n confidence: c.confidence,\r\n });\r\n insertNode.run(\r\n c.id,\r\n 'credential',\r\n `cred:${c.id}`,\r\n props,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n c.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_CREDENTIAL',\r\n c.service_id,\r\n c.id,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n );\r\n if (c.endpoint_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'ENDPOINT_CREDENTIAL',\r\n c.endpoint_id,\r\n c.id,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2j. vulnerabilities → nodes (kind=\"vulnerability\") + edge (SERVICE_VULNERABILITY) + optional (ENDPOINT_VULNERABILITY)\r\n if (tableExists('vulnerabilities')) {\r\n const vulns = db.prepare('SELECT * FROM vulnerabilities').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const v of vulns) {\r\n const props = JSON.stringify({\r\n vulnType: v.vuln_type,\r\n title: v.title,\r\n description: v.description ?? undefined,\r\n severity: v.severity,\r\n confidence: v.confidence,\r\n status: v.status ?? 'unverified',\r\n });\r\n insertNode.run(\r\n v.id,\r\n 'vulnerability',\r\n `vuln:${v.id}`,\r\n props,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n v.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_VULNERABILITY',\r\n v.service_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n if (v.endpoint_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'ENDPOINT_VULNERABILITY',\r\n v.endpoint_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2k. cves → nodes (kind=\"cve\") + edge (VULNERABILITY_CVE)\r\n if (tableExists('cves')) {\r\n const cves = db.prepare('SELECT * FROM cves').all() as Array<Record<string, unknown>>;\r\n for (const c of cves) {\r\n const props = JSON.stringify({\r\n cveId: c.cve_id,\r\n description: c.description ?? undefined,\r\n cvssScore: c.cvss_score ?? undefined,\r\n cvssVector: c.cvss_vector ?? undefined,\r\n referenceUrl: c.reference_url ?? undefined,\r\n });\r\n insertNode.run(\r\n c.id,\r\n 'cve',\r\n `cve:${c.vulnerability_id}:${c.cve_id}`,\r\n props,\r\n null, // cves had no evidence_artifact_id\r\n c.created_at,\r\n c.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'VULNERABILITY_CVE',\r\n c.vulnerability_id,\r\n c.id,\r\n null,\r\n c.created_at,\r\n );\r\n }\r\n }\r\n\r\n // --------------------------------------------------\r\n // 3. 旧テーブル DROP\r\n // FK 依存順に削除(子テーブルから親テーブルへ)\r\n // --------------------------------------------------\r\n // FK を一時的に無効化して DROP\r\n db.pragma('foreign_keys = OFF');\r\n\r\n const tablesToDrop = [\r\n 'endpoint_inputs',\r\n 'observations',\r\n 'credentials',\r\n 'cves',\r\n 'vulnerabilities',\r\n 'http_endpoints',\r\n 'service_observations',\r\n 'inputs',\r\n 'services',\r\n 'vhosts',\r\n 'hosts',\r\n 'datalog_rules',\r\n ];\r\n\r\n for (const table of tablesToDrop) {\r\n if (tableExists(table)) {\r\n db.exec(`DROP TABLE ${table}`);\r\n }\r\n }\r\n\r\n // FK を再度有効化\r\n db.pragma('foreign_keys = ON');\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\r\n * Migration v5: file_mtime + 運用テーブル (Continuous STG Pentest)\r\n *\r\n * Part A: technique_docs\r\n * - Adds `file_mtime` TEXT column (NULL-able) for incremental indexing.\r\n * - Adds composite index on (source, file_path) for efficient mtime lookups.\r\n *\r\n * Part B: 7 新テーブル\r\n * - engagements, runs, action_queue, action_executions,\r\n * findings, finding_events, risk_snapshots\r\n *\r\n * Part C: scans/artifacts ALTER TABLE(リネージカラム追加)\r\n * - scans に engagement_id, run_id を追加\r\n * - artifacts に engagement_id, run_id, action_execution_id を追加\r\n *\r\n * Part D: 既存データの backfill\r\n * - 既存 scans がある場合、デフォルト engagement を作成\r\n * - scans.engagement_id と artifacts.engagement_id をデフォルト engagement で埋める\r\n *\r\n * See docs/v5-db-design.md for full design rationale.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport { randomUUID } from 'node:crypto';\r\nimport type { Migration } from './index.js';\r\n\r\nconst migration: Migration = {\r\n version: 5,\r\n description:\r\n 'Add file_mtime, operational tables (engagements, runs, action_queue, etc.), and lineage columns',\r\n up(db: Database.Database): void {\r\n // -------------------------------------------------------\r\n // Part A: technique_docs — file_mtime + 複合インデックス\r\n // -------------------------------------------------------\r\n db.exec(`\r\n ALTER TABLE technique_docs ADD COLUMN file_mtime TEXT;\r\n\r\n CREATE INDEX IF NOT EXISTS idx_technique_docs_source_filepath\r\n ON technique_docs(source, file_path);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part B: 運用テーブル — Continuous STG Pentest\r\n // -------------------------------------------------------\r\n\r\n // 1) engagements\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS engagements (\r\n id TEXT PRIMARY KEY,\r\n name TEXT NOT NULL,\r\n environment TEXT NOT NULL DEFAULT 'stg',\r\n scope_json TEXT NOT NULL DEFAULT '{}',\r\n policy_json TEXT NOT NULL DEFAULT '{}',\r\n schedule_cron TEXT,\r\n status TEXT NOT NULL DEFAULT 'active',\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_engagements_status ON engagements(status);\r\n `);\r\n\r\n // 2) runs\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS runs (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n trigger_kind TEXT NOT NULL,\r\n trigger_ref TEXT,\r\n status TEXT NOT NULL,\r\n started_at TEXT,\r\n finished_at TEXT,\r\n summary_json TEXT NOT NULL DEFAULT '{}',\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_runs_engagement_created ON runs(engagement_id, created_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);\r\n `);\r\n\r\n // 3) action_queue\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS action_queue (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n run_id TEXT,\r\n parent_action_id TEXT,\r\n kind TEXT NOT NULL,\r\n priority INTEGER NOT NULL DEFAULT 100,\r\n dedupe_key TEXT NOT NULL,\r\n params_json TEXT NOT NULL DEFAULT '{}',\r\n state TEXT NOT NULL,\r\n attempt_count INTEGER NOT NULL DEFAULT 0,\r\n max_attempts INTEGER NOT NULL DEFAULT 3,\r\n available_at TEXT NOT NULL,\r\n lease_owner TEXT,\r\n lease_expires_at TEXT,\r\n last_error TEXT,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (parent_action_id) REFERENCES action_queue(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_action_queue_poll\r\n ON action_queue(state, available_at, priority, created_at);\r\n CREATE INDEX IF NOT EXISTS idx_action_queue_engagement_state\r\n ON action_queue(engagement_id, state, created_at DESC);\r\n CREATE UNIQUE INDEX IF NOT EXISTS uq_action_queue_active_dedupe\r\n ON action_queue(engagement_id, dedupe_key)\r\n WHERE state IN ('queued', 'running');\r\n `);\r\n\r\n // 4) action_executions\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS action_executions (\r\n id TEXT PRIMARY KEY,\r\n action_id TEXT NOT NULL,\r\n run_id TEXT,\r\n executor TEXT NOT NULL,\r\n command TEXT,\r\n input_json TEXT NOT NULL DEFAULT '{}',\r\n output_json TEXT NOT NULL DEFAULT '{}',\r\n stdout_artifact_id TEXT,\r\n stderr_artifact_id TEXT,\r\n exit_code INTEGER,\r\n error_type TEXT,\r\n error_message TEXT,\r\n started_at TEXT NOT NULL,\r\n finished_at TEXT,\r\n duration_ms INTEGER,\r\n FOREIGN KEY (action_id) REFERENCES action_queue(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (stdout_artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL,\r\n FOREIGN KEY (stderr_artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_action_exec_action_started ON action_executions(action_id, started_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_action_exec_run_started ON action_executions(run_id, started_at DESC);\r\n `);\r\n\r\n // 5) findings\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS findings (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n canonical_key TEXT NOT NULL,\r\n node_id TEXT,\r\n title TEXT NOT NULL,\r\n severity TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n state TEXT NOT NULL,\r\n state_reason TEXT,\r\n owner TEXT,\r\n ticket_ref TEXT,\r\n first_seen_run_id TEXT,\r\n last_seen_run_id TEXT,\r\n first_seen_at TEXT NOT NULL,\r\n last_seen_at TEXT NOT NULL,\r\n sla_due_at TEXT,\r\n attrs_json TEXT NOT NULL DEFAULT '{}',\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE SET NULL,\r\n FOREIGN KEY (first_seen_run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (last_seen_run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n UNIQUE (engagement_id, canonical_key)\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_findings_engagement_state_sev\r\n ON findings(engagement_id, state, severity, last_seen_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_findings_node ON findings(node_id);\r\n `);\r\n\r\n // 6) finding_events\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS finding_events (\r\n id TEXT PRIMARY KEY,\r\n finding_id TEXT NOT NULL,\r\n run_id TEXT,\r\n event_type TEXT NOT NULL,\r\n before_json TEXT NOT NULL DEFAULT '{}',\r\n after_json TEXT NOT NULL DEFAULT '{}',\r\n artifact_id TEXT,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (finding_id) REFERENCES findings(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_finding_events_finding_created\r\n ON finding_events(finding_id, created_at DESC);\r\n `);\r\n\r\n // 7) risk_snapshots\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS risk_snapshots (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n run_id TEXT,\r\n score REAL NOT NULL,\r\n open_critical INTEGER NOT NULL DEFAULT 0,\r\n open_high INTEGER NOT NULL DEFAULT 0,\r\n open_medium INTEGER NOT NULL DEFAULT 0,\r\n open_low INTEGER NOT NULL DEFAULT 0,\r\n open_info INTEGER NOT NULL DEFAULT 0,\r\n open_total INTEGER NOT NULL DEFAULT 0,\r\n attack_path_count INTEGER NOT NULL DEFAULT 0,\r\n exposed_cred_count INTEGER NOT NULL DEFAULT 0,\r\n model_version TEXT,\r\n attrs_json TEXT NOT NULL DEFAULT '{}',\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_risk_snapshots_engagement_created\r\n ON risk_snapshots(engagement_id, created_at DESC);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part C: 既存テーブルへのリネージカラム追加\r\n // -------------------------------------------------------\r\n\r\n // scans に engagement_id, run_id を追加\r\n // NOTE: SQLite の ALTER TABLE は REFERENCES 句を無視するが、設計意図として記載\r\n db.exec(`\r\n ALTER TABLE scans ADD COLUMN engagement_id TEXT; -- REFERENCES engagements(id) ON DELETE SET NULL\r\n ALTER TABLE scans ADD COLUMN run_id TEXT; -- REFERENCES runs(id) ON DELETE SET NULL\r\n CREATE INDEX IF NOT EXISTS idx_scans_engagement_started ON scans(engagement_id, started_at DESC);\r\n `);\r\n\r\n // artifacts に engagement_id, run_id, action_execution_id を追加\r\n db.exec(`\r\n ALTER TABLE artifacts ADD COLUMN engagement_id TEXT; -- REFERENCES engagements(id) ON DELETE SET NULL\r\n ALTER TABLE artifacts ADD COLUMN run_id TEXT; -- REFERENCES runs(id) ON DELETE SET NULL\r\n ALTER TABLE artifacts ADD COLUMN action_execution_id TEXT; -- REFERENCES action_executions(id) ON DELETE SET NULL\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_engagement_captured ON artifacts(engagement_id, captured_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_run_captured ON artifacts(run_id, captured_at DESC);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part D: 既存データの backfill\r\n // -------------------------------------------------------\r\n const scanCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM scans').get() as { cnt: number }\r\n ).cnt;\r\n\r\n if (scanCount > 0) {\r\n const now = new Date().toISOString();\r\n const defaultEngId = randomUUID();\r\n\r\n // デフォルト engagement を作成\r\n db.prepare(\r\n `INSERT INTO engagements (id, name, environment, scope_json, policy_json, status, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\r\n ).run(defaultEngId, 'default', 'stg', '{}', '{}', 'active', now, now);\r\n\r\n // scans.engagement_id をデフォルト engagement で埋める\r\n db.prepare('UPDATE scans SET engagement_id = ? WHERE engagement_id IS NULL').run(\r\n defaultEngId,\r\n );\r\n\r\n // artifacts.engagement_id をデフォルト engagement で埋める\r\n db.prepare('UPDATE artifacts SET engagement_id = ? WHERE engagement_id IS NULL').run(\r\n defaultEngId,\r\n );\r\n }\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\r\n * sonobat — Database migration registry\r\n *\r\n * Manages versioned migrations using SQLite's PRAGMA user_version.\r\n * Each migration has a version number and an up() function.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport v0 from './v0.js';\r\nimport v1 from './v1.js';\r\nimport v2 from './v2.js';\r\nimport v3 from './v3.js';\r\nimport v4 from './v4.js';\r\nimport v5 from './v5.js';\r\n\r\nexport interface Migration {\r\n version: number;\r\n description: string;\r\n up(db: Database.Database): void;\r\n}\r\n\r\n/** All migrations in order. Must be sorted by version ascending. */\r\nconst migrations: Migration[] = [v0, v1, v2, v3, v4, v5];\r\n\r\n/** The latest schema version (after all migrations applied). */\r\nexport const LATEST_VERSION: number =\r\n migrations.length > 0 ? migrations[migrations.length - 1].version : 0;\r\n\r\n/**\r\n * Get the current schema version from the database.\r\n */\r\nexport function getSchemaVersion(db: Database.Database): number {\r\n const row = db.prepare('PRAGMA user_version').get() as {\r\n user_version: number;\r\n };\r\n return row.user_version;\r\n}\r\n\r\n/**\r\n * Set the schema version in the database.\r\n */\r\nexport function setSchemaVersion(db: Database.Database, version: number): void {\r\n db.pragma(`user_version = ${version}`);\r\n}\r\n\r\n/**\r\n * Run all pending migrations from currentVersion to LATEST_VERSION.\r\n * Each migration runs inside a transaction for safety.\r\n * Schema version is updated after each successful migration so that\r\n * a failure mid-sequence leaves the DB at the last completed version.\r\n */\r\nexport function runMigrations(db: Database.Database, currentVersion: number): void {\r\n for (const migration of migrations) {\r\n if (migration.version > currentVersion) {\r\n const runMigration = db.transaction(() => {\r\n migration.up(db);\r\n });\r\n runMigration();\r\n setSchemaVersion(db, migration.version);\r\n }\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport { getSchemaVersion, runMigrations, LATEST_VERSION } from './migrations/index.js';\r\n\r\n/**\r\n * Migrate the database to the latest schema version.\r\n *\r\n * - New database (user_version = 0, no tables): runs ALL migrations from v0.\r\n * - Existing database (user_version = 0, has tables): runs incremental migrations from v1.\r\n * - Partially migrated: runs remaining migrations.\r\n * - Already up-to-date (user_version = LATEST_VERSION): no-op.\r\n */\r\nexport function migrateDatabase(db: Database.Database): void {\r\n db.pragma('foreign_keys = ON');\r\n\r\n const currentVersion = getSchemaVersion(db);\r\n\r\n if (currentVersion >= LATEST_VERSION) {\r\n return;\r\n }\r\n\r\n if (currentVersion === 0) {\r\n // Check if this is a truly new DB or an existing v0 DB\r\n const tableCount = (\r\n db\r\n .prepare(\r\n \"SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\r\n )\r\n .get() as { cnt: number }\r\n ).cnt;\r\n\r\n if (tableCount === 0) {\r\n // Brand new database: run ALL migrations including v0 (base schema)\r\n runMigrations(db, -1);\r\n return;\r\n }\r\n\r\n // Existing v0 database: run incremental migrations from v1\r\n runMigrations(db, 0);\r\n return;\r\n }\r\n\r\n // Partially migrated: run remaining migrations\r\n runMigrations(db, currentVersion);\r\n}\r\n","/**\r\n * sonobat — MCP Server\r\n *\r\n * Creates and configures the MCP server with all tools and resources.\r\n */\r\n\r\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { registerQueryTool } from './tools/query.js';\r\nimport { registerMutateTool } from './tools/mutate.js';\r\nimport { registerIngestTool } from './tools/ingest.js';\r\nimport { registerProposeTool } from './tools/propose.js';\r\nimport { registerKbTools } from './tools/kb.js';\r\nimport { registerOpsTools } from './tools/ops.js';\r\nimport { registerFindingsTools } from './tools/findings.js';\r\nimport { registerResources } from './resources.js';\r\n\r\n/**\r\n * Create a fully configured MCP server with all sonobat tools and resources.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param version - Package version string (read from package.json by caller)\r\n * @returns Configured McpServer instance\r\n */\r\nexport function createMcpServer(db: Database.Database, version?: string): McpServer {\r\n const server = new McpServer({\r\n name: 'sonobat',\r\n version: version ?? '0.0.0',\r\n });\r\n\r\n // Register tools (8 tools total)\r\n registerQueryTool(server, db);\r\n registerMutateTool(server, db);\r\n registerIngestTool(server, db);\r\n registerProposeTool(server, db);\r\n registerKbTools(server, db); // search_kb + index_kb\r\n registerOpsTools(server, db); // ops (engagement/run/action management)\r\n registerFindingsTools(server, db); // findings (finding/risk management)\r\n\r\n // Register resources\r\n registerResources(server, db);\r\n\r\n return server;\r\n}\r\n","/**\r\n * sonobat — MCP Query Tool (unified)\r\n *\r\n * Single 'query' tool with an 'action' parameter that replaces\r\n * all previous read-only tools (list_hosts, get_host, list_services, etc.).\r\n *\r\n * Actions: list_nodes, get_node, traverse, summary, attack_paths\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { z } from 'zod';\r\nimport { NodeRepository } from '../../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../../db/repository/edge-repository.js';\r\nimport { GraphQueryRepository } from '../../db/repository/graph-query-repository.js';\r\nimport { NODE_KINDS, EDGE_KINDS } from '../../types/graph.js';\r\nimport type { NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\nexport function registerQueryTool(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n const graphQueryRepo = new GraphQueryRepository(db);\r\n\r\n server.tool(\r\n 'query',\r\n 'Query the AttackDataGraph. Actions: list_nodes, get_node, traverse, summary, attack_paths',\r\n {\r\n action: z.enum(['list_nodes', 'get_node', 'traverse', 'summary', 'attack_paths']),\r\n // Parameters for list_nodes\r\n kind: z.string().optional().describe('Node kind filter (host, service, endpoint, etc.)'),\r\n // Parameters for get_node\r\n id: z.string().optional().describe('Node ID'),\r\n // Parameters for traverse\r\n startId: z.string().optional().describe('Start node ID for traversal'),\r\n depth: z.number().optional().describe('Max traversal depth'),\r\n edgeKinds: z.array(z.string()).optional().describe('Edge kinds to follow'),\r\n // Parameters for attack_paths\r\n pattern: z.string().optional().describe('Preset pattern name'),\r\n // Common filters\r\n filtersJson: z\r\n .string()\r\n .optional()\r\n .describe('Filters as JSON object for list_nodes (JSON_EXTRACT on props)'),\r\n },\r\n async ({ action, kind, id, startId, depth, edgeKinds, pattern, filtersJson }) => {\r\n switch (action) {\r\n case 'list_nodes': {\r\n if (!kind) {\r\n return {\r\n content: [{ type: 'text', text: 'kind parameter required for list_nodes' }],\r\n isError: true,\r\n };\r\n }\r\n // Validate kind\r\n if (!NODE_KINDS.includes(kind as NodeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid kind: ${kind}. Valid: ${NODE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n const filters = filtersJson\r\n ? (JSON.parse(filtersJson) as Record<string, unknown>)\r\n : undefined;\r\n const nodes = nodeRepo.findByKind(kind as NodeKind, filters);\r\n const result = nodes.map((n) => ({ ...n, props: JSON.parse(n.propsJson) }));\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n case 'get_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter required for get_node' }],\r\n isError: true,\r\n };\r\n }\r\n const node = nodeRepo.findById(id);\r\n if (!node) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n // Get adjacent edges and nodes\r\n const outEdges = edgeRepo.findBySource(node.id);\r\n const inEdges = edgeRepo.findByTarget(node.id);\r\n const adjacentNodeIds = new Set([\r\n ...outEdges.map((e) => e.targetId),\r\n ...inEdges.map((e) => e.sourceId),\r\n ]);\r\n const adjacentNodes = [...adjacentNodeIds]\r\n .map((nid) => nodeRepo.findById(nid))\r\n .filter(Boolean);\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n outEdges,\r\n inEdges,\r\n adjacentNodes: adjacentNodes.map((n) => ({\r\n ...n!,\r\n props: JSON.parse(n!.propsJson),\r\n })),\r\n };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n case 'traverse': {\r\n if (!startId) {\r\n return {\r\n content: [{ type: 'text', text: 'startId parameter required for traverse' }],\r\n isError: true,\r\n };\r\n }\r\n const validEdgeKinds = edgeKinds?.filter((ek) => EDGE_KINDS.includes(ek as EdgeKind)) as\r\n | EdgeKind[]\r\n | undefined;\r\n const results = graphQueryRepo.traverse(startId, depth, validEdgeKinds);\r\n const enriched = results.map((r) => ({\r\n ...r,\r\n node: { ...r.node, props: JSON.parse(r.node.propsJson) },\r\n }));\r\n return { content: [{ type: 'text', text: JSON.stringify(enriched, null, 2) }] };\r\n }\r\n case 'summary': {\r\n // Count nodes by kind and edges by kind\r\n const nodeCounts: Record<string, number> = {};\r\n for (const k of NODE_KINDS) {\r\n nodeCounts[k] = nodeRepo.findByKind(k).length;\r\n }\r\n const edgeCounts: Record<string, number> = {};\r\n for (const ek of EDGE_KINDS) {\r\n edgeCounts[ek] = edgeRepo.findByKind(ek).length;\r\n }\r\n // Also count artifacts\r\n const artifactCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM artifacts').get() as { cnt: number }\r\n ).cnt;\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: JSON.stringify(\r\n { nodes: nodeCounts, edges: edgeCounts, artifacts: artifactCount },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n case 'attack_paths': {\r\n if (!pattern) {\r\n return {\r\n content: [{ type: 'text', text: 'pattern parameter required for attack_paths' }],\r\n isError: true,\r\n };\r\n }\r\n try {\r\n const results = graphQueryRepo.runPreset(pattern);\r\n return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n return {\r\n content: [{ type: 'text', text: `attack_paths error: ${message}` }],\r\n isError: true,\r\n };\r\n }\r\n }\r\n }\r\n },\r\n );\r\n}\r\n","/**\r\n * sonobat — NodeRepository\r\n *\r\n * Graph-native スキーマの nodes テーブルに対する CRUD 操作を提供する。\r\n * snake_case (DB) ↔ camelCase (TypeScript) の変換を内部で行う。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\nimport type { GraphNode, NodeKind } from '../../types/graph.js';\r\nimport { validateProps, buildNaturalKey } from '../../types/graph.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// DB row 型\r\n// ---------------------------------------------------------------------------\r\n\r\n/** better-sqlite3 から返る nodes テーブルの行形状 */\r\ninterface NodeRow {\r\n id: string;\r\n kind: string;\r\n natural_key: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n updated_at: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Row → GraphNode 変換\r\n// ---------------------------------------------------------------------------\r\n\r\n/** snake_case DB row を camelCase GraphNode にマッピング */\r\nfunction rowToGraphNode(row: NodeRow): GraphNode {\r\n return {\r\n id: row.id,\r\n kind: row.kind as NodeKind,\r\n naturalKey: row.natural_key,\r\n propsJson: row.props_json,\r\n ...(row.evidence_artifact_id !== null ? { evidenceArtifactId: row.evidence_artifact_id } : {}),\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// NodeRepository\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * nodes テーブルの CRUD リポジトリ。\r\n *\r\n * - props は Zod バリデーション後に JSON 文字列としてストア\r\n * - natural_key は buildNaturalKey() で自動生成\r\n * - ID は crypto.randomUUID() で生成\r\n */\r\nexport class NodeRepository {\r\n private readonly db: Database.Database;\r\n\r\n private readonly insertStmt: Database.Statement;\r\n private readonly selectByIdStmt: Database.Statement;\r\n private readonly selectByKindStmt: Database.Statement;\r\n private readonly selectByNaturalKeyStmt: Database.Statement;\r\n private readonly updatePropsStmt: Database.Statement;\r\n private readonly deleteStmt: Database.Statement;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n\r\n this.insertStmt = this.db.prepare(\r\n `INSERT INTO nodes (id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\r\n );\r\n\r\n this.selectByIdStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE id = ?`,\r\n );\r\n\r\n this.selectByKindStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE kind = ?`,\r\n );\r\n\r\n this.selectByNaturalKeyStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE natural_key = ?`,\r\n );\r\n\r\n this.updatePropsStmt = this.db.prepare(\r\n `UPDATE nodes SET props_json = ?, updated_at = ? WHERE id = ?`,\r\n );\r\n\r\n this.deleteStmt = this.db.prepare(`DELETE FROM nodes WHERE id = ?`);\r\n }\r\n\r\n /**\r\n * ノードを新規作成して返す。\r\n *\r\n * @param kind ノード種別\r\n * @param props ノードの props(Zod バリデーション対象)\r\n * @param evidenceArtifactId 証拠 artifact の ID(任意)\r\n * @param parentId 親ノード ID(service, endpoint 等で必要)\r\n * @throws props バリデーションエラー、または natural_key 重複時\r\n */\r\n create(\r\n kind: NodeKind,\r\n props: Record<string, unknown>,\r\n evidenceArtifactId?: string,\r\n parentId?: string,\r\n ): GraphNode {\r\n // props バリデーション\r\n const validation = validateProps(kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${kind}\": ${validation.error}`);\r\n }\r\n\r\n const id = crypto.randomUUID();\r\n const naturalKey = buildNaturalKey(kind, validation.data, parentId);\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n\r\n this.insertStmt.run(\r\n id,\r\n kind,\r\n naturalKey,\r\n propsJson,\r\n evidenceArtifactId ?? null,\r\n timestamp,\r\n timestamp,\r\n );\r\n\r\n return {\r\n id,\r\n kind,\r\n naturalKey,\r\n propsJson,\r\n ...(evidenceArtifactId !== undefined ? { evidenceArtifactId } : {}),\r\n createdAt: timestamp,\r\n updatedAt: timestamp,\r\n };\r\n }\r\n\r\n /**\r\n * Upsert: natural_key が存在すれば更新、なければ新規作成。\r\n *\r\n * UUID ベースの natural key (observation, credential, vulnerability, svc_observation) は\r\n * 毎回異なるキーが生成されるため、常に新規作成となる。\r\n *\r\n * @returns { node, created } — created は新規作成時 true、既存更新時 false\r\n */\r\n upsert(\r\n kind: NodeKind,\r\n props: Record<string, unknown>,\r\n evidenceArtifactId?: string,\r\n parentId?: string,\r\n ): { node: GraphNode; created: boolean } {\r\n // props バリデーション\r\n const validation = validateProps(kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${kind}\": ${validation.error}`);\r\n }\r\n\r\n const naturalKey = buildNaturalKey(kind, validation.data, parentId);\r\n\r\n // 既存ノードを natural_key で検索\r\n const existing = this.findByNaturalKey(naturalKey);\r\n\r\n if (existing !== undefined) {\r\n // 既存ノードの props を更新\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n this.updatePropsStmt.run(propsJson, timestamp, existing.id);\r\n\r\n const updated = this.findById(existing.id)!;\r\n return { node: updated, created: false };\r\n }\r\n\r\n // 新規作成\r\n const node = this.create(kind, props, evidenceArtifactId, parentId);\r\n return { node, created: true };\r\n }\r\n\r\n /**\r\n * ID でノードを取得する。存在しなければ undefined。\r\n */\r\n findById(id: string): GraphNode | undefined {\r\n const row = this.selectByIdStmt.get(id) as NodeRow | undefined;\r\n if (row === undefined) {\r\n return undefined;\r\n }\r\n return rowToGraphNode(row);\r\n }\r\n\r\n /**\r\n * kind でノード一覧を取得する。\r\n *\r\n * filters を指定すると、props_json の中身に対して JSON_EXTRACT で絞り込む。\r\n * 例: findByKind('host', { authority: '192.168.1.1' })\r\n * → WHERE kind = 'host' AND JSON_EXTRACT(props_json, '$.authority') = '192.168.1.1'\r\n */\r\n findByKind(kind: NodeKind, filters?: Record<string, unknown>): GraphNode[] {\r\n if (filters === undefined || Object.keys(filters).length === 0) {\r\n const rows = this.selectByKindStmt.all(kind) as NodeRow[];\r\n return rows.map(rowToGraphNode);\r\n }\r\n\r\n // 動的フィルタ構築(プリペアドステートメントで安全に)\r\n const filterKeys = Object.keys(filters);\r\n const whereClauses = filterKeys.map((_key) => `JSON_EXTRACT(props_json, '$.' || ?) = ?`);\r\n const sql = `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes\r\n WHERE kind = ? AND ${whereClauses.join(' AND ')}`;\r\n\r\n const params: unknown[] = [kind];\r\n for (const key of filterKeys) {\r\n params.push(key, filters[key]);\r\n }\r\n\r\n const rows = this.db.prepare(sql).all(...params) as NodeRow[];\r\n return rows.map(rowToGraphNode);\r\n }\r\n\r\n /**\r\n * natural_key でノードを取得する。存在しなければ undefined。\r\n */\r\n findByNaturalKey(naturalKey: string): GraphNode | undefined {\r\n const row = this.selectByNaturalKeyStmt.get(naturalKey) as NodeRow | undefined;\r\n if (row === undefined) {\r\n return undefined;\r\n }\r\n return rowToGraphNode(row);\r\n }\r\n\r\n /**\r\n * ノードの props を更新する。updated_at も自動更新される。\r\n *\r\n * @returns 更新後の GraphNode。id が存在しなければ undefined。\r\n * @throws props バリデーションエラー時\r\n */\r\n updateProps(id: string, props: Record<string, unknown>): GraphNode | undefined {\r\n // 既存ノードを取得して kind を確認\r\n const existing = this.findById(id);\r\n if (existing === undefined) {\r\n return undefined;\r\n }\r\n\r\n // props バリデーション\r\n const validation = validateProps(existing.kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${existing.kind}\": ${validation.error}`);\r\n }\r\n\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n\r\n this.updatePropsStmt.run(propsJson, timestamp, id);\r\n\r\n return this.findById(id);\r\n }\r\n\r\n /**\r\n * ノードを削除する。\r\n *\r\n * @returns 削除成功時 true、id が存在しない場合 false。\r\n */\r\n delete(id: string): boolean {\r\n const result = this.deleteStmt.run(id);\r\n return result.changes > 0;\r\n }\r\n}\r\n","/**\r\n * sonobat — Graph type system\r\n *\r\n * グラフネイティブスキーマの型定義。\r\n * NodeKind/EdgeKind 列挙、Zod props スキーマ、\r\n * GraphNode/GraphEdge インターフェース、natural key builder。\r\n */\r\n\r\nimport { z } from 'zod';\r\nimport { randomUUID } from 'node:crypto';\r\n\r\n// ============================================================\r\n// NodeKind / EdgeKind 列挙\r\n// ============================================================\r\n\r\nexport const NODE_KINDS = [\r\n 'host',\r\n 'vhost',\r\n 'service',\r\n 'endpoint',\r\n 'input',\r\n 'observation',\r\n 'credential',\r\n 'vulnerability',\r\n 'cve',\r\n 'svc_observation',\r\n] as const;\r\nexport type NodeKind = (typeof NODE_KINDS)[number];\r\n\r\nexport const EDGE_KINDS = [\r\n 'HOST_SERVICE',\r\n 'HOST_VHOST',\r\n 'SERVICE_ENDPOINT',\r\n 'SERVICE_INPUT',\r\n 'SERVICE_CREDENTIAL',\r\n 'SERVICE_VULNERABILITY',\r\n 'SERVICE_OBSERVATION',\r\n 'ENDPOINT_INPUT',\r\n 'ENDPOINT_VULNERABILITY',\r\n 'ENDPOINT_CREDENTIAL',\r\n 'INPUT_OBSERVATION',\r\n 'VULNERABILITY_CVE',\r\n 'VHOST_ENDPOINT',\r\n] as const;\r\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\r\n\r\n// ============================================================\r\n// Zod Props スキーマ\r\n// ============================================================\r\n\r\nexport const HostPropsSchema = z.object({\r\n authorityKind: z.enum(['IP', 'DOMAIN']),\r\n authority: z.string().min(1),\r\n resolvedIpsJson: z.string().default('[]'),\r\n});\r\nexport type HostProps = z.infer<typeof HostPropsSchema>;\r\n\r\nexport const VhostPropsSchema = z.object({\r\n hostname: z.string().min(1),\r\n source: z.string().optional(),\r\n});\r\nexport type VhostProps = z.infer<typeof VhostPropsSchema>;\r\n\r\nexport const ServicePropsSchema = z.object({\r\n transport: z.string().min(1),\r\n port: z.number().int().nonnegative(),\r\n appProto: z.string().min(1),\r\n protoConfidence: z.string().min(1),\r\n banner: z.string().optional(),\r\n product: z.string().optional(),\r\n version: z.string().optional(),\r\n state: z.string().min(1),\r\n});\r\nexport type ServiceProps = z.infer<typeof ServicePropsSchema>;\r\n\r\nexport const EndpointPropsSchema = z.object({\r\n baseUri: z.string().min(1),\r\n method: z.string().min(1),\r\n path: z.string().min(1),\r\n statusCode: z.number().int().optional(),\r\n contentLength: z.number().int().optional(),\r\n words: z.number().int().optional(),\r\n lines: z.number().int().optional(),\r\n});\r\nexport type EndpointProps = z.infer<typeof EndpointPropsSchema>;\r\n\r\nexport const InputPropsSchema = z.object({\r\n location: z.string().min(1),\r\n name: z.string().min(1),\r\n typeHint: z.string().optional(),\r\n});\r\nexport type InputProps = z.infer<typeof InputPropsSchema>;\r\n\r\nexport const ObservationPropsSchema = z.object({\r\n rawValue: z.string(),\r\n normValue: z.string(),\r\n bodyPath: z.string().optional(),\r\n source: z.string().min(1),\r\n confidence: z.string().min(1),\r\n observedAt: z.string().min(1),\r\n});\r\nexport type ObservationProps = z.infer<typeof ObservationPropsSchema>;\r\n\r\nexport const CredentialPropsSchema = z.object({\r\n username: z.string(),\r\n secret: z.string(),\r\n secretType: z.string().min(1),\r\n source: z.string().min(1),\r\n confidence: z.string().min(1),\r\n});\r\nexport type CredentialProps = z.infer<typeof CredentialPropsSchema>;\r\n\r\nexport const VulnerabilityPropsSchema = z.object({\r\n vulnType: z.string().min(1),\r\n title: z.string().min(1),\r\n description: z.string().optional(),\r\n severity: z.string().min(1),\r\n confidence: z.string().min(1),\r\n status: z.string().min(1).default('unverified'),\r\n});\r\nexport type VulnerabilityProps = z.infer<typeof VulnerabilityPropsSchema>;\r\n\r\nexport const CvePropsSchema = z.object({\r\n cveId: z.string().min(1),\r\n description: z.string().optional(),\r\n cvssScore: z.number().optional(),\r\n cvssVector: z.string().optional(),\r\n referenceUrl: z.string().optional(),\r\n});\r\nexport type CveProps = z.infer<typeof CvePropsSchema>;\r\n\r\nexport const SvcObservationPropsSchema = z.object({\r\n key: z.string().min(1),\r\n value: z.string(),\r\n confidence: z.string().min(1),\r\n});\r\nexport type SvcObservationProps = z.infer<typeof SvcObservationPropsSchema>;\r\n\r\n/** NodeKind → Zod スキーマのマッピング */\r\nconst PROPS_SCHEMA_MAP: Record<NodeKind, z.ZodTypeAny> = {\r\n host: HostPropsSchema,\r\n vhost: VhostPropsSchema,\r\n service: ServicePropsSchema,\r\n endpoint: EndpointPropsSchema,\r\n input: InputPropsSchema,\r\n observation: ObservationPropsSchema,\r\n credential: CredentialPropsSchema,\r\n vulnerability: VulnerabilityPropsSchema,\r\n cve: CvePropsSchema,\r\n svc_observation: SvcObservationPropsSchema,\r\n};\r\n\r\n// ============================================================\r\n// GraphNode / GraphEdge インターフェース\r\n// ============================================================\r\n\r\nexport interface GraphNode {\r\n id: string;\r\n kind: NodeKind;\r\n naturalKey: string;\r\n propsJson: string;\r\n evidenceArtifactId?: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\nexport interface GraphEdge {\r\n id: string;\r\n kind: EdgeKind;\r\n sourceId: string;\r\n targetId: string;\r\n propsJson: string;\r\n evidenceArtifactId?: string;\r\n createdAt: string;\r\n}\r\n\r\n// ============================================================\r\n// validateProps\r\n// ============================================================\r\n\r\nexport type ValidateResult = { ok: true; data: unknown } | { ok: false; error: string };\r\n\r\n/**\r\n * 指定された NodeKind に対する props のバリデーション。\r\n */\r\nexport function validateProps(kind: NodeKind, props: unknown): ValidateResult {\r\n const schema = PROPS_SCHEMA_MAP[kind];\r\n const result = schema.safeParse(props);\r\n if (result.success) {\r\n return { ok: true, data: result.data };\r\n }\r\n return { ok: false, error: result.error.message };\r\n}\r\n\r\n// ============================================================\r\n// buildNaturalKey\r\n// ============================================================\r\n\r\n/**\r\n * ノード種別と props から自然キーを構築する。\r\n *\r\n * 決定的なキーが生成できるノード (host, vhost, service, endpoint, input, cve) は\r\n * 常に同じ入力に対して同じキーを返す。\r\n *\r\n * 一意性が保証できないノード (observation, credential, vulnerability, svc_observation)\r\n * は UUID ベースのキーを生成する。\r\n *\r\n * @param kind ノード種別\r\n * @param props ノードの props(部分的でも可)\r\n * @param parentId 親ノードの ID(vhost, service, endpoint, input, cve で必須)\r\n */\r\nexport function buildNaturalKey(kind: NodeKind, props: unknown, parentId?: string): string {\r\n const p = props as Record<string, unknown>;\r\n\r\n switch (kind) {\r\n case 'host':\r\n return `host:${p.authority}`;\r\n\r\n case 'vhost':\r\n return `vhost:${parentId}:${p.hostname}`;\r\n\r\n case 'service':\r\n return `svc:${parentId}:${p.transport}:${p.port}`;\r\n\r\n case 'endpoint':\r\n return `ep:${parentId}:${p.method}:${p.path}`;\r\n\r\n case 'input':\r\n return `in:${parentId}:${p.location}:${p.name}`;\r\n\r\n case 'cve':\r\n return `cve:${parentId}:${p.cveId}`;\r\n\r\n case 'observation':\r\n return `obs:${randomUUID()}`;\r\n\r\n case 'credential':\r\n return `cred:${randomUUID()}`;\r\n\r\n case 'vulnerability':\r\n return `vuln:${randomUUID()}`;\r\n\r\n case 'svc_observation':\r\n return `svcobs:${randomUUID()}`;\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\nimport type { EdgeKind, GraphEdge } from '../../types/graph.js';\r\n\r\n/** Row shape returned by better-sqlite3 for the edges table. */\r\ninterface EdgeRow {\r\n id: string;\r\n kind: string;\r\n source_id: string;\r\n target_id: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n}\r\n\r\n/** Maps a snake_case DB row to a camelCase GraphEdge entity. */\r\nfunction rowToEdge(row: EdgeRow): GraphEdge {\r\n return {\r\n id: row.id,\r\n kind: row.kind as EdgeKind,\r\n sourceId: row.source_id,\r\n targetId: row.target_id,\r\n propsJson: row.props_json,\r\n ...(row.evidence_artifact_id !== null ? { evidenceArtifactId: row.evidence_artifact_id } : {}),\r\n createdAt: row.created_at,\r\n };\r\n}\r\n\r\n/**\r\n * Repository for the `edges` table.\r\n *\r\n * Provides CRUD operations with camelCase <-> snake_case mapping\r\n * between the TypeScript entity layer and the SQLite storage layer.\r\n */\r\nexport class EdgeRepository {\r\n private readonly db: Database.Database;\r\n\r\n private readonly insertStmt: Database.Statement;\r\n private readonly selectByCompositeKeyStmt: Database.Statement;\r\n private readonly selectBySourceStmt: Database.Statement;\r\n private readonly selectBySourceAndKindStmt: Database.Statement;\r\n private readonly selectByTargetStmt: Database.Statement;\r\n private readonly selectByTargetAndKindStmt: Database.Statement;\r\n private readonly selectByKindStmt: Database.Statement;\r\n private readonly deleteStmt: Database.Statement;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n\r\n this.insertStmt = this.db.prepare(\r\n 'INSERT INTO edges (id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)',\r\n );\r\n\r\n this.selectByCompositeKeyStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE kind = ? AND source_id = ? AND target_id = ?',\r\n );\r\n\r\n this.selectBySourceStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE source_id = ?',\r\n );\r\n\r\n this.selectBySourceAndKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE source_id = ? AND kind = ?',\r\n );\r\n\r\n this.selectByTargetStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE target_id = ?',\r\n );\r\n\r\n this.selectByTargetAndKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE target_id = ? AND kind = ?',\r\n );\r\n\r\n this.selectByKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE kind = ?',\r\n );\r\n\r\n this.deleteStmt = this.db.prepare('DELETE FROM edges WHERE id = ?');\r\n }\r\n\r\n /**\r\n * Create a new edge linking two nodes.\r\n *\r\n * Throws if the (kind, source_id, target_id) combination already exists.\r\n */\r\n create(\r\n kind: EdgeKind,\r\n sourceId: string,\r\n targetId: string,\r\n evidenceArtifactId?: string,\r\n propsJson?: string,\r\n ): GraphEdge {\r\n const id = crypto.randomUUID();\r\n const createdAt = new Date().toISOString();\r\n const props = propsJson ?? '{}';\r\n\r\n this.insertStmt.run(id, kind, sourceId, targetId, props, evidenceArtifactId ?? null, createdAt);\r\n\r\n return {\r\n id,\r\n kind,\r\n sourceId,\r\n targetId,\r\n propsJson: props,\r\n ...(evidenceArtifactId !== undefined ? { evidenceArtifactId } : {}),\r\n createdAt,\r\n };\r\n }\r\n\r\n /**\r\n * Upsert an edge by (kind, source_id, target_id).\r\n *\r\n * If the combination already exists, returns the existing edge with `created: false`.\r\n * Otherwise creates a new edge and returns it with `created: true`.\r\n */\r\n upsert(\r\n kind: EdgeKind,\r\n sourceId: string,\r\n targetId: string,\r\n evidenceArtifactId?: string,\r\n propsJson?: string,\r\n ): { edge: GraphEdge; created: boolean } {\r\n const existing = this.selectByCompositeKeyStmt.get(kind, sourceId, targetId) as\r\n | EdgeRow\r\n | undefined;\r\n\r\n if (existing !== undefined) {\r\n return { edge: rowToEdge(existing), created: false };\r\n }\r\n\r\n const edge = this.create(kind, sourceId, targetId, evidenceArtifactId, propsJson);\r\n return { edge, created: true };\r\n }\r\n\r\n /**\r\n * Find all edges originating from a source node.\r\n *\r\n * Optionally filter by edge kind.\r\n */\r\n findBySource(sourceId: string, edgeKind?: EdgeKind): GraphEdge[] {\r\n if (edgeKind !== undefined) {\r\n const rows = this.selectBySourceAndKindStmt.all(sourceId, edgeKind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n const rows = this.selectBySourceStmt.all(sourceId) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Find all edges targeting a node.\r\n *\r\n * Optionally filter by edge kind.\r\n */\r\n findByTarget(targetId: string, edgeKind?: EdgeKind): GraphEdge[] {\r\n if (edgeKind !== undefined) {\r\n const rows = this.selectByTargetAndKindStmt.all(targetId, edgeKind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n const rows = this.selectByTargetStmt.all(targetId) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Find all edges of a specific kind.\r\n */\r\n findByKind(kind: EdgeKind): GraphEdge[] {\r\n const rows = this.selectByKindStmt.all(kind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Delete an edge by ID.\r\n *\r\n * Returns true if the edge was found and deleted, false otherwise.\r\n */\r\n delete(id: string): boolean {\r\n const result = this.deleteStmt.run(id);\r\n return result.changes > 0;\r\n }\r\n}\r\n","/**\r\n * sonobat — GraphQueryRepository\r\n *\r\n * グラフ走査・パス探索・プリセットクエリを提供するリポジトリ。\r\n * WITH RECURSIVE CTE を活用した SQLite ネイティブなグラフ操作。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { GraphNode, GraphEdge, NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Row → Entity マッピング\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface NodeRow {\r\n id: string;\r\n kind: string;\r\n natural_key: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n updated_at: string;\r\n}\r\n\r\ninterface EdgeRow {\r\n id: string;\r\n kind: string;\r\n source_id: string;\r\n target_id: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n}\r\n\r\nfunction rowToNode(row: NodeRow): GraphNode {\r\n return {\r\n id: row.id,\r\n kind: row.kind as NodeKind,\r\n naturalKey: row.natural_key,\r\n propsJson: row.props_json,\r\n evidenceArtifactId: row.evidence_artifact_id ?? undefined,\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nfunction rowToEdge(row: EdgeRow): GraphEdge {\r\n return {\r\n id: row.id,\r\n kind: row.kind as EdgeKind,\r\n sourceId: row.source_id,\r\n targetId: row.target_id,\r\n propsJson: row.props_json,\r\n evidenceArtifactId: row.evidence_artifact_id ?? undefined,\r\n createdAt: row.created_at,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 公開型\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TraversalResult {\r\n node: GraphNode;\r\n depth: number;\r\n path: string[]; // node IDs from start to this node\r\n}\r\n\r\nexport interface PathResult {\r\n nodes: GraphNode[];\r\n edges: GraphEdge[];\r\n length: number;\r\n}\r\n\r\nexport type PresetResult = Record<string, unknown>[];\r\n\r\n// ---------------------------------------------------------------------------\r\n// GraphQueryRepository\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * グラフ走査・パス探索・プリセットクエリを提供するリポジトリ。\r\n *\r\n * すべてのクエリはプリペアドステートメントを使用し、SQL インジェクションを防止。\r\n */\r\nexport class GraphQueryRepository {\r\n private readonly db: Database.Database;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n }\r\n\r\n /**\r\n * 開始ノードから有向エッジを辿り、到達可能なノードを幅優先で返す。\r\n *\r\n * @param startId 開始ノード ID\r\n * @param maxDepth 最大探索深度(デフォルト: 10)\r\n * @param edgeKinds 辿るエッジ種別のフィルタ(省略時は全種別)\r\n * @returns 到達可能なノードのリスト(開始ノード自身は含まない)\r\n */\r\n traverse(startId: string, maxDepth?: number, edgeKinds?: EdgeKind[]): TraversalResult[] {\r\n const depth = maxDepth ?? 10;\r\n\r\n // 開始ノードの存在確認\r\n const startNode = this.db\r\n .prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?')\r\n .get(startId);\r\n if (!startNode) {\r\n return [];\r\n }\r\n\r\n // edgeKinds フィルタ用の条件構築\r\n let edgeFilter = '';\r\n const params: unknown[] = [startId, startId, depth];\r\n\r\n if (edgeKinds && edgeKinds.length > 0) {\r\n const placeholders = edgeKinds.map(() => '?').join(', ');\r\n edgeFilter = `AND e.kind IN (${placeholders})`;\r\n params.push(...edgeKinds);\r\n }\r\n\r\n const sql = `\r\n WITH RECURSIVE graph_walk(node_id, depth, path) AS (\r\n SELECT ?, 0, ?\r\n UNION ALL\r\n SELECT e.target_id, gw.depth + 1, gw.path || ',' || e.target_id\r\n FROM graph_walk gw\r\n JOIN edges e ON e.source_id = gw.node_id\r\n WHERE gw.depth < ?\r\n ${edgeFilter}\r\n AND instr(',' || gw.path || ',', ',' || e.target_id || ',') = 0\r\n )\r\n SELECT DISTINCT\r\n n.id, n.kind, n.natural_key, n.props_json,\r\n n.evidence_artifact_id, n.created_at, n.updated_at,\r\n gw.depth AS walk_depth,\r\n gw.path AS walk_path\r\n FROM graph_walk gw\r\n JOIN nodes n ON n.id = gw.node_id\r\n WHERE gw.depth > 0\r\n ORDER BY gw.depth ASC\r\n `;\r\n\r\n const stmt = this.db.prepare(sql);\r\n const rows = stmt.all(...params) as Array<NodeRow & { walk_depth: number; walk_path: string }>;\r\n\r\n // 同一ノードが複数パスで到達可能な場合、最短パスのみ保持\r\n const seen = new Map<string, TraversalResult>();\r\n\r\n for (const row of rows) {\r\n if (!seen.has(row.id) || seen.get(row.id)!.depth > row.walk_depth) {\r\n seen.set(row.id, {\r\n node: rowToNode(row),\r\n depth: row.walk_depth,\r\n path: row.walk_path.split(','),\r\n });\r\n }\r\n }\r\n\r\n return Array.from(seen.values()).sort((a, b) => a.depth - b.depth);\r\n }\r\n\r\n /**\r\n * 開始ノードから有向エッジを辿り、到達可能な全ノードを返す。\r\n *\r\n * @param nodeId 開始ノード ID\r\n * @param targetKind フィルタ対象のノード種別(省略時は全種別)\r\n * @returns 到達可能なノードのリスト\r\n */\r\n reachableFrom(nodeId: string, targetKind?: NodeKind): GraphNode[] {\r\n // BFS を TypeScript 側で実装する\r\n const visited = new Set<string>();\r\n const queue: string[] = [nodeId];\r\n visited.add(nodeId);\r\n\r\n const results: GraphNode[] = [];\r\n\r\n const edgeStmt = this.db.prepare<[string], EdgeRow>('SELECT * FROM edges WHERE source_id = ?');\r\n const nodeStmt = this.db.prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?');\r\n\r\n while (queue.length > 0) {\r\n const currentId = queue.shift()!;\r\n const edges = edgeStmt.all(currentId);\r\n\r\n for (const edge of edges) {\r\n if (!visited.has(edge.target_id)) {\r\n visited.add(edge.target_id);\r\n queue.push(edge.target_id);\r\n\r\n const targetNode = nodeStmt.get(edge.target_id);\r\n if (targetNode) {\r\n const graphNode = rowToNode(targetNode);\r\n if (targetKind === undefined || graphNode.kind === targetKind) {\r\n results.push(graphNode);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * 2 ノード間の最短パスを BFS で探索する。\r\n *\r\n * @param sourceId 起点ノード ID\r\n * @param targetId 終点ノード ID\r\n * @returns パス情報。パスが存在しない場合は undefined\r\n */\r\n shortestPath(sourceId: string, targetId: string): PathResult | undefined {\r\n const nodeStmt = this.db.prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?');\r\n\r\n // 同一ノードの場合\r\n if (sourceId === targetId) {\r\n const node = nodeStmt.get(sourceId);\r\n if (!node) {\r\n return undefined;\r\n }\r\n return {\r\n nodes: [rowToNode(node)],\r\n edges: [],\r\n length: 0,\r\n };\r\n }\r\n\r\n // BFS で最短パスを探索\r\n const edgeStmt = this.db.prepare<[string], EdgeRow>('SELECT * FROM edges WHERE source_id = ?');\r\n\r\n // parent[nodeId] = { parentId, edge }\r\n const parent = new Map<string, { parentId: string; edge: EdgeRow }>();\r\n const visited = new Set<string>();\r\n const queue: string[] = [sourceId];\r\n visited.add(sourceId);\r\n\r\n let found = false;\r\n\r\n while (queue.length > 0 && !found) {\r\n const currentId = queue.shift()!;\r\n const edges = edgeStmt.all(currentId);\r\n\r\n for (const edge of edges) {\r\n if (!visited.has(edge.target_id)) {\r\n visited.add(edge.target_id);\r\n parent.set(edge.target_id, { parentId: currentId, edge });\r\n if (edge.target_id === targetId) {\r\n found = true;\r\n break;\r\n }\r\n queue.push(edge.target_id);\r\n }\r\n }\r\n }\r\n\r\n if (!found) {\r\n return undefined;\r\n }\r\n\r\n // パスを逆順に再構築\r\n const pathNodeIds: string[] = [];\r\n const pathEdges: GraphEdge[] = [];\r\n\r\n let currentId = targetId;\r\n while (currentId !== sourceId) {\r\n pathNodeIds.unshift(currentId);\r\n const info = parent.get(currentId)!;\r\n pathEdges.unshift(rowToEdge(info.edge));\r\n currentId = info.parentId;\r\n }\r\n pathNodeIds.unshift(sourceId);\r\n\r\n // ノード情報を取得\r\n const nodes: GraphNode[] = [];\r\n for (const nid of pathNodeIds) {\r\n const row = nodeStmt.get(nid);\r\n if (row) {\r\n nodes.push(rowToNode(row));\r\n }\r\n }\r\n\r\n return {\r\n nodes,\r\n edges: pathEdges,\r\n length: pathEdges.length,\r\n };\r\n }\r\n\r\n /**\r\n * プリセットクエリを実行する。\r\n *\r\n * @param pattern プリセット名\r\n * @param params パラメータ(プリセットによって異なる)\r\n * @returns クエリ結果の配列\r\n */\r\n runPreset(pattern: string, params?: Record<string, unknown>): PresetResult {\r\n switch (pattern) {\r\n case 'attack_surface':\r\n return this.presetAttackSurface();\r\n case 'critical_vulns':\r\n return this.presetCriticalVulns();\r\n case 'credential_exposure':\r\n return this.presetCredentialExposure();\r\n case 'unscanned_services':\r\n return this.presetUnscannedServices();\r\n case 'vuln_by_host':\r\n return this.presetVulnByHost();\r\n case 'reachable_services':\r\n return this.presetReachableServices(params);\r\n default:\r\n throw new Error(`Unknown preset pattern: ${pattern}`);\r\n }\r\n }\r\n\r\n // =========================================================================\r\n // プリセットクエリ実装\r\n // =========================================================================\r\n\r\n /**\r\n * attack_surface: host → service → endpoint → input の完全パスを返す。\r\n * input がないエンドポイントも含む(inputId は null)。\r\n */\r\n private presetAttackSurface(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n ep.id AS endpoint_id,\r\n ep.props_json AS endpoint_props,\r\n inp.id AS input_id,\r\n inp.props_json AS input_props\r\n FROM nodes h\r\n JOIN edges e_hs ON e_hs.source_id = h.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n JOIN edges e_se ON e_se.source_id = s.id AND e_se.kind = 'SERVICE_ENDPOINT'\r\n JOIN nodes ep ON ep.id = e_se.target_id AND ep.kind = 'endpoint'\r\n LEFT JOIN edges e_ei ON e_ei.source_id = ep.id AND e_ei.kind = 'ENDPOINT_INPUT'\r\n LEFT JOIN nodes inp ON inp.id = e_ei.target_id AND inp.kind = 'input'\r\n WHERE h.kind = 'host'\r\n ORDER BY h.id, s.id, ep.id, inp.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n endpointId: row.endpoint_id,\r\n endpointProps: row.endpoint_props,\r\n inputId: row.input_id ?? null,\r\n inputProps: row.input_props ?? null,\r\n }));\r\n }\r\n\r\n /**\r\n * critical_vulns: severity が critical/high の脆弱性をホスト情報付きで返す。\r\n */\r\n private presetCriticalVulns(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n v.id AS vuln_id,\r\n v.props_json AS vuln_props,\r\n json_extract(v.props_json, '$.severity') AS severity,\r\n json_extract(v.props_json, '$.title') AS title\r\n FROM nodes v\r\n JOIN edges e_sv ON e_sv.target_id = v.id AND e_sv.kind = 'SERVICE_VULNERABILITY'\r\n JOIN nodes s ON s.id = e_sv.source_id AND s.kind = 'service'\r\n JOIN edges e_hs ON e_hs.target_id = s.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes h ON h.id = e_hs.source_id AND h.kind = 'host'\r\n WHERE v.kind = 'vulnerability'\r\n AND json_extract(v.props_json, '$.severity') IN ('critical', 'high')\r\n ORDER BY\r\n CASE json_extract(v.props_json, '$.severity')\r\n WHEN 'critical' THEN 0\r\n WHEN 'high' THEN 1\r\n END,\r\n h.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n vulnId: row.vuln_id,\r\n vulnProps: row.vuln_props,\r\n severity: row.severity,\r\n title: row.title,\r\n }));\r\n }\r\n\r\n /**\r\n * credential_exposure: service → credential の全マッピングを返す。\r\n */\r\n private presetCredentialExposure(): PresetResult {\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n c.id AS credential_id,\r\n c.props_json AS credential_props\r\n FROM nodes c\r\n JOIN edges e_sc ON e_sc.target_id = c.id AND e_sc.kind = 'SERVICE_CREDENTIAL'\r\n JOIN nodes s ON s.id = e_sc.source_id AND s.kind = 'service'\r\n WHERE c.kind = 'credential'\r\n ORDER BY s.id, c.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n credentialId: row.credential_id,\r\n credentialProps: row.credential_props,\r\n }));\r\n }\r\n\r\n /**\r\n * unscanned_services: endpoint が 0 件のサービスを返す。\r\n */\r\n private presetUnscannedServices(): PresetResult {\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n h.id AS host_id,\r\n h.props_json AS host_props\r\n FROM nodes s\r\n JOIN edges e_hs ON e_hs.target_id = s.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes h ON h.id = e_hs.source_id AND h.kind = 'host'\r\n LEFT JOIN edges e_se ON e_se.source_id = s.id AND e_se.kind = 'SERVICE_ENDPOINT'\r\n WHERE s.kind = 'service'\r\n AND e_se.id IS NULL\r\n ORDER BY h.id, s.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n }));\r\n }\r\n\r\n /**\r\n * vuln_by_host: ホスト別脆弱性カウントを返す。\r\n */\r\n private presetVulnByHost(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n COUNT(v.id) AS vuln_count\r\n FROM nodes h\r\n JOIN edges e_hs ON e_hs.source_id = h.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n JOIN edges e_sv ON e_sv.source_id = s.id AND e_sv.kind = 'SERVICE_VULNERABILITY'\r\n JOIN nodes v ON v.id = e_sv.target_id AND v.kind = 'vulnerability'\r\n WHERE h.kind = 'host'\r\n GROUP BY h.id\r\n ORDER BY vuln_count DESC\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n vulnCount: row.vuln_count,\r\n }));\r\n }\r\n\r\n /**\r\n * reachable_services: 指定 host から到達可能な全サービスを返す。\r\n */\r\n private presetReachableServices(params?: Record<string, unknown>): PresetResult {\r\n const hostId = params?.hostId as string | undefined;\r\n if (!hostId) {\r\n throw new Error('reachable_services preset requires hostId parameter');\r\n }\r\n\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n e_hs.kind AS edge_kind\r\n FROM edges e_hs\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n WHERE e_hs.source_id = ?\r\n AND e_hs.kind = 'HOST_SERVICE'\r\n ORDER BY s.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all(hostId) as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n edgeKind: row.edge_kind,\r\n }));\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Mutate Tool (unified)\r\n *\r\n * Single 'mutate' tool with an 'action' parameter that replaces\r\n * all previous mutation tools (add_host, add_credential, etc.).\r\n *\r\n * Actions: add_node, add_edge, update_node, delete_node\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { z } from 'zod';\r\nimport { NodeRepository } from '../../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../../db/repository/edge-repository.js';\r\nimport { NODE_KINDS, EDGE_KINDS, validateProps } from '../../types/graph.js';\r\nimport type { NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\n/**\r\n * Get or create a singleton \"manual\" artifact for manual data entry.\r\n * Reused across all manual mutations to avoid artifact proliferation.\r\n * Uses direct SQL since ArtifactRepository is being removed.\r\n */\r\nfunction getOrCreateManualArtifact(db: Database.Database): string {\r\n const row = db.prepare(\"SELECT id FROM artifacts WHERE tool = 'manual' LIMIT 1\").get() as\r\n | { id: string }\r\n | undefined;\r\n if (row) return row.id;\r\n\r\n const id = crypto.randomUUID();\r\n const now = new Date().toISOString();\r\n db.prepare(\r\n \"INSERT INTO artifacts (id, tool, kind, path, captured_at) VALUES (?, 'manual', 'manual_entry', 'manual', ?)\",\r\n ).run(id, now);\r\n return id;\r\n}\r\n\r\nexport function registerMutateTool(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n server.tool(\r\n 'mutate',\r\n 'Mutate the AttackDataGraph. Actions: add_node, add_edge, update_node, delete_node',\r\n {\r\n action: z.enum(['add_node', 'add_edge', 'update_node', 'delete_node']),\r\n // Parameters for add_node\r\n kind: z.string().optional().describe('Node kind (host, service, endpoint, etc.)'),\r\n propsJson: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Node properties as JSON string (e.g. {\"authorityKind\":\"IP\",\"authority\":\"10.0.0.1\",\"resolvedIpsJson\":\"[]\"})',\r\n ),\r\n parentId: z\r\n .string()\r\n .optional()\r\n .describe('Parent node ID (required for service, endpoint, input, vhost, cve)'),\r\n // Parameters for add_edge\r\n edgeKind: z.string().optional().describe('Edge kind (HOST_SERVICE, SERVICE_ENDPOINT, etc.)'),\r\n sourceId: z.string().optional().describe('Source node ID for edge'),\r\n targetId: z.string().optional().describe('Target node ID for edge'),\r\n // Parameters for update_node / delete_node\r\n id: z.string().optional().describe('Node ID for update or delete'),\r\n // Common optional\r\n evidenceArtifactId: z\r\n .string()\r\n .optional()\r\n .describe('Evidence artifact ID. If omitted, a \"manual\" artifact is auto-created/reused.'),\r\n },\r\n async ({\r\n action,\r\n kind,\r\n propsJson: propsJsonStr,\r\n parentId,\r\n edgeKind,\r\n sourceId,\r\n targetId,\r\n id,\r\n evidenceArtifactId,\r\n }) => {\r\n // Parse propsJson string into an object when provided\r\n let props: Record<string, unknown> | undefined;\r\n if (propsJsonStr) {\r\n try {\r\n props = JSON.parse(propsJsonStr) as Record<string, unknown>;\r\n } catch {\r\n return {\r\n content: [{ type: 'text', text: `Invalid JSON in propsJson: ${propsJsonStr}` }],\r\n isError: true,\r\n };\r\n }\r\n }\r\n\r\n switch (action) {\r\n // ----------------------------------------------------------------\r\n // add_node\r\n // ----------------------------------------------------------------\r\n case 'add_node': {\r\n if (!kind) {\r\n return {\r\n content: [{ type: 'text', text: 'kind parameter is required for add_node' }],\r\n isError: true,\r\n };\r\n }\r\n if (!NODE_KINDS.includes(kind as NodeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid kind: ${kind}. Valid kinds: ${NODE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n if (!props) {\r\n return {\r\n content: [{ type: 'text', text: 'propsJson parameter is required for add_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Validate props against the schema for this kind\r\n const validation = validateProps(kind as NodeKind, props);\r\n if (!validation.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Props validation failed for kind=\"${kind}\": ${validation.error}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n const artifactId = evidenceArtifactId ?? getOrCreateManualArtifact(db);\r\n const { node, created } = nodeRepo.upsert(kind as NodeKind, props, artifactId, parentId);\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n created,\r\n };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // add_edge\r\n // ----------------------------------------------------------------\r\n case 'add_edge': {\r\n if (!edgeKind) {\r\n return {\r\n content: [{ type: 'text', text: 'edgeKind parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n if (!EDGE_KINDS.includes(edgeKind as EdgeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid edgeKind: ${edgeKind}. Valid kinds: ${EDGE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n if (!sourceId) {\r\n return {\r\n content: [{ type: 'text', text: 'sourceId parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n if (!targetId) {\r\n return {\r\n content: [{ type: 'text', text: 'targetId parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n const artifactId = evidenceArtifactId ?? getOrCreateManualArtifact(db);\r\n const { edge, created } = edgeRepo.upsert(\r\n edgeKind as EdgeKind,\r\n sourceId,\r\n targetId,\r\n artifactId,\r\n );\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify({ ...edge, created }, null, 2) }],\r\n };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // update_node\r\n // ----------------------------------------------------------------\r\n case 'update_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter is required for update_node' }],\r\n isError: true,\r\n };\r\n }\r\n if (!props) {\r\n return {\r\n content: [{ type: 'text', text: 'propsJson parameter is required for update_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Find existing node to get its kind and current props\r\n const existing = nodeRepo.findById(id);\r\n if (!existing) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Merge existing props with the partial update\r\n const existingProps = JSON.parse(existing.propsJson) as Record<string, unknown>;\r\n const mergedProps = { ...existingProps, ...props };\r\n\r\n // Validate merged props\r\n const validation = validateProps(existing.kind, mergedProps);\r\n if (!validation.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Props validation failed for kind=\"${existing.kind}\": ${validation.error}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n const updated = nodeRepo.updateProps(id, mergedProps);\r\n if (!updated) {\r\n return {\r\n content: [{ type: 'text', text: `Failed to update node: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n const result = { ...updated, props: JSON.parse(updated.propsJson) };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // delete_node\r\n // ----------------------------------------------------------------\r\n case 'delete_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter is required for delete_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n const deleted = nodeRepo.delete(id);\r\n if (!deleted) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n return {\r\n content: [{ type: 'text', text: `Node ${id} deleted successfully.` }],\r\n };\r\n }\r\n }\r\n },\r\n );\r\n}\r\n","/**\n * sonobat — MCP Ingest Tool\n *\n * Tool for ingesting tool output files into the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { ingest } from '../../engine/ingest.js';\n\nexport function registerIngestTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'ingest_file',\n 'Ingest a tool output file (nmap XML, ffuf JSON, nuclei JSONL) into the AttackDataGraph',\n {\n path: z.string().describe('Absolute path to the tool output file'),\n tool: z.enum(['nmap', 'ffuf', 'nuclei']).describe('Tool that produced the output'),\n },\n async ({ path, tool }) => {\n try {\n const result = ingest(db, { path, tool });\n const nr = result.normalizeResult;\n const summary = [\n `Ingested ${tool} output from ${path}`,\n `Artifact ID: ${result.artifactId}`,\n `Created: ${nr.hostsCreated} hosts, ${nr.servicesCreated} services, ${nr.httpEndpointsCreated} endpoints, ${nr.inputsCreated} inputs, ${nr.observationsCreated} observations, ${nr.vulnerabilitiesCreated} vulnerabilities, ${nr.cvesCreated} CVEs`,\n ].join('\\n');\n return { content: [{ type: 'text', text: summary }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: 'text', text: `Ingest failed: ${message}` }], isError: true };\n }\n },\n );\n}\n","/**\r\n * sonobat — Ingest Engine\r\n *\r\n * ツール出力ファイルを読み込み、パース・正規化してDBに格納する。\r\n * ingestContent() はコアロジック(ファイルシステム非依存・テスト可能)。\r\n * ingest() はファイル読み込みの薄いラッパー。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport crypto from 'node:crypto';\r\nimport path from 'node:path';\r\nimport type { IngestInput, IngestResult } from '../types/engine.js';\r\nimport { parseNmapXml } from '../parser/nmap-parser.js';\r\nimport { parseFfufJson } from '../parser/ffuf-parser.js';\r\nimport { parseNucleiJsonl } from '../parser/nuclei-parser.js';\r\nimport { normalize } from './normalizer.js';\r\n\r\n/**\r\n * ツール出力の文字列を直接受け取り、パース・正規化してDBに格納する。\r\n * ファイルシステムに依存しないため、テストから直接呼び出せる。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param tool ツール種別('nmap' | 'ffuf' | 'nuclei')\r\n * @param content ツール出力の文字列\r\n * @param filePath Artifact に記録するファイルパス\r\n * @returns IngestResult(artifactId + normalizeResult)\r\n */\r\nexport function ingestContent(\r\n db: Database.Database,\r\n tool: 'nmap' | 'ffuf' | 'nuclei',\r\n content: string,\r\n filePath: string,\r\n): IngestResult {\r\n // 1. SHA-256 ハッシュを計算\r\n const sha256 = crypto.createHash('sha256').update(content).digest('hex');\r\n\r\n // 2. Artifact を作成 (direct SQL — ArtifactRepository は廃止)\r\n const artifactId = crypto.randomUUID();\r\n const capturedAt = new Date().toISOString();\r\n db.prepare(\r\n 'INSERT INTO artifacts (id, tool, kind, path, sha256, captured_at) VALUES (?, ?, ?, ?, ?, ?)',\r\n ).run(artifactId, tool, 'tool_output', filePath, sha256, capturedAt);\r\n\r\n // 3. ツール種別に応じてパース\r\n let parseResult;\r\n switch (tool) {\r\n case 'nmap':\r\n parseResult = parseNmapXml(content);\r\n break;\r\n case 'ffuf':\r\n parseResult = parseFfufJson(content);\r\n break;\r\n case 'nuclei':\r\n parseResult = parseNucleiJsonl(content);\r\n break;\r\n default: {\r\n // never 型による網羅性チェック — 未知の tool が渡された場合はコンパイルエラー\r\n const _exhaustive: never = tool;\r\n throw new Error(`Unknown tool: ${String(_exhaustive)}`);\r\n }\r\n }\r\n\r\n // 4. 正規化してDBに書き込む\r\n const normalizeResult = normalize(db, artifactId, parseResult);\r\n\r\n // 5. 結果を返す\r\n return {\r\n artifactId,\r\n normalizeResult,\r\n };\r\n}\r\n\r\n/**\r\n * ファイルパスからツール出力を読み込み、インジェストする。\r\n * ingestContent() の薄いラッパー。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param input IngestInput(path + tool)\r\n * @returns IngestResult\r\n */\r\nexport function ingest(db: Database.Database, input: IngestInput): IngestResult {\r\n const resolved = path.resolve(input.path);\r\n const content = fs.readFileSync(resolved, 'utf-8');\r\n return ingestContent(db, input.tool, content, resolved);\r\n}\r\n","/**\n * sonobat — Nmap XML パーサー\n *\n * nmap の XML 出力を解析し、ParseResult 中間表現を返す。\n * fast-xml-parser を使用して XML をパースする。\n */\n\nimport { XMLParser } from 'fast-xml-parser';\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedServiceObservation,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// XML パース後の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NmapAddress {\n '@_addr': string;\n '@_addrtype': string;\n}\n\ninterface NmapHostname {\n '@_name': string;\n '@_type': string;\n}\n\ninterface NmapPortState {\n '@_state': string;\n '@_reason'?: string;\n}\n\ninterface NmapServiceAttr {\n '@_name'?: string;\n '@_product'?: string;\n '@_version'?: string;\n '@_extrainfo'?: string;\n '@_tunnel'?: string;\n '@_conf'?: string;\n}\n\ninterface NmapPort {\n '@_protocol': string;\n '@_portid': string;\n state: NmapPortState;\n service?: NmapServiceAttr;\n}\n\ninterface NmapOsMatch {\n '@_name': string;\n '@_accuracy': string;\n}\n\ninterface NmapHost {\n address: NmapAddress | NmapAddress[];\n hostnames?: {\n hostname?: NmapHostname | NmapHostname[];\n };\n ports?: {\n port?: NmapPort | NmapPort[];\n };\n os?: {\n osmatch?: NmapOsMatch | NmapOsMatch[];\n };\n}\n\ninterface NmapRun {\n nmaprun: {\n host?: NmapHost | NmapHost[];\n };\n}\n\n// ============================================================\n// ユーティリティ\n// ============================================================\n\n/** 値を配列に正規化する。undefined/null は空配列を返す。 */\nfunction ensureArray<T>(value: T | T[] | undefined | null): T[] {\n if (value === undefined || value === null) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\n/** nmap の conf 属性から protoConfidence を決定する */\nfunction toProtoConfidence(conf: string | undefined): string {\n const n = conf !== undefined ? Number(conf) : 0;\n if (n === 10) return 'high';\n if (n >= 7) return 'medium';\n return 'low';\n}\n\n/** OS accuracy から confidence を決定する */\nfunction toOsConfidence(accuracy: string): string {\n const n = Number(accuracy);\n if (n >= 90) return 'high';\n if (n >= 50) return 'medium';\n return 'low';\n}\n\n/** サービス名が HTTPS を示すかどうかを判定する */\nfunction isHttps(service: NmapServiceAttr): boolean {\n return service['@_name'] === 'https' || service['@_tunnel'] === 'ssl';\n}\n\n/** product, version, extrainfo からバナー文字列を合成する */\nfunction buildBanner(service: NmapServiceAttr): string | undefined {\n const parts: string[] = [];\n if (service['@_product']) parts.push(service['@_product']);\n if (service['@_version']) parts.push(service['@_version']);\n if (service['@_extrainfo']) parts.push(service['@_extrainfo']);\n return parts.length > 0 ? parts.join(' ') : undefined;\n}\n\n/** IPv4 アドレスを address 配列から取得する */\nfunction getIpv4Address(addresses: NmapAddress[]): string | undefined {\n const ipv4 = addresses.find((a) => a['@_addrtype'] === 'ipv4');\n return ipv4?.['@_addr'];\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nmap XML 出力をパースし、ParseResult を返す。\n *\n * @param xml - nmap の XML 出力文字列\n * @returns ParseResult 中間表現\n */\nexport function parseNmapXml(xml: string): ParseResult {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n allowBooleanAttributes: true,\n });\n\n const parsed: unknown = parser.parse(xml);\n const nmapRun = parsed as NmapRun;\n\n const result = emptyParseResult();\n\n const hosts = ensureArray(nmapRun.nmaprun?.host);\n\n for (const host of hosts) {\n processHost(host, result);\n }\n\n return result;\n}\n\n/**\n * 単一の host 要素を処理し、結果に追加する。\n */\nfunction processHost(host: NmapHost, result: ParseResult): void {\n // --- ホスト情報 ---\n const addresses = ensureArray(host.address);\n const authority = getIpv4Address(addresses);\n if (authority === undefined) {\n return; // IPv4 アドレスがないホストはスキップ\n }\n\n const parsedHost: ParsedHost = {\n authority,\n authorityKind: 'IP',\n };\n result.hosts.push(parsedHost);\n\n // --- サービス情報 ---\n const ports = ensureArray(host.ports?.port);\n const services: ParsedService[] = [];\n\n for (const port of ports) {\n const service = processPort(port, authority);\n services.push(service);\n result.services.push(service);\n }\n\n // --- OS 情報 ---\n const osMatches = ensureArray(host.os?.osmatch);\n if (osMatches.length > 0) {\n processOsMatches(osMatches, authority, services, result);\n }\n}\n\n/**\n * 単一の port 要素を処理し、ParsedService を返す。\n */\nfunction processPort(port: NmapPort, hostAuthority: string): ParsedService {\n const service = port.service;\n const serviceName = service?.['@_name'] ?? '';\n\n // appProto の決定: https or tunnel=ssl -> 'https'\n const appProto = service !== undefined && isHttps(service) ? 'https' : serviceName;\n\n const banner = service !== undefined ? buildBanner(service) : undefined;\n const protoConfidence = toProtoConfidence(service?.['@_conf']);\n\n return {\n hostAuthority,\n transport: port['@_protocol'],\n port: Number(port['@_portid']),\n appProto,\n protoConfidence,\n banner,\n product: service?.['@_product'],\n version: service?.['@_version'],\n state: port.state['@_state'],\n };\n}\n\n/**\n * OS マッチ情報を serviceObservations に追加する。\n * 最初のサービスの transport/port を使う。サービスがない場合は port=0, transport='tcp' を使う。\n */\nfunction processOsMatches(\n osMatches: NmapOsMatch[],\n hostAuthority: string,\n services: ParsedService[],\n result: ParseResult,\n): void {\n const firstService = services.length > 0 ? services[0] : undefined;\n const transport = firstService?.transport ?? 'tcp';\n const port = firstService?.port ?? 0;\n\n for (const osMatch of osMatches) {\n const observation: ParsedServiceObservation = {\n hostAuthority,\n transport,\n port,\n key: 'os',\n value: osMatch['@_name'],\n confidence: toOsConfidence(osMatch['@_accuracy']),\n };\n result.serviceObservations.push(observation);\n }\n}\n","/**\n * sonobat — Parser intermediate types\n *\n * パーサーは DB の ID を持たない中間表現を返す。\n * Normalizer が自然キー(authority, port 等)で DB を検索・upsert する。\n */\n\n// ============================================================\n// 中間表現(DB ID を持たない)\n// ============================================================\n\n/** ホストの中間表現 */\nexport interface ParsedHost {\n authority: string;\n authorityKind: 'IP' | 'DOMAIN';\n resolvedIps?: string[];\n}\n\n/** サービスの中間表現 */\nexport interface ParsedService {\n hostAuthority: string;\n transport: string;\n port: number;\n appProto: string;\n protoConfidence: string;\n banner?: string;\n product?: string;\n version?: string;\n state: string;\n}\n\n/** サービス観測の中間表現 */\nexport interface ParsedServiceObservation {\n hostAuthority: string;\n transport: string;\n port: number;\n key: string;\n value: string;\n confidence: string;\n}\n\n/** HTTP エンドポイントの中間表現 */\nexport interface ParsedHttpEndpoint {\n hostAuthority: string;\n port: number;\n baseUri: string;\n method: string;\n path: string;\n statusCode?: number;\n contentLength?: number;\n words?: number;\n lines?: number;\n}\n\n/** 入力パラメータの中間表現 */\nexport interface ParsedInput {\n hostAuthority: string;\n port: number;\n location: string;\n name: string;\n typeHint?: string;\n}\n\n/** エンドポイント ↔ 入力の紐づけ中間表現 */\nexport interface ParsedEndpointInput {\n hostAuthority: string;\n port: number;\n method: string;\n path: string;\n inputLocation: string;\n inputName: string;\n}\n\n/** 観測値の中間表現 */\nexport interface ParsedObservation {\n hostAuthority: string;\n port: number;\n inputLocation: string;\n inputName: string;\n rawValue: string;\n normValue: string;\n source: string;\n confidence: string;\n}\n\n/** 脆弱性の中間表現 */\nexport interface ParsedVulnerability {\n hostAuthority: string;\n port: number;\n method?: string;\n path?: string;\n vulnType: string;\n title: string;\n description?: string;\n severity: string;\n confidence: string;\n}\n\n/** CVE の中間表現 */\nexport interface ParsedCve {\n /** 対応する脆弱性の title(紐づけ用) */\n vulnerabilityTitle: string;\n cveId: string;\n description?: string;\n cvssScore?: number;\n cvssVector?: string;\n referenceUrl?: string;\n}\n\n// ============================================================\n// パース結果\n// ============================================================\n\n/** パーサーが返す統一的な結果型 */\nexport interface ParseResult {\n hosts: ParsedHost[];\n services: ParsedService[];\n serviceObservations: ParsedServiceObservation[];\n httpEndpoints: ParsedHttpEndpoint[];\n inputs: ParsedInput[];\n endpointInputs: ParsedEndpointInput[];\n observations: ParsedObservation[];\n vulnerabilities: ParsedVulnerability[];\n cves: ParsedCve[];\n}\n\n/** 空の ParseResult を生成するユーティリティ */\nexport function emptyParseResult(): ParseResult {\n return {\n hosts: [],\n services: [],\n serviceObservations: [],\n httpEndpoints: [],\n inputs: [],\n endpointInputs: [],\n observations: [],\n vulnerabilities: [],\n cves: [],\n };\n}\n","/**\r\n * sonobat — ffuf JSON output parser\r\n *\r\n * ffuf の JSON 出力をパースし、ParseResult に変換する。\r\n * パスディスカバリ、パラメータファジングの両方に対応。\r\n */\r\n\r\nimport type {\r\n ParseResult,\r\n ParsedHost,\r\n ParsedService,\r\n ParsedHttpEndpoint,\r\n ParsedInput,\r\n ParsedEndpointInput,\r\n ParsedObservation,\r\n} from '../types/parser.js';\r\nimport { emptyParseResult } from '../types/parser.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// 内部型: ffuf JSON 構造のバリデーション用\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface FfufConfig {\r\n url: string;\r\n method: string;\r\n}\r\n\r\ninterface FfufResult {\r\n input: Record<string, string>;\r\n status: number;\r\n length: number;\r\n words: number;\r\n lines: number;\r\n url: string;\r\n host: string;\r\n}\r\n\r\ninterface FfufJson {\r\n commandline: string;\r\n config: FfufConfig;\r\n results: FfufResult[];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// バリデーション\r\n// ---------------------------------------------------------------------------\r\n\r\nconst IP_REGEX = /^\\d{1,3}(\\.\\d{1,3}){3}$/;\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction validateFfufJson(raw: unknown): FfufJson {\r\n if (!isRecord(raw)) {\r\n throw new Error('ffuf JSON: root must be an object');\r\n }\r\n\r\n if (typeof raw['commandline'] !== 'string') {\r\n throw new Error('ffuf JSON: commandline must be a string');\r\n }\r\n\r\n const config = raw['config'];\r\n if (!isRecord(config)) {\r\n throw new Error('ffuf JSON: config must be an object');\r\n }\r\n if (typeof config['url'] !== 'string' || typeof config['method'] !== 'string') {\r\n throw new Error('ffuf JSON: config.url and config.method must be strings');\r\n }\r\n\r\n if (!Array.isArray(raw['results'])) {\r\n throw new Error('ffuf JSON: results must be an array');\r\n }\r\n\r\n const results: FfufResult[] = [];\r\n for (const item of raw['results'] as unknown[]) {\r\n if (!isRecord(item)) {\r\n throw new Error('ffuf JSON: each result must be an object');\r\n }\r\n results.push({\r\n input: isRecord(item['input'])\r\n ? Object.fromEntries(\r\n Object.entries(item['input'] as Record<string, unknown>).map(([k, v]) => [\r\n k,\r\n String(v),\r\n ]),\r\n )\r\n : {},\r\n status: typeof item['status'] === 'number' ? item['status'] : 0,\r\n length: typeof item['length'] === 'number' ? item['length'] : 0,\r\n words: typeof item['words'] === 'number' ? item['words'] : 0,\r\n lines: typeof item['lines'] === 'number' ? item['lines'] : 0,\r\n url: typeof item['url'] === 'string' ? item['url'] : '',\r\n host: typeof item['host'] === 'string' ? item['host'] : '',\r\n });\r\n }\r\n\r\n return {\r\n commandline: raw['commandline'] as string,\r\n config: {\r\n url: config['url'] as string,\r\n method: config['method'] as string,\r\n },\r\n results,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// URL ユーティリティ\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedUrl {\r\n scheme: string;\r\n hostname: string;\r\n port: number;\r\n pathname: string;\r\n searchParams: URLSearchParams;\r\n}\r\n\r\nfunction parseUrl(urlStr: string): ParsedUrl {\r\n const parsed = new URL(urlStr);\r\n const scheme = parsed.protocol.replace(':', '');\r\n\r\n let port: number;\r\n if (parsed.port !== '') {\r\n port = Number(parsed.port);\r\n } else {\r\n port = scheme === 'https' ? 443 : 80;\r\n }\r\n\r\n return {\r\n scheme,\r\n hostname: parsed.hostname,\r\n port,\r\n pathname: parsed.pathname,\r\n searchParams: parsed.searchParams,\r\n };\r\n}\r\n\r\nfunction determineAuthorityKind(hostname: string): 'IP' | 'DOMAIN' {\r\n return IP_REGEX.test(hostname) ? 'IP' : 'DOMAIN';\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// メインパーサー\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * ffuf の JSON 出力文字列をパースし、ParseResult を返す。\r\n *\r\n * @param jsonContent - ffuf が `-of json` で出力した JSON 文字列\r\n * @returns ParseResult\r\n */\r\nexport function parseFfufJson(jsonContent: string): ParseResult {\r\n const raw: unknown = JSON.parse(jsonContent);\r\n const ffuf = validateFfufJson(raw);\r\n const method = ffuf.config.method;\r\n\r\n // results が空なら全て空配列\r\n if (ffuf.results.length === 0) {\r\n return emptyParseResult();\r\n }\r\n\r\n // ----- 集約用 Map -----\r\n // hosts: authority -> ParsedHost\r\n const hostsMap = new Map<string, ParsedHost>();\r\n // services: \"authority:port\" -> ParsedService\r\n const servicesMap = new Map<string, ParsedService>();\r\n // httpEndpoints: \"method:path\" -> ParsedHttpEndpoint\r\n const endpointsMap = new Map<string, ParsedHttpEndpoint>();\r\n // inputs: \"name\" -> ParsedInput (クエリパラメータ名で重複排除)\r\n const inputsMap = new Map<string, ParsedInput>();\r\n // endpointInputs: \"method:path:location:name\" -> ParsedEndpointInput\r\n const endpointInputsMap = new Map<string, ParsedEndpointInput>();\r\n // observations: \"location:name:rawValue\" -> ParsedObservation\r\n const observationsMap = new Map<string, ParsedObservation>();\r\n\r\n for (const result of ffuf.results) {\r\n if (result.url === '') {\r\n continue;\r\n }\r\n\r\n const parsed = parseUrl(result.url);\r\n const { scheme, hostname, port, pathname, searchParams } = parsed;\r\n const authorityKind = determineAuthorityKind(hostname);\r\n const baseUri = `${scheme}://${hostname}:${port}`;\r\n\r\n // --- Host ---\r\n if (!hostsMap.has(hostname)) {\r\n hostsMap.set(hostname, {\r\n authority: hostname,\r\n authorityKind,\r\n });\r\n }\r\n\r\n // --- Service ---\r\n const serviceKey = `${hostname}:${port}`;\r\n if (!servicesMap.has(serviceKey)) {\r\n servicesMap.set(serviceKey, {\r\n hostAuthority: hostname,\r\n transport: 'tcp',\r\n port,\r\n appProto: scheme,\r\n protoConfidence: 'high',\r\n state: 'open',\r\n });\r\n }\r\n\r\n // --- HTTP Endpoint (method + path で重複排除) ---\r\n const endpointKey = `${hostname}:${port}:${method}:${pathname}`;\r\n if (!endpointsMap.has(endpointKey)) {\r\n endpointsMap.set(endpointKey, {\r\n hostAuthority: hostname,\r\n port,\r\n baseUri,\r\n method,\r\n path: pathname,\r\n statusCode: result.status,\r\n contentLength: result.length,\r\n words: result.words,\r\n lines: result.lines,\r\n });\r\n }\r\n\r\n // --- Query Parameters -> inputs, observations, endpointInputs ---\r\n for (const [paramName, paramValue] of searchParams.entries()) {\r\n // Input (パラメータ名で重複排除)\r\n const inputKey = `${hostname}:${port}:query:${paramName}`;\r\n if (!inputsMap.has(inputKey)) {\r\n inputsMap.set(inputKey, {\r\n hostAuthority: hostname,\r\n port,\r\n location: 'query',\r\n name: paramName,\r\n });\r\n }\r\n\r\n // EndpointInput (endpoint + input の組み合わせで重複排除)\r\n const eiKey = `${hostname}:${port}:${method}:${pathname}:query:${paramName}`;\r\n if (!endpointInputsMap.has(eiKey)) {\r\n endpointInputsMap.set(eiKey, {\r\n hostAuthority: hostname,\r\n port,\r\n method,\r\n path: pathname,\r\n inputLocation: 'query',\r\n inputName: paramName,\r\n });\r\n }\r\n\r\n // Observation (パラメータ名 + 値で重複排除)\r\n const obsKey = `${hostname}:${port}:query:${paramName}:${paramValue}`;\r\n if (!observationsMap.has(obsKey)) {\r\n observationsMap.set(obsKey, {\r\n hostAuthority: hostname,\r\n port,\r\n inputLocation: 'query',\r\n inputName: paramName,\r\n rawValue: paramValue,\r\n normValue: paramValue,\r\n source: 'ffuf_url',\r\n confidence: 'high',\r\n });\r\n }\r\n }\r\n }\r\n\r\n return {\r\n hosts: [...hostsMap.values()],\r\n services: [...servicesMap.values()],\r\n serviceObservations: [],\r\n httpEndpoints: [...endpointsMap.values()],\r\n inputs: [...inputsMap.values()],\r\n endpointInputs: [...endpointInputsMap.values()],\r\n observations: [...observationsMap.values()],\r\n vulnerabilities: [],\r\n cves: [],\r\n };\r\n}\r\n","/**\n * sonobat — Nuclei JSONL パーサー\n *\n * nuclei の JSONL 出力を解析し、ParseResult 中間表現を返す。\n * 各行は独立した JSON オブジェクト(nuclei finding)として処理する。\n */\n\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedHttpEndpoint,\n ParsedVulnerability,\n ParsedCve,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// nuclei finding の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NucleiClassification {\n 'cve-id'?: string[];\n 'cvss-metrics'?: string;\n 'cvss-score'?: number;\n}\n\ninterface NucleiInfo {\n name: string;\n severity: string;\n tags: string[];\n classification?: NucleiClassification;\n}\n\ninterface NucleiFinding {\n 'template-id': string;\n info: NucleiInfo;\n type: string;\n host: string;\n 'matched-at': string;\n ip: string;\n port: string;\n scheme: string;\n url: string;\n}\n\n// ============================================================\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 isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string');\n}\n\nfunction isNucleiClassification(value: unknown): value is NucleiClassification {\n if (!isRecord(value)) return false;\n // classification は空オブジェクトでも許容する\n if ('cve-id' in value && !isStringArray(value['cve-id'])) return false;\n if (\n 'cvss-metrics' in value &&\n typeof value['cvss-metrics'] !== 'string' &&\n value['cvss-metrics'] !== undefined\n )\n return false;\n if (\n 'cvss-score' in value &&\n typeof value['cvss-score'] !== 'number' &&\n value['cvss-score'] !== undefined\n )\n return false;\n return true;\n}\n\nfunction isNucleiInfo(value: unknown): value is NucleiInfo {\n if (!isRecord(value)) return false;\n if (typeof value.name !== 'string') return false;\n if (typeof value.severity !== 'string') return false;\n if (!isStringArray(value.tags)) return false;\n if (\n 'classification' in value &&\n value.classification !== undefined &&\n !isNucleiClassification(value.classification)\n )\n return false;\n return true;\n}\n\nfunction isNucleiFinding(value: unknown): value is NucleiFinding {\n if (!isRecord(value)) return false;\n if (typeof value['template-id'] !== 'string') return false;\n if (!isNucleiInfo(value.info)) return false;\n if (typeof value.type !== 'string') return false;\n if (typeof value.host !== 'string') return false;\n if (typeof value['matched-at'] !== 'string') return false;\n if (typeof value.ip !== 'string') return false;\n if (typeof value.port !== 'string') return false;\n if (typeof value.scheme !== 'string') return false;\n if (typeof value.url !== 'string') return false;\n return true;\n}\n\n// ============================================================\n// URL パス抽出(生文字列から、デコードせずに抽出する)\n// ============================================================\n\n/**\n * URL 文字列から pathname 部分を生文字列のまま抽出する。\n * Node.js の URL クラスは %2e を . にデコードしてパスを正規化してしまうため、\n * パストラバーサル系のペイロードを保存するには raw なパスが必要。\n */\nfunction extractRawPathname(urlStr: string): string {\n // scheme://authority の後のパス部分を取り出す\n // authority の終わり = 3つ目の / の位置(scheme://host:port/path...)\n const schemeEnd = urlStr.indexOf('://');\n if (schemeEnd === -1) {\n return '/';\n }\n const afterAuthority = urlStr.indexOf('/', schemeEnd + 3);\n if (afterAuthority === -1) {\n return '/';\n }\n // パスの終わり = ? または # の最初の出現位置\n const queryStart = urlStr.indexOf('?', afterAuthority);\n const fragmentStart = urlStr.indexOf('#', afterAuthority);\n let pathEnd = urlStr.length;\n if (queryStart !== -1 && queryStart < pathEnd) {\n pathEnd = queryStart;\n }\n if (fragmentStart !== -1 && fragmentStart < pathEnd) {\n pathEnd = fragmentStart;\n }\n return urlStr.substring(afterAuthority, pathEnd);\n}\n\n// ============================================================\n// vulnType 推定\n// ============================================================\n\n/** タグ配列から vulnType を推定する。優先度順に判定する。 */\nfunction inferVulnType(tags: string[]): string {\n const priorityTags = ['sqli', 'xss', 'rce', 'lfi', 'ssrf'] as const;\n for (const tag of priorityTags) {\n if (tags.includes(tag)) {\n return tag;\n }\n }\n return 'other';\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nuclei JSONL 出力をパースし、ParseResult を返す。\n *\n * @param jsonl - nuclei の JSONL 出力文字列(1行1JSON)\n * @returns ParseResult 中間表現\n */\nexport function parseNucleiJsonl(jsonl: string): ParseResult {\n const result = emptyParseResult();\n\n if (jsonl.trim() === '') {\n return result;\n }\n\n const lines = jsonl.split('\\n');\n const seenHosts = new Set<string>();\n const seenServices = new Set<string>();\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === '') {\n continue;\n }\n\n const parsed: unknown = JSON.parse(trimmed);\n if (!isNucleiFinding(parsed)) {\n continue;\n }\n\n processFinding(parsed, result, seenHosts, seenServices);\n }\n\n return result;\n}\n\n/**\n * 単一の nuclei finding を処理し、結果に追加する。\n */\nfunction processFinding(\n finding: NucleiFinding,\n result: ParseResult,\n seenHosts: Set<string>,\n seenServices: Set<string>,\n): void {\n const ip = finding.ip;\n const port = Number(finding.port);\n const scheme = finding.scheme;\n const matchedAt = finding['matched-at'];\n\n // --- ホスト(重複排除) ---\n if (!seenHosts.has(ip)) {\n seenHosts.add(ip);\n const host: ParsedHost = {\n authority: ip,\n authorityKind: 'IP',\n };\n result.hosts.push(host);\n }\n\n // --- サービス(authority+port で重複排除) ---\n const serviceKey = `${ip}:${port}`;\n if (!seenServices.has(serviceKey)) {\n seenServices.add(serviceKey);\n const service: ParsedService = {\n hostAuthority: ip,\n transport: 'tcp',\n port,\n appProto: scheme,\n protoConfidence: 'high',\n state: 'open',\n };\n result.services.push(service);\n }\n\n // --- HTTP エンドポイント ---\n const rawPath = extractRawPathname(matchedAt);\n const baseUri = `${scheme}://${ip}:${port}`;\n\n const endpoint: ParsedHttpEndpoint = {\n hostAuthority: ip,\n port,\n baseUri,\n method: 'GET',\n path: rawPath,\n };\n result.httpEndpoints.push(endpoint);\n\n // --- 脆弱性 ---\n const info = finding.info;\n const vulnerability: ParsedVulnerability = {\n hostAuthority: ip,\n port,\n method: 'GET',\n path: rawPath,\n vulnType: inferVulnType(info.tags),\n title: info.name,\n severity: info.severity,\n confidence: 'high',\n };\n result.vulnerabilities.push(vulnerability);\n\n // --- CVE ---\n const classification = info.classification;\n if (\n classification !== undefined &&\n isRecord(classification) &&\n 'cve-id' in classification &&\n isStringArray(classification['cve-id']) &&\n classification['cve-id'].length > 0\n ) {\n for (const cveId of classification['cve-id']) {\n const cve: ParsedCve = {\n vulnerabilityTitle: info.name,\n cveId,\n cvssScore:\n typeof classification['cvss-score'] === 'number'\n ? classification['cvss-score']\n : undefined,\n cvssVector:\n typeof classification['cvss-metrics'] === 'string'\n ? classification['cvss-metrics']\n : undefined,\n };\n result.cves.push(cve);\n }\n }\n}\n","/**\r\n * sonobat — Normalizer (Graph-native)\r\n *\r\n * ParseResult(パーサーの中間表現)を受け取り、\r\n * NodeRepository / EdgeRepository を使ってグラフ DB に書き込む。\r\n * 全操作はトランザクションでラップし、アトミックに実行する。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { ParseResult } from '../types/parser.js';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\n\r\n// ============================================================\r\n// 結果型\r\n// ============================================================\r\n\r\n/** normalize() の実行結果。各エンティティの新規作成数を返す。 */\r\nexport interface NormalizeResult {\r\n hostsCreated: number;\r\n servicesCreated: number;\r\n serviceObservationsCreated: number;\r\n httpEndpointsCreated: number;\r\n inputsCreated: number;\r\n endpointInputsCreated: number;\r\n observationsCreated: number;\r\n vulnerabilitiesCreated: number;\r\n cvesCreated: number;\r\n}\r\n\r\n// ============================================================\r\n// normalize\r\n// ============================================================\r\n\r\n/**\r\n * ParseResult を DB に正規化して書き込む(Graph-native 版)。\r\n *\r\n * - NodeRepository.upsert() で自然キーによる重複排除を行い、\r\n * EdgeRepository.upsert() でリレーションを作成する。\r\n * - 全操作は 1 トランザクション内で実行される。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param artifactId 根拠となる Artifact の ID(evidence_artifact_id に設定)\r\n * @param parseResult パーサーが返した中間表現\r\n * @returns 各エンティティの新規作成数\r\n */\r\nexport function normalize(\r\n db: Database.Database,\r\n artifactId: string,\r\n parseResult: ParseResult,\r\n): NormalizeResult {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n const run = db.transaction((): NormalizeResult => {\r\n const result: NormalizeResult = {\r\n hostsCreated: 0,\r\n servicesCreated: 0,\r\n serviceObservationsCreated: 0,\r\n httpEndpointsCreated: 0,\r\n inputsCreated: 0,\r\n endpointInputsCreated: 0,\r\n observationsCreated: 0,\r\n vulnerabilitiesCreated: 0,\r\n cvesCreated: 0,\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // 自然キー → ノード ID のマッピング\r\n // ---------------------------------------------------------\r\n const hostIdByAuthority = new Map<string, string>();\r\n // key: \"hostNodeId:transport:port\"\r\n const serviceIdByKey = new Map<string, string>();\r\n // key: \"serviceNodeId:method:path\"\r\n const endpointIdByKey = new Map<string, string>();\r\n // key: \"serviceNodeId:location:name\"\r\n const inputIdByKey = new Map<string, string>();\r\n // key: vulnerability title → node ID\r\n const vulnIdByTitle = new Map<string, string>();\r\n\r\n // ---------------------------------------------------------\r\n // 1. Upsert hosts\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.hosts) {\r\n const { node, created } = nodeRepo.upsert('host', {\r\n authorityKind: parsed.authorityKind,\r\n authority: parsed.authority,\r\n resolvedIpsJson: JSON.stringify(parsed.resolvedIps ?? []),\r\n });\r\n\r\n hostIdByAuthority.set(parsed.authority, node.id);\r\n if (created) {\r\n result.hostsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 2. Upsert services + HOST_SERVICE edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.services) {\r\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\r\n if (!hostId) continue;\r\n\r\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\r\n if (serviceIdByKey.has(svcKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'service',\r\n {\r\n transport: parsed.transport,\r\n port: parsed.port,\r\n appProto: parsed.appProto,\r\n protoConfidence: parsed.protoConfidence,\r\n banner: parsed.banner,\r\n product: parsed.product,\r\n version: parsed.version,\r\n state: parsed.state,\r\n },\r\n artifactId,\r\n hostId,\r\n );\r\n\r\n serviceIdByKey.set(svcKey, node.id);\r\n\r\n // HOST_SERVICE edge\r\n edgeRepo.upsert('HOST_SERVICE', hostId, node.id, artifactId);\r\n\r\n if (created) {\r\n result.servicesCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // ヘルパー: hostAuthority + port → serviceId を解決\r\n // HTTP 系エンティティは transport が常に tcp\r\n // ---------------------------------------------------------\r\n function resolveServiceId(hostAuthority: string, port: number): string | undefined {\r\n const hostId = hostIdByAuthority.get(hostAuthority);\r\n if (!hostId) return undefined;\r\n return serviceIdByKey.get(`${hostId}:tcp:${port}`);\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 3. Service observations (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.serviceObservations) {\r\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\r\n if (!hostId) continue;\r\n\r\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\r\n const serviceId = serviceIdByKey.get(svcKey);\r\n if (!serviceId) continue;\r\n\r\n const obsNode = nodeRepo.create(\r\n 'svc_observation',\r\n {\r\n key: parsed.key,\r\n value: parsed.value,\r\n confidence: parsed.confidence,\r\n },\r\n artifactId,\r\n );\r\n\r\n edgeRepo.create('SERVICE_OBSERVATION', serviceId, obsNode.id, artifactId);\r\n result.serviceObservationsCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 4. Upsert HTTP endpoints + SERVICE_ENDPOINT edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.httpEndpoints) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n if (endpointIdByKey.has(epKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'endpoint',\r\n {\r\n baseUri: parsed.baseUri,\r\n method: parsed.method,\r\n path: parsed.path,\r\n statusCode: parsed.statusCode,\r\n contentLength: parsed.contentLength,\r\n words: parsed.words,\r\n lines: parsed.lines,\r\n },\r\n artifactId,\r\n serviceId,\r\n );\r\n\r\n endpointIdByKey.set(epKey, node.id);\r\n\r\n // SERVICE_ENDPOINT edge\r\n edgeRepo.upsert('SERVICE_ENDPOINT', serviceId, node.id, artifactId);\r\n\r\n if (created) {\r\n result.httpEndpointsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 5. Upsert inputs + SERVICE_INPUT edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.inputs) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.location}:${parsed.name}`;\r\n if (inputIdByKey.has(inKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'input',\r\n {\r\n location: parsed.location,\r\n name: parsed.name,\r\n typeHint: parsed.typeHint,\r\n },\r\n undefined,\r\n serviceId,\r\n );\r\n\r\n inputIdByKey.set(inKey, node.id);\r\n\r\n // SERVICE_INPUT edge\r\n edgeRepo.upsert('SERVICE_INPUT', serviceId, node.id);\r\n\r\n if (created) {\r\n result.inputsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 6. Upsert endpoint_inputs (ENDPOINT_INPUT edges)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.endpointInputs) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n const endpointId = endpointIdByKey.get(epKey);\r\n if (!endpointId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\r\n const inputId = inputIdByKey.get(inKey);\r\n if (!inputId) continue;\r\n\r\n const { created } = edgeRepo.upsert('ENDPOINT_INPUT', endpointId, inputId, artifactId);\r\n if (created) {\r\n result.endpointInputsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 7. Observations (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.observations) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\r\n const inputId = inputIdByKey.get(inKey);\r\n if (!inputId) continue;\r\n\r\n const obsNode = nodeRepo.create(\r\n 'observation',\r\n {\r\n rawValue: parsed.rawValue,\r\n normValue: parsed.normValue,\r\n source: parsed.source,\r\n confidence: parsed.confidence,\r\n observedAt: new Date().toISOString(),\r\n },\r\n artifactId,\r\n );\r\n\r\n edgeRepo.create('INPUT_OBSERVATION', inputId, obsNode.id, artifactId);\r\n result.observationsCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 8. Vulnerabilities (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.vulnerabilities) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n // endpoint への紐づけ(任意)\r\n let endpointId: string | undefined;\r\n if (parsed.method && parsed.path) {\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n endpointId = endpointIdByKey.get(epKey);\r\n }\r\n\r\n const vulnNode = nodeRepo.create(\r\n 'vulnerability',\r\n {\r\n vulnType: parsed.vulnType,\r\n title: parsed.title,\r\n description: parsed.description,\r\n severity: parsed.severity,\r\n confidence: parsed.confidence,\r\n },\r\n artifactId,\r\n );\r\n\r\n vulnIdByTitle.set(parsed.title, vulnNode.id);\r\n\r\n // SERVICE_VULNERABILITY edge\r\n edgeRepo.create('SERVICE_VULNERABILITY', serviceId, vulnNode.id, artifactId);\r\n\r\n // optional ENDPOINT_VULNERABILITY edge\r\n if (endpointId) {\r\n edgeRepo.create('ENDPOINT_VULNERABILITY', endpointId, vulnNode.id, artifactId);\r\n }\r\n\r\n result.vulnerabilitiesCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 9. CVEs (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.cves) {\r\n const vulnId = vulnIdByTitle.get(parsed.vulnerabilityTitle);\r\n if (!vulnId) continue;\r\n\r\n const cveNode = nodeRepo.create(\r\n 'cve',\r\n {\r\n cveId: parsed.cveId,\r\n description: parsed.description,\r\n cvssScore: parsed.cvssScore,\r\n cvssVector: parsed.cvssVector,\r\n referenceUrl: parsed.referenceUrl,\r\n },\r\n undefined,\r\n vulnId,\r\n );\r\n\r\n edgeRepo.create('VULNERABILITY_CVE', vulnId, cveNode.id);\r\n result.cvesCreated++;\r\n }\r\n\r\n return result;\r\n });\r\n\r\n return run();\r\n}\r\n","/**\n * sonobat — MCP Propose Tool\n *\n * Tool for generating next-step action proposals based on missing data.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { propose } from '../../engine/proposer.js';\n\nexport function registerProposeTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'propose',\n 'Analyze the AttackDataGraph for missing data and propose next-step actions',\n {\n hostId: z.string().optional().describe('Limit proposals to a specific host (optional)'),\n },\n async ({ hostId }) => {\n const actions = propose(db, hostId);\n if (actions.length === 0) {\n return {\n content: [\n { type: 'text', text: 'No actions proposed. All discovered data appears complete.' },\n ],\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(actions, null, 2) }] };\n },\n );\n}\n","/**\r\n * sonobat — Proposer engine (graph-native)\r\n *\r\n * Analyzes the AttackDataGraph stored in SQLite and proposes\r\n * next-step actions (scans, discovery, etc.) based on missing data.\r\n *\r\n * Uses NodeRepository + EdgeRepository instead of entity-specific repositories.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Action } from '../types/engine.js';\r\nimport type { GraphNode } from '../types/graph.js';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\n\r\n/** Helper: parse propsJson safely */\r\nfunction parseProps(node: GraphNode): Record<string, unknown> {\r\n return JSON.parse(node.propsJson) as Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Analyze the database for missing reconnaissance data and return\r\n * a list of proposed actions to fill the gaps.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param hostId - Optional: limit analysis to a single host\r\n * @returns Array of proposed actions\r\n */\r\nexport function propose(db: Database.Database, hostId?: string): Action[] {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n const actions: Action[] = [];\r\n\r\n // Determine target hosts\r\n let hosts: GraphNode[];\r\n if (hostId !== undefined) {\r\n const host = nodeRepo.findById(hostId);\r\n if (host === undefined) {\r\n return [];\r\n }\r\n hosts = [host];\r\n } else {\r\n hosts = nodeRepo.findByKind('host');\r\n }\r\n\r\n for (const host of hosts) {\r\n const hostProps = parseProps(host);\r\n const authority = hostProps.authority as string;\r\n\r\n // Find services: edges from host where kind='HOST_SERVICE' -> target nodes\r\n const serviceEdges = edgeRepo.findBySource(host.id, 'HOST_SERVICE');\r\n\r\n // (a) No services at all -> suggest nmap scan\r\n if (serviceEdges.length === 0) {\r\n actions.push({\r\n kind: 'nmap_scan',\r\n description: `Port scan ${authority} to discover services`,\r\n command: `nmap -p- -sV ${authority}`,\r\n params: { hostId: host.id },\r\n });\r\n continue;\r\n }\r\n\r\n // (b) For each HTTP/HTTPS service, check for missing data\r\n for (const serviceEdge of serviceEdges) {\r\n const serviceNode = nodeRepo.findById(serviceEdge.targetId);\r\n if (serviceNode === undefined) {\r\n continue;\r\n }\r\n\r\n const serviceProps = parseProps(serviceNode);\r\n const appProto = serviceProps.appProto as string;\r\n const port = serviceProps.port as number;\r\n const state = serviceProps.state as string | undefined;\r\n\r\n if (appProto !== 'http' && appProto !== 'https') {\r\n continue;\r\n }\r\n if (state !== undefined && state !== 'open') {\r\n continue;\r\n }\r\n\r\n const baseUri = `${appProto}://${authority}:${port}`;\r\n\r\n proposeForHttpService(actions, host, serviceNode, authority, baseUri, nodeRepo, edgeRepo);\r\n }\r\n }\r\n\r\n return actions;\r\n}\r\n\r\n/**\r\n * Check a single HTTP/HTTPS service for missing data and push\r\n * proposed actions into the actions array.\r\n */\r\nfunction proposeForHttpService(\r\n actions: Action[],\r\n host: GraphNode,\r\n service: GraphNode,\r\n authority: string,\r\n baseUri: string,\r\n nodeRepo: NodeRepository,\r\n edgeRepo: EdgeRepository,\r\n): void {\r\n // Find endpoints: edges from service where kind='SERVICE_ENDPOINT' -> target nodes\r\n const endpointEdges = edgeRepo.findBySource(service.id, 'SERVICE_ENDPOINT');\r\n\r\n // No endpoints -> suggest directory/file discovery\r\n if (endpointEdges.length === 0) {\r\n actions.push({\r\n kind: 'ffuf_discovery',\r\n description: `Discover endpoints on ${baseUri}`,\r\n command: `ffuf -u ${baseUri}/FUZZ -w /usr/share/wordlists/dirb/common.txt`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities at service level (used for value_fuzz decision)\r\n // Filter out false_positive vulnerabilities — they should not prevent\r\n // the proposer from suggesting further testing actions.\r\n const vulnEdges = edgeRepo.findBySource(service.id, 'SERVICE_VULNERABILITY');\r\n const activeVulns = vulnEdges.filter((ve) => {\r\n const vulnNode = nodeRepo.findById(ve.targetId);\r\n if (vulnNode === undefined) {\r\n return false;\r\n }\r\n const vulnProps = parseProps(vulnNode);\r\n return vulnProps.status !== 'false_positive';\r\n });\r\n\r\n // For each endpoint: check inputs via ENDPOINT_INPUT edges (per-endpoint)\r\n for (const endpointEdge of endpointEdges) {\r\n const endpointNode = nodeRepo.findById(endpointEdge.targetId);\r\n if (endpointNode === undefined) {\r\n continue;\r\n }\r\n\r\n const endpointProps = parseProps(endpointNode);\r\n const endpointPath = endpointProps.path as string;\r\n\r\n // Find endpoint_input edges: edges from endpoint where kind='ENDPOINT_INPUT' -> input nodes\r\n const inputEdges = edgeRepo.findBySource(endpointNode.id, 'ENDPOINT_INPUT');\r\n\r\n if (inputEdges.length === 0) {\r\n actions.push({\r\n kind: 'parameter_discovery',\r\n description: `Discover input parameters for ${baseUri}${endpointPath}`,\r\n params: { hostId: host.id, serviceId: service.id, endpointId: endpointNode.id },\r\n });\r\n }\r\n\r\n // For each linked input: check observations\r\n for (const inputEdge of inputEdges) {\r\n const inputNode = nodeRepo.findById(inputEdge.targetId);\r\n if (inputNode === undefined) {\r\n continue;\r\n }\r\n\r\n const inputProps = parseProps(inputNode);\r\n const inputName = inputProps.name as string;\r\n const inputLocation = inputProps.location as string;\r\n\r\n // Find observations: edges from input where kind='INPUT_OBSERVATION' -> target nodes\r\n const observationEdges = edgeRepo.findBySource(inputNode.id, 'INPUT_OBSERVATION');\r\n\r\n if (observationEdges.length === 0) {\r\n actions.push({\r\n kind: 'value_collection',\r\n description: `Collect observed values for input \"${inputName}\" (${inputLocation})`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpointNode.id,\r\n inputId: inputNode.id,\r\n },\r\n });\r\n } else if (activeVulns.length === 0) {\r\n // Has input + observations, but no active vulnerabilities -> suggest fuzzing\r\n actions.push({\r\n kind: 'value_fuzz',\r\n description: `Fuzz input \"${inputName}\" (${inputLocation}) on ${baseUri}${endpointPath}`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpointNode.id,\r\n inputId: inputNode.id,\r\n },\r\n });\r\n }\r\n }\r\n }\r\n\r\n // Check vhosts: edges from host where kind='HOST_VHOST'\r\n const vhostEdges = edgeRepo.findBySource(host.id, 'HOST_VHOST');\r\n if (vhostEdges.length === 0) {\r\n actions.push({\r\n kind: 'vhost_discovery',\r\n description: `Discover virtual hosts for ${authority}`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities -> suggest nuclei scan if no active vulnerabilities\r\n if (activeVulns.length === 0) {\r\n actions.push({\r\n kind: 'nuclei_scan',\r\n description: `Scan ${baseUri} for known vulnerabilities`,\r\n command: `nuclei -u ${baseUri} -jsonl`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Knowledge Base Tools\r\n *\r\n * Tools for searching technique documentation (HackTricks knowledge base)\r\n * and managing the FTS5 index.\r\n *\r\n * Features:\r\n * - search_kb: Full-text search with BM25 ranking\r\n * - index_kb: Auto-clone/pull HackTricks + incremental indexing\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport { z } from 'zod';\r\nimport { TechniqueDocRepository } from '../../db/repository/technique-doc-repository.js';\r\nimport { indexHacktricks } from '../../engine/indexer.js';\r\nimport { getHacktricksDir, ensureDataDir } from '../../engine/data-dir.js';\r\nimport { cloneHacktricks, pullHacktricks, isGitAvailable } from '../../engine/git-ops.js';\r\nimport type { IndexResult } from '../../engine/indexer.js';\r\n\r\n/**\r\n * Format an IndexResult into a human-readable message.\r\n */\r\nfunction formatIndexResult(result: IndexResult, dir: string): string {\r\n const lines: string[] = [\r\n `Successfully indexed from ${dir}.`,\r\n ` Chunks inserted: ${result.totalChunks}`,\r\n ` New files: ${result.newFiles}`,\r\n ` Updated files: ${result.updatedFiles}`,\r\n ` Deleted files: ${result.deletedFiles}`,\r\n ` Skipped (unchanged): ${result.skippedFiles}`,\r\n ];\r\n return lines.join('\\n');\r\n}\r\n\r\nexport function registerKbTools(server: McpServer, db: Database.Database): void {\r\n const repo = new TechniqueDocRepository(db);\r\n\r\n // 1. search_kb\r\n server.tool(\r\n 'search_kb',\r\n 'Search the technique knowledge base (HackTricks) using full-text search. Returns relevant technique documentation chunks ranked by relevance.',\r\n {\r\n query: z.string().describe('Search query (e.g. \"docker breakout\", \"SQL injection\")'),\r\n category: z\r\n .string()\r\n .optional()\r\n .describe('Filter by category (e.g. \"linux-hardening\", \"web\")'),\r\n limit: z.number().optional().describe('Maximum number of results (default: 10)'),\r\n },\r\n async ({ query, category, limit }) => {\r\n const results = repo.search(query, { limit: limit ?? 10, category });\r\n\r\n if (results.length === 0) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `No results found for \"${query}\".${repo.count() === 0 ? ' The technique index is empty. Run index_kb to populate it.' : ''}`,\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const formatted = results.map((r) => ({\r\n title: r.title,\r\n category: r.category,\r\n filePath: r.filePath,\r\n chunkIndex: r.chunkIndex,\r\n score: r.score,\r\n content: r.content,\r\n }));\r\n\r\n return { content: [{ type: 'text', text: JSON.stringify(formatted, null, 2) }] };\r\n },\r\n );\r\n\r\n // 2. index_kb (with auto-clone/pull)\r\n server.tool(\r\n 'index_kb',\r\n 'Index or re-index the HackTricks documentation into the full-text search database. When called without a path, automatically clones or updates HackTricks from GitHub. Uses incremental indexing to skip unchanged files.',\r\n {\r\n path: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Path to the HackTricks repository directory. If omitted, auto-clones to ~/.sonobat/data/hacktricks/',\r\n ),\r\n update: z\r\n .boolean()\r\n .optional()\r\n .describe(\r\n 'Whether to pull latest changes before indexing (default: true). Set to false to skip git pull.',\r\n ),\r\n },\r\n async ({ path: hacktricksPath, update }) => {\r\n try {\r\n let dir: string;\r\n const messages: string[] = [];\r\n\r\n if (hacktricksPath) {\r\n // Explicit path provided — use as-is\r\n dir = hacktricksPath;\r\n } else {\r\n // Auto-clone/pull flow\r\n dir = getHacktricksDir();\r\n ensureDataDir();\r\n\r\n if (!fs.existsSync(dir)) {\r\n // Directory doesn't exist → clone\r\n const gitAvailable = await isGitAvailable();\r\n if (!gitAvailable) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: 'git is not installed or not in PATH. Please install git and try again, or provide a path to an existing HackTricks directory.',\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n messages.push(`Cloning HackTricks to ${dir}...`);\r\n const cloneResult = await cloneHacktricks(dir);\r\n if (!cloneResult.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Failed to clone HackTricks: ${cloneResult.error.message}${cloneResult.error.cause ? ` (${cloneResult.error.cause})` : ''}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n messages.push('Clone completed.');\r\n } else if (update !== false) {\r\n // Directory exists → pull (failure is a warning, not an error)\r\n const pullResult = await pullHacktricks(dir);\r\n if (pullResult.ok) {\r\n messages.push(`Updated: ${pullResult.message}`);\r\n } else {\r\n messages.push(\r\n `Warning: git pull failed (${pullResult.error.message}). Continuing with existing data.`,\r\n );\r\n }\r\n }\r\n }\r\n\r\n const result = indexHacktricks(db, dir);\r\n messages.push(formatIndexResult(result, dir));\r\n\r\n return {\r\n content: [{ type: 'text', text: messages.join('\\n') }],\r\n };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n return {\r\n content: [{ type: 'text', text: `Failed to index HackTricks: ${message}` }],\r\n isError: true,\r\n };\r\n }\r\n },\r\n );\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\n\r\n/** Technique documentation entity (camelCase). */\r\nexport interface TechniqueDoc {\r\n id: string;\r\n source: string;\r\n filePath: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunkIndex: number;\r\n indexedAt: string;\r\n fileMtime: string | null;\r\n}\r\n\r\n/** Input for creating a new technique document (no id/indexedAt). */\r\nexport interface CreateTechniqueDocInput {\r\n source: string;\r\n filePath: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunkIndex: number;\r\n fileMtime?: string;\r\n}\r\n\r\n/**\r\n * Raw row shape returned by better-sqlite3 for the `technique_docs` table.\r\n */\r\ninterface TechniqueDocRow {\r\n id: string;\r\n source: string;\r\n file_path: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunk_index: number;\r\n indexed_at: string;\r\n file_mtime: string | null;\r\n}\r\n\r\n/**\r\n * Row shape for FTS5 search results with rank score.\r\n */\r\ninterface TechniqueDocSearchRow extends TechniqueDocRow {\r\n rank: number;\r\n}\r\n\r\n/** Maps a snake_case DB row to a camelCase TechniqueDoc entity. */\r\nfunction rowToTechniqueDoc(row: TechniqueDocRow): TechniqueDoc {\r\n return {\r\n id: row.id,\r\n source: row.source,\r\n filePath: row.file_path,\r\n title: row.title,\r\n category: row.category,\r\n content: row.content,\r\n chunkIndex: row.chunk_index,\r\n indexedAt: row.indexed_at,\r\n fileMtime: row.file_mtime,\r\n };\r\n}\r\n\r\n/** TechniqueDoc with search relevance score. */\r\nexport interface TechniqueDocSearchResult extends TechniqueDoc {\r\n score: number;\r\n}\r\n\r\n/** Options for FTS5 search. */\r\nexport interface SearchOptions {\r\n limit?: number;\r\n category?: string;\r\n}\r\n\r\nconst BATCH_SIZE = 100;\r\n\r\n/**\r\n * Repository for the `technique_docs` table with FTS5 full-text search.\r\n *\r\n * All queries use prepared statements to prevent SQL injection.\r\n */\r\nexport class TechniqueDocRepository {\r\n private readonly db: Database.Database;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n }\r\n\r\n /**\r\n * Bulk-insert technique documents. Runs in batches within transactions.\r\n * Returns the number of inserted documents.\r\n */\r\n index(docs: CreateTechniqueDocInput[]): number {\r\n if (docs.length === 0) return 0;\r\n\r\n const now = new Date().toISOString();\r\n const stmt = this.db.prepare(\r\n `INSERT INTO technique_docs (id, source, file_path, title, category, content, chunk_index, indexed_at, file_mtime)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\r\n );\r\n\r\n let inserted = 0;\r\n\r\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\r\n const batch = docs.slice(i, i + BATCH_SIZE);\r\n const insertBatch = this.db.transaction(() => {\r\n for (const doc of batch) {\r\n const id = crypto.randomUUID();\r\n stmt.run(\r\n id,\r\n doc.source,\r\n doc.filePath,\r\n doc.title,\r\n doc.category,\r\n doc.content,\r\n doc.chunkIndex,\r\n now,\r\n doc.fileMtime ?? null,\r\n );\r\n inserted++;\r\n }\r\n });\r\n insertBatch();\r\n }\r\n\r\n return inserted;\r\n }\r\n\r\n /**\r\n * Full-text search using FTS5 BM25 ranking.\r\n * User input is wrapped in double quotes for literal matching to prevent FTS5 syntax errors.\r\n */\r\n search(query: string, options: SearchOptions = {}): TechniqueDocSearchResult[] {\r\n const { limit = 20, category } = options;\r\n\r\n // Split query into individual terms, wrap each in double quotes for safety,\r\n // and join with spaces (FTS5 implicit AND). This prevents FTS5 syntax errors\r\n // while allowing flexible multi-word matching.\r\n const terms = query\r\n .trim()\r\n .split(/\\s+/)\r\n .filter((t) => t.length > 0);\r\n const safeQuery = terms.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(' ');\r\n\r\n let sql: string;\r\n const params: unknown[] = [safeQuery];\r\n\r\n if (category) {\r\n sql = `SELECT td.*, fts.rank\r\n FROM technique_docs td\r\n JOIN technique_docs_fts fts ON td.rowid = fts.rowid\r\n WHERE technique_docs_fts MATCH ?\r\n AND td.category = ?\r\n ORDER BY fts.rank\r\n LIMIT ?`;\r\n params.push(category, limit);\r\n } else {\r\n sql = `SELECT td.*, fts.rank\r\n FROM technique_docs td\r\n JOIN technique_docs_fts fts ON td.rowid = fts.rowid\r\n WHERE technique_docs_fts MATCH ?\r\n ORDER BY fts.rank\r\n LIMIT ?`;\r\n params.push(limit);\r\n }\r\n\r\n const rows = this.db.prepare(sql).all(...params) as TechniqueDocSearchRow[];\r\n\r\n return rows.map((row) => ({\r\n ...rowToTechniqueDoc(row),\r\n score: row.rank * -1,\r\n }));\r\n }\r\n\r\n /** Return all unique categories. */\r\n listCategories(): string[] {\r\n const rows = this.db\r\n .prepare('SELECT DISTINCT category FROM technique_docs ORDER BY category')\r\n .all() as Array<{ category: string }>;\r\n return rows.map((r) => r.category);\r\n }\r\n\r\n /** Return all documents in a given category. */\r\n findByCategory(category: string): TechniqueDoc[] {\r\n const rows = this.db\r\n .prepare<[string], TechniqueDocRow>(\r\n `SELECT id, source, file_path, title, category, content, chunk_index, indexed_at, file_mtime\r\n FROM technique_docs\r\n WHERE category = ?\r\n ORDER BY file_path, chunk_index`,\r\n )\r\n .all(category);\r\n return rows.map(rowToTechniqueDoc);\r\n }\r\n\r\n /**\r\n * Delete all documents from a given source.\r\n * Returns the number of deleted documents.\r\n */\r\n deleteBySource(source: string): number {\r\n const result = this.db.prepare('DELETE FROM technique_docs WHERE source = ?').run(source);\r\n return result.changes;\r\n }\r\n\r\n /** Return the total number of indexed documents. */\r\n count(): number {\r\n const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM technique_docs').get() as {\r\n cnt: number;\r\n };\r\n return row.cnt;\r\n }\r\n\r\n /**\r\n * Get a map of file_path → file_mtime for all documents from a given source.\r\n * Returns one entry per unique file_path (uses the first mtime found).\r\n * Used for incremental indexing to detect changed/new/deleted files.\r\n */\r\n findMtimesBySource(source: string): Map<string, string | null> {\r\n const rows = this.db\r\n .prepare(\r\n `SELECT DISTINCT file_path, file_mtime FROM technique_docs WHERE source = ? GROUP BY file_path`,\r\n )\r\n .all(source) as Array<{ file_path: string; file_mtime: string | null }>;\r\n\r\n const map = new Map<string, string | null>();\r\n for (const row of rows) {\r\n map.set(row.file_path, row.file_mtime);\r\n }\r\n return map;\r\n }\r\n\r\n /**\r\n * Delete documents from a given source whose file_path is in the provided list.\r\n * Returns the number of deleted documents.\r\n * Used for incremental indexing to remove changed/deleted files before re-inserting.\r\n */\r\n deleteBySourceAndFilePaths(source: string, filePaths: string[]): number {\r\n if (filePaths.length === 0) return 0;\r\n\r\n // Use parameterized IN clause to prevent SQL injection\r\n const placeholders = filePaths.map(() => '?').join(', ');\r\n const stmt = this.db.prepare(\r\n `DELETE FROM technique_docs WHERE source = ? AND file_path IN (${placeholders})`,\r\n );\r\n\r\n const result = stmt.run(source, ...filePaths);\r\n return result.changes;\r\n }\r\n}\r\n","/**\r\n * sonobat — HackTricks Indexer Engine\r\n *\r\n * Parses Markdown files into searchable chunks and indexes them into\r\n * the technique_docs table with FTS5 full-text search.\r\n * Supports incremental indexing by comparing file mtimes.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { TechniqueDocRepository } from '../db/repository/technique-doc-repository.js';\r\nimport type { CreateTechniqueDocInput } from '../db/repository/technique-doc-repository.js';\r\n\r\n/** A parsed chunk from a Markdown file. */\r\nexport interface MarkdownChunk {\r\n title: string;\r\n content: string;\r\n chunkIndex: number;\r\n}\r\n\r\n/** Result of an indexing operation. */\r\nexport interface IndexResult {\r\n totalChunks: number;\r\n newFiles: number;\r\n updatedFiles: number;\r\n deletedFiles: number;\r\n skippedFiles: number;\r\n}\r\n\r\n/**\r\n * Parse a Markdown file into chunks split by H2 boundaries.\r\n *\r\n * - H1 (`# Title`) is extracted as the document title (falls back to filename).\r\n * - Content is split at H2 (`## Section`) boundaries.\r\n * - Each chunk is an independent search unit.\r\n * - If no H2 is present, the entire content is one chunk.\r\n * - Empty/whitespace-only content returns an empty array.\r\n */\r\nexport function parseMarkdownChunks(markdown: string, fileName: string): MarkdownChunk[] {\r\n const trimmed = markdown.trim();\r\n if (trimmed.length === 0) return [];\r\n\r\n const lines = trimmed.split('\\n');\r\n\r\n // Extract H1 title\r\n let title: string | undefined;\r\n let contentStartIndex = 0;\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const match = lines[i].match(/^#\\s+(.+)$/);\r\n if (match) {\r\n title = match[1].trim();\r\n contentStartIndex = i + 1;\r\n break;\r\n }\r\n }\r\n\r\n if (!title) {\r\n // Use filename without extension as title\r\n title = path.basename(fileName, path.extname(fileName));\r\n contentStartIndex = 0;\r\n }\r\n\r\n const contentLines = lines.slice(contentStartIndex);\r\n\r\n // Split by H2 boundaries\r\n const sections: Array<{ heading: string | null; lines: string[] }> = [];\r\n let currentSection: { heading: string | null; lines: string[] } = { heading: null, lines: [] };\r\n\r\n for (const line of contentLines) {\r\n const h2Match = line.match(/^##\\s+(.+)$/);\r\n if (h2Match) {\r\n // Save current section if it has content\r\n sections.push(currentSection);\r\n currentSection = { heading: h2Match[1].trim(), lines: [] };\r\n } else {\r\n currentSection.lines.push(line);\r\n }\r\n }\r\n sections.push(currentSection);\r\n\r\n // Build chunks\r\n const chunks: MarkdownChunk[] = [];\r\n let chunkIndex = 0;\r\n\r\n for (const section of sections) {\r\n const sectionContent = section.heading\r\n ? `## ${section.heading}\\n\\n${section.lines.join('\\n').trim()}`\r\n : section.lines.join('\\n').trim();\r\n\r\n if (sectionContent.length === 0) continue;\r\n\r\n chunks.push({\r\n title,\r\n content: sectionContent,\r\n chunkIndex,\r\n });\r\n chunkIndex++;\r\n }\r\n\r\n return chunks;\r\n}\r\n\r\n/**\r\n * Extract category from a file path (relative to the root directory).\r\n * Category is the directory portion of the path using forward slashes.\r\n *\r\n * Examples:\r\n * \"linux-hardening/privilege-escalation/docker-breakout.md\" → \"linux-hardening/privilege-escalation\"\r\n * \"web/sql-injection.md\" → \"web\"\r\n * \"README.md\" → \"\"\r\n */\r\nexport function extractCategory(filePath: string): string {\r\n // Normalize Windows backslashes to forward slashes\r\n const normalized = filePath.replace(/\\\\/g, '/');\r\n const dir = path.posix.dirname(normalized);\r\n return dir === '.' ? '' : dir;\r\n}\r\n\r\n/**\r\n * Recursively collect all .md files from a directory.\r\n */\r\nfunction collectMarkdownFiles(dir: string, baseDir: string): string[] {\r\n const files: string[] = [];\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n files.push(...collectMarkdownFiles(fullPath, baseDir));\r\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\r\n // Skip README files\r\n if (entry.name.toLowerCase() === 'readme.md') continue;\r\n files.push(fullPath);\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\nconst SOURCE_NAME = 'hacktricks';\r\n\r\n/**\r\n * Index all Markdown files from a HackTricks directory into the database.\r\n *\r\n * Uses incremental indexing:\r\n * 1. Fetches existing file_path → file_mtime map from DB\r\n * 2. Compares with disk files to classify: new, updated, deleted, unchanged\r\n * 3. Only processes new/updated files; deletes removed files from DB\r\n *\r\n * Returns an IndexResult with statistics about what was processed.\r\n */\r\nexport function indexHacktricks(db: Database.Database, hacktricksDir: string): IndexResult {\r\n const repo = new TechniqueDocRepository(db);\r\n\r\n // Step 1: Get existing mtimes from DB\r\n const existingMtimes = repo.findMtimesBySource(SOURCE_NAME);\r\n\r\n // Step 2: Collect current files on disk and their mtimes\r\n const mdFiles = collectMarkdownFiles(hacktricksDir, hacktricksDir);\r\n const diskFiles = new Map<string, string>(); // relativePath → mtime ISO string\r\n\r\n for (const filePath of mdFiles) {\r\n const relativePath = path.relative(hacktricksDir, filePath).replace(/\\\\/g, '/');\r\n const stat = fs.statSync(filePath);\r\n diskFiles.set(relativePath, stat.mtime.toISOString());\r\n }\r\n\r\n // Step 3: Classify files\r\n const newFiles: string[] = [];\r\n const updatedFiles: string[] = [];\r\n const skippedFiles: string[] = [];\r\n\r\n for (const [relativePath, diskMtime] of diskFiles) {\r\n const existingMtime = existingMtimes.get(relativePath);\r\n if (existingMtime === undefined) {\r\n // Not in DB → new file\r\n newFiles.push(relativePath);\r\n } else if (existingMtime !== diskMtime) {\r\n // In DB but mtime changed → updated file\r\n updatedFiles.push(relativePath);\r\n } else {\r\n // Same mtime → skip\r\n skippedFiles.push(relativePath);\r\n }\r\n }\r\n\r\n // Deleted files: in DB but not on disk\r\n const deletedFiles: string[] = [];\r\n for (const existingPath of existingMtimes.keys()) {\r\n if (!diskFiles.has(existingPath)) {\r\n deletedFiles.push(existingPath);\r\n }\r\n }\r\n\r\n // Step 4: Delete changed/removed files from DB\r\n const filesToDelete = [...updatedFiles, ...deletedFiles];\r\n if (filesToDelete.length > 0) {\r\n repo.deleteBySourceAndFilePaths(SOURCE_NAME, filesToDelete);\r\n }\r\n\r\n // Step 5: Parse and insert new/updated files\r\n const filesToInsert = [...newFiles, ...updatedFiles];\r\n const allDocs: CreateTechniqueDocInput[] = [];\r\n\r\n for (const relativePath of filesToInsert) {\r\n const fullPath = path.join(hacktricksDir, relativePath);\r\n const content = fs.readFileSync(fullPath, 'utf-8');\r\n const category = extractCategory(relativePath);\r\n const chunks = parseMarkdownChunks(content, path.basename(fullPath));\r\n const fileMtime = diskFiles.get(relativePath)!;\r\n\r\n for (const chunk of chunks) {\r\n allDocs.push({\r\n source: SOURCE_NAME,\r\n filePath: relativePath,\r\n title: chunk.title,\r\n category,\r\n content: chunk.content,\r\n chunkIndex: chunk.chunkIndex,\r\n fileMtime,\r\n });\r\n }\r\n }\r\n\r\n const insertedChunks = repo.index(allDocs);\r\n\r\n return {\r\n totalChunks: insertedChunks,\r\n newFiles: newFiles.length,\r\n updatedFiles: updatedFiles.length,\r\n deletedFiles: deletedFiles.length,\r\n skippedFiles: skippedFiles.length,\r\n };\r\n}\r\n","/**\r\n * sonobat — Data Directory Utilities\r\n *\r\n * Manages the default data directory (~/.sonobat/data/) where HackTricks\r\n * and other data sources are stored. Supports overriding via environment\r\n * variable SONOBAT_DATA_DIR for testing and custom setups.\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport os from 'node:os';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Get the root data directory path.\r\n * Default: ~/.sonobat/data/\r\n * Override: set SONOBAT_DATA_DIR environment variable.\r\n */\r\nexport function getDataDir(): string {\r\n const envDir = process.env['SONOBAT_DATA_DIR'];\r\n if (envDir) return envDir;\r\n return path.join(os.homedir(), '.sonobat', 'data');\r\n}\r\n\r\n/**\r\n * Get the HackTricks repository directory path.\r\n * Returns: <dataDir>/hacktricks/\r\n */\r\nexport function getHacktricksDir(): string {\r\n return path.join(getDataDir(), 'hacktricks');\r\n}\r\n\r\n/**\r\n * Ensure the data directory exists, creating it recursively if needed.\r\n */\r\nexport function ensureDataDir(): void {\r\n fs.mkdirSync(getDataDir(), { recursive: true });\r\n}\r\n","/**\r\n * sonobat — Git Operations for HackTricks\r\n *\r\n * Provides git clone/pull operations for managing the HackTricks repository.\r\n * Uses execFile (not shell) to prevent command injection.\r\n */\r\n\r\nimport { execFile as execFileCb } from 'node:child_process';\r\nimport { promisify } from 'node:util';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\nconst execFile = promisify(execFileCb);\r\n\r\nconst HACKTRICKS_REPO = 'https://github.com/HackTricks-wiki/hacktricks.git';\r\nconst TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\r\n\r\n/** Error kinds for git operations. */\r\nexport type GitErrorKind =\r\n | 'git_not_found'\r\n | 'clone_failed'\r\n | 'pull_failed'\r\n | 'permission_denied'\r\n | 'network_error'\r\n | 'directory_not_found'\r\n | 'unknown';\r\n\r\n/** Result type for git operations. */\r\nexport type GitResult =\r\n | { ok: true; message: string }\r\n | { ok: false; error: { kind: GitErrorKind; message: string; cause?: string } };\r\n\r\n/**\r\n * Check if git is available on the system PATH.\r\n */\r\nexport async function isGitAvailable(): Promise<boolean> {\r\n try {\r\n await execFile('git', ['--version'], { timeout: 10_000 });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Classify an error from execFile into a GitErrorKind.\r\n */\r\nfunction classifyError(err: unknown, operation: 'clone' | 'pull'): GitResult {\r\n const errorMessage = err instanceof Error ? err.message : String(err);\r\n const stderr =\r\n err !== null && typeof err === 'object' && 'stderr' in err\r\n ? String((err as { stderr: unknown }).stderr)\r\n : '';\r\n\r\n const combined = `${errorMessage} ${stderr}`.toLowerCase();\r\n\r\n if (combined.includes('enoent') || combined.includes('not recognized') || combined.includes('not found')) {\r\n return {\r\n ok: false,\r\n error: { kind: 'git_not_found', message: 'git is not installed or not in PATH', cause: errorMessage },\r\n };\r\n }\r\n\r\n if (combined.includes('permission denied') || combined.includes('access denied')) {\r\n return {\r\n ok: false,\r\n error: { kind: 'permission_denied', message: `Permission denied during ${operation}`, cause: errorMessage },\r\n };\r\n }\r\n\r\n if (\r\n combined.includes('could not resolve') ||\r\n combined.includes('unable to access') ||\r\n combined.includes('network') ||\r\n combined.includes('timed out')\r\n ) {\r\n return {\r\n ok: false,\r\n error: { kind: 'network_error', message: `Network error during ${operation}`, cause: errorMessage },\r\n };\r\n }\r\n\r\n return {\r\n ok: false,\r\n error: {\r\n kind: operation === 'clone' ? 'clone_failed' : 'pull_failed',\r\n message: `git ${operation} failed`,\r\n cause: errorMessage,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Clone the HackTricks repository with --depth 1 (shallow clone).\r\n * The targetDir is the destination directory for the clone.\r\n * The parent directory of targetDir must exist.\r\n */\r\nexport async function cloneHacktricks(targetDir: string): Promise<GitResult> {\r\n // Check parent directory exists before attempting clone\r\n const parentDir = path.dirname(targetDir);\r\n\r\n if (!fs.existsSync(parentDir)) {\r\n return {\r\n ok: false,\r\n error: {\r\n kind: 'clone_failed',\r\n message: `Parent directory does not exist: ${parentDir}`,\r\n },\r\n };\r\n }\r\n\r\n try {\r\n const { stdout } = await execFile(\r\n 'git',\r\n ['clone', '--depth', '1', HACKTRICKS_REPO, targetDir],\r\n { timeout: TIMEOUT_MS },\r\n );\r\n return { ok: true, message: stdout.trim() || 'Clone completed successfully' };\r\n } catch (err) {\r\n return classifyError(err, 'clone');\r\n }\r\n}\r\n\r\n/**\r\n * Pull latest changes in an existing HackTricks repository.\r\n * Uses --ff-only to avoid merge conflicts.\r\n */\r\nexport async function pullHacktricks(repoDir: string): Promise<GitResult> {\r\n // Check if directory exists\r\n if (!fs.existsSync(repoDir)) {\r\n return {\r\n ok: false,\r\n error: {\r\n kind: 'directory_not_found',\r\n message: `Directory not found: ${repoDir}`,\r\n },\r\n };\r\n }\r\n\r\n try {\r\n const { stdout } = await execFile('git', ['pull', '--ff-only'], {\r\n cwd: repoDir,\r\n timeout: TIMEOUT_MS,\r\n });\r\n return { ok: true, message: stdout.trim() || 'Already up to date' };\r\n } catch (err) {\r\n return classifyError(err, 'pull');\r\n }\r\n}\r\n","/**\n * sonobat — MCP Ops Tool (unified)\n *\n * Single 'ops' tool with an 'action' parameter for managing\n * engagements, runs, action queue, and action executions.\n *\n * Actions: create_engagement, list_engagements, get_engagement,\n * update_engagement, delete_engagement, create_run, list_runs,\n * get_run, update_run_status, enqueue_action, poll_action,\n * complete_action, fail_action, cancel_action, list_actions,\n * get_execution, list_executions\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { EngagementRepository } from '../../db/repository/engagement-repository.js';\nimport { RunRepository } from '../../db/repository/run-repository.js';\nimport { ActionQueueRepository } from '../../db/repository/action-queue-repository.js';\nimport { ActionExecutionRepository } from '../../db/repository/action-execution-repository.js';\n\nexport function registerOpsTools(server: McpServer, db: Database.Database): void {\n const engagementRepo = new EngagementRepository(db);\n const runRepo = new RunRepository(db);\n const actionQueueRepo = new ActionQueueRepository(db);\n const actionExecRepo = new ActionExecutionRepository(db);\n\n server.tool(\n 'ops',\n 'Manage operational entities. Actions: create_engagement, list_engagements, get_engagement, update_engagement, delete_engagement, create_run, list_runs, get_run, update_run_status, enqueue_action, poll_action, complete_action, fail_action, cancel_action, list_actions, get_execution, list_executions',\n {\n action: z.enum([\n 'create_engagement',\n 'list_engagements',\n 'get_engagement',\n 'update_engagement',\n 'delete_engagement',\n 'create_run',\n 'list_runs',\n 'get_run',\n 'update_run_status',\n 'enqueue_action',\n 'poll_action',\n 'complete_action',\n 'fail_action',\n 'cancel_action',\n 'list_actions',\n 'get_execution',\n 'list_executions',\n ]),\n id: z.string().optional().describe('Entity ID'),\n engagementId: z.string().optional().describe('Engagement ID'),\n runId: z.string().optional().describe('Run ID'),\n actionId: z.string().optional().describe('Action queue item ID'),\n name: z.string().optional().describe('Engagement name'),\n environment: z.string().optional().describe('Environment (e.g. stg, prod)'),\n status: z.string().optional().describe('Status value'),\n triggerKind: z.string().optional().describe('Run trigger kind'),\n triggerRef: z.string().optional().describe('Run trigger reference'),\n kind: z.string().optional().describe('Action kind'),\n dedupeKey: z.string().optional().describe('Action deduplication key'),\n leaseOwner: z.string().optional().describe('Lease owner for poll'),\n executor: z.string().optional().describe('Executor name'),\n errorMessage: z.string().optional().describe('Error message for fail'),\n scopeJson: z.string().optional().describe('Scope as JSON'),\n policyJson: z.string().optional().describe('Policy as JSON'),\n scheduleCron: z.string().optional().describe('Schedule cron expression'),\n paramsJson: z.string().optional().describe('Action parameters as JSON'),\n summaryJson: z.string().optional().describe('Run summary as JSON'),\n inputJson: z.string().optional().describe('Execution input as JSON'),\n priority: z.number().optional().describe('Action priority (lower = higher)'),\n maxAttempts: z.number().optional().describe('Max retry attempts'),\n leaseDurationSec: z.number().optional().describe('Lease duration in seconds'),\n limit: z.number().optional().describe('Result limit'),\n state: z.string().optional().describe('Action state filter'),\n parentActionId: z.string().optional().describe('Parent action ID'),\n availableAt: z.string().optional().describe('Available at timestamp'),\n },\n async ({\n action,\n id,\n engagementId,\n runId,\n actionId,\n name,\n environment,\n status,\n triggerKind,\n triggerRef,\n kind,\n dedupeKey,\n leaseOwner,\n errorMessage,\n scopeJson,\n policyJson,\n scheduleCron,\n paramsJson,\n summaryJson,\n priority,\n maxAttempts,\n leaseDurationSec,\n limit,\n state,\n parentActionId,\n availableAt,\n }) => {\n switch (action) {\n // ----------------------------------------------------------------\n // Engagement actions\n // ----------------------------------------------------------------\n case 'create_engagement': {\n if (!name) {\n return {\n content: [\n { type: 'text', text: 'name parameter is required for create_engagement' },\n ],\n isError: true,\n };\n }\n const engagement = engagementRepo.create({\n name,\n environment,\n scopeJson,\n policyJson,\n scheduleCron,\n status,\n });\n return { content: [{ type: 'text', text: JSON.stringify(engagement, null, 2) }] };\n }\n\n case 'list_engagements': {\n const engagements = status\n ? engagementRepo.findByStatus(status)\n : engagementRepo.list();\n return { content: [{ type: 'text', text: JSON.stringify(engagements, null, 2) }] };\n }\n\n case 'get_engagement': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for get_engagement' },\n ],\n isError: true,\n };\n }\n const engagement = engagementRepo.findById(id);\n if (!engagement) {\n return {\n content: [{ type: 'text', text: `Engagement not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(engagement, null, 2) }] };\n }\n\n case 'update_engagement': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for update_engagement' },\n ],\n isError: true,\n };\n }\n const fields: Record<string, unknown> = {};\n if (name !== undefined) fields.name = name;\n if (environment !== undefined) fields.environment = environment;\n if (scopeJson !== undefined) fields.scopeJson = scopeJson;\n if (policyJson !== undefined) fields.policyJson = policyJson;\n if (scheduleCron !== undefined) fields.scheduleCron = scheduleCron;\n if (status !== undefined) fields.status = status;\n\n const updated = engagementRepo.update(id, fields);\n if (!updated) {\n return {\n content: [{ type: 'text', text: `Engagement not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(updated, null, 2) }] };\n }\n\n case 'delete_engagement': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for delete_engagement' },\n ],\n isError: true,\n };\n }\n const deleted = engagementRepo.delete(id);\n if (!deleted) {\n return {\n content: [{ type: 'text', text: `Engagement not found: ${id}` }],\n isError: true,\n };\n }\n return {\n content: [{ type: 'text', text: `Engagement ${id} deleted successfully.` }],\n };\n }\n\n // ----------------------------------------------------------------\n // Run actions\n // ----------------------------------------------------------------\n case 'create_run': {\n if (!engagementId) {\n return {\n content: [\n { type: 'text', text: 'engagementId parameter is required for create_run' },\n ],\n isError: true,\n };\n }\n if (!triggerKind) {\n return {\n content: [\n { type: 'text', text: 'triggerKind parameter is required for create_run' },\n ],\n isError: true,\n };\n }\n if (!status) {\n return {\n content: [\n { type: 'text', text: 'status parameter is required for create_run' },\n ],\n isError: true,\n };\n }\n const run = runRepo.create({\n engagementId,\n triggerKind,\n triggerRef,\n status,\n });\n return { content: [{ type: 'text', text: JSON.stringify(run, null, 2) }] };\n }\n\n case 'list_runs': {\n if (!engagementId) {\n return {\n content: [\n { type: 'text', text: 'engagementId parameter is required for list_runs' },\n ],\n isError: true,\n };\n }\n const runs = runRepo.findByEngagement(engagementId, limit);\n return { content: [{ type: 'text', text: JSON.stringify(runs, null, 2) }] };\n }\n\n case 'get_run': {\n if (!id) {\n return {\n content: [{ type: 'text', text: 'id parameter is required for get_run' }],\n isError: true,\n };\n }\n const run = runRepo.findById(id);\n if (!run) {\n return {\n content: [{ type: 'text', text: `Run not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(run, null, 2) }] };\n }\n\n case 'update_run_status': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for update_run_status' },\n ],\n isError: true,\n };\n }\n if (!status) {\n return {\n content: [\n { type: 'text', text: 'status parameter is required for update_run_status' },\n ],\n isError: true,\n };\n }\n const updatedRun = runRepo.updateStatus(id, status, summaryJson);\n if (!updatedRun) {\n return {\n content: [{ type: 'text', text: `Run not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(updatedRun, null, 2) }] };\n }\n\n // ----------------------------------------------------------------\n // ActionQueue actions\n // ----------------------------------------------------------------\n case 'enqueue_action': {\n if (!engagementId) {\n return {\n content: [\n {\n type: 'text',\n text: 'engagementId parameter is required for enqueue_action',\n },\n ],\n isError: true,\n };\n }\n if (!kind) {\n return {\n content: [\n { type: 'text', text: 'kind parameter is required for enqueue_action' },\n ],\n isError: true,\n };\n }\n if (!dedupeKey) {\n return {\n content: [\n { type: 'text', text: 'dedupeKey parameter is required for enqueue_action' },\n ],\n isError: true,\n };\n }\n const item = actionQueueRepo.enqueue({\n engagementId,\n runId,\n parentActionId,\n kind,\n priority,\n dedupeKey,\n paramsJson,\n state,\n maxAttempts,\n availableAt,\n });\n return { content: [{ type: 'text', text: JSON.stringify(item, null, 2) }] };\n }\n\n case 'poll_action': {\n if (!leaseOwner) {\n return {\n content: [\n { type: 'text', text: 'leaseOwner parameter is required for poll_action' },\n ],\n isError: true,\n };\n }\n const polled = actionQueueRepo.poll(leaseOwner, leaseDurationSec);\n if (!polled) {\n return {\n content: [{ type: 'text', text: 'No action available to poll' }],\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(polled, null, 2) }] };\n }\n\n case 'complete_action': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for complete_action' },\n ],\n isError: true,\n };\n }\n const completed = actionQueueRepo.complete(id);\n if (!completed) {\n return {\n content: [\n { type: 'text', text: `Action not found or not in running state: ${id}` },\n ],\n isError: true,\n };\n }\n return {\n content: [{ type: 'text', text: `Action ${id} completed successfully.` }],\n };\n }\n\n case 'fail_action': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for fail_action' },\n ],\n isError: true,\n };\n }\n if (!errorMessage) {\n return {\n content: [\n { type: 'text', text: 'errorMessage parameter is required for fail_action' },\n ],\n isError: true,\n };\n }\n const failed = actionQueueRepo.fail(id, errorMessage);\n if (!failed) {\n return {\n content: [\n { type: 'text', text: `Action not found or not in running state: ${id}` },\n ],\n isError: true,\n };\n }\n const afterFail = actionQueueRepo.findById(id);\n return {\n content: [{ type: 'text', text: JSON.stringify(afterFail, null, 2) }],\n };\n }\n\n case 'cancel_action': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for cancel_action' },\n ],\n isError: true,\n };\n }\n const cancelled = actionQueueRepo.cancel(id);\n if (!cancelled) {\n return {\n content: [{ type: 'text', text: `Action not found: ${id}` }],\n isError: true,\n };\n }\n return {\n content: [{ type: 'text', text: `Action ${id} cancelled successfully.` }],\n };\n }\n\n case 'list_actions': {\n if (!engagementId) {\n return {\n content: [\n { type: 'text', text: 'engagementId parameter is required for list_actions' },\n ],\n isError: true,\n };\n }\n const actions = actionQueueRepo.findByEngagement(engagementId, state);\n return { content: [{ type: 'text', text: JSON.stringify(actions, null, 2) }] };\n }\n\n // ----------------------------------------------------------------\n // ActionExecution actions\n // ----------------------------------------------------------------\n case 'get_execution': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for get_execution' },\n ],\n isError: true,\n };\n }\n const execution = actionExecRepo.findById(id);\n if (!execution) {\n return {\n content: [{ type: 'text', text: `Execution not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(execution, null, 2) }] };\n }\n\n case 'list_executions': {\n if (actionId) {\n const executions = actionExecRepo.findByAction(actionId);\n return {\n content: [{ type: 'text', text: JSON.stringify(executions, null, 2) }],\n };\n }\n if (runId) {\n const executions = actionExecRepo.findByRun(runId);\n return {\n content: [{ type: 'text', text: JSON.stringify(executions, null, 2) }],\n };\n }\n return {\n content: [\n {\n type: 'text',\n text: 'actionId or runId parameter is required for list_executions',\n },\n ],\n isError: true,\n };\n }\n }\n },\n );\n}\n","/**\n * sonobat — EngagementRepository\n *\n * engagements テーブルに対する CRUD 操作を提供する。\n * snake_case (DB) ↔ camelCase (TypeScript) の変換を内部で行う。\n */\n\nimport type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Engagement, CreateEngagementInput } from '../../types/operational.js';\n\n// ---------------------------------------------------------------------------\n// DB row 型\n// ---------------------------------------------------------------------------\n\n/** better-sqlite3 から返る engagements テーブルの行形状 */\ninterface EngagementRow {\n id: string;\n name: string;\n environment: string;\n scope_json: string;\n policy_json: string;\n schedule_cron: string | null;\n status: string;\n created_at: string;\n updated_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Row → Engagement 変換\n// ---------------------------------------------------------------------------\n\n/** snake_case DB row を camelCase Engagement にマッピング */\nfunction rowToEngagement(row: EngagementRow): Engagement {\n return {\n id: row.id,\n name: row.name,\n environment: row.environment,\n scopeJson: row.scope_json,\n policyJson: row.policy_json,\n ...(row.schedule_cron !== null ? { scheduleCron: row.schedule_cron } : {}),\n status: row.status,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n// ---------------------------------------------------------------------------\n// camelCase → snake_case フィールドマッピング\n// ---------------------------------------------------------------------------\n\n/** CreateEngagementInput のキーから DB カラム名へのマッピング */\nconst FIELD_TO_COLUMN: Record<string, string> = {\n name: 'name',\n environment: 'environment',\n scopeJson: 'scope_json',\n policyJson: 'policy_json',\n scheduleCron: 'schedule_cron',\n status: 'status',\n};\n\n// ---------------------------------------------------------------------------\n// EngagementRepository\n// ---------------------------------------------------------------------------\n\n/**\n * engagements テーブルの CRUD リポジトリ。\n *\n * - デフォルト値: environment='stg', scopeJson='{}', policyJson='{}', status='active'\n * - ID は crypto.randomUUID() で生成\n * - update() は動的 SQL で提供されたフィールドのみ SET する\n */\nexport class EngagementRepository {\n private readonly db: Database.Database;\n\n private readonly insertStmt: Database.Statement;\n private readonly selectByIdStmt: Database.Statement;\n private readonly selectByStatusStmt: Database.Statement;\n private readonly selectAllStmt: Database.Statement;\n private readonly deleteStmt: Database.Statement;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertStmt = this.db.prepare(\n `INSERT INTO engagements (id, name, environment, scope_json, policy_json, schedule_cron, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectByIdStmt = this.db.prepare(\n `SELECT id, name, environment, scope_json, policy_json, schedule_cron, status, created_at, updated_at\n FROM engagements WHERE id = ?`,\n );\n\n this.selectByStatusStmt = this.db.prepare(\n `SELECT id, name, environment, scope_json, policy_json, schedule_cron, status, created_at, updated_at\n FROM engagements WHERE status = ?`,\n );\n\n this.selectAllStmt = this.db.prepare(\n `SELECT id, name, environment, scope_json, policy_json, schedule_cron, status, created_at, updated_at\n FROM engagements`,\n );\n\n this.deleteStmt = this.db.prepare(`DELETE FROM engagements WHERE id = ?`);\n }\n\n /**\n * Engagement を新規作成して返す。\n *\n * デフォルト値:\n * - environment: 'stg'\n * - scopeJson: '{}'\n * - policyJson: '{}'\n * - status: 'active'\n */\n create(input: CreateEngagementInput): Engagement {\n const id = crypto.randomUUID();\n const timestamp = new Date().toISOString();\n\n const environment = input.environment ?? 'stg';\n const scopeJson = input.scopeJson ?? '{}';\n const policyJson = input.policyJson ?? '{}';\n const scheduleCron = input.scheduleCron ?? null;\n const status = input.status ?? 'active';\n\n this.insertStmt.run(\n id,\n input.name,\n environment,\n scopeJson,\n policyJson,\n scheduleCron,\n status,\n timestamp,\n timestamp,\n );\n\n return {\n id,\n name: input.name,\n environment,\n scopeJson,\n policyJson,\n ...(scheduleCron !== null ? { scheduleCron } : {}),\n status,\n createdAt: timestamp,\n updatedAt: timestamp,\n };\n }\n\n /**\n * ID で Engagement を取得する。存在しなければ undefined。\n */\n findById(id: string): Engagement | undefined {\n const row = this.selectByIdStmt.get(id) as EngagementRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToEngagement(row);\n }\n\n /**\n * status で Engagement 一覧を取得する。\n */\n findByStatus(status: string): Engagement[] {\n const rows = this.selectByStatusStmt.all(status) as EngagementRow[];\n return rows.map(rowToEngagement);\n }\n\n /**\n * 全 Engagement を取得する。\n */\n list(): Engagement[] {\n const rows = this.selectAllStmt.all() as EngagementRow[];\n return rows.map(rowToEngagement);\n }\n\n /**\n * Engagement の指定フィールドを更新する。\n *\n * 提供されたフィールドのみ SET し、updated_at を自動更新する。\n * 存在しない ID の場合 undefined を返す。\n */\n update(id: string, fields: Partial<CreateEngagementInput>): Engagement | undefined {\n // 更新対象のフィールドがなくても updated_at は更新する\n const setClauses: string[] = [];\n const params: unknown[] = [];\n\n for (const [key, value] of Object.entries(fields)) {\n const column = FIELD_TO_COLUMN[key];\n if (column !== undefined) {\n setClauses.push(`${column} = ?`);\n params.push(value ?? null);\n }\n }\n\n // updated_at は常に更新\n const timestamp = new Date().toISOString();\n setClauses.push('updated_at = ?');\n params.push(timestamp);\n\n // WHERE id = ?\n params.push(id);\n\n const sql = `UPDATE engagements SET ${setClauses.join(', ')} WHERE id = ?`;\n const result = this.db.prepare(sql).run(...params);\n\n if (result.changes === 0) {\n return undefined;\n }\n\n return this.findById(id);\n }\n\n /**\n * Engagement を削除する。\n *\n * CASCADE により関連する runs なども同時に削除される。\n *\n * @returns 削除成功時 true、id が存在しない場合 false。\n */\n delete(id: string): boolean {\n const result = this.deleteStmt.run(id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — RunRepository\n *\n * runs テーブルに対する CRUD 操作を提供する。\n * snake_case (DB) ↔ camelCase (TypeScript) の変換を内部で行う。\n */\n\nimport type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Run, CreateRunInput } from '../../types/operational.js';\n\n// ---------------------------------------------------------------------------\n// DB row 型\n// ---------------------------------------------------------------------------\n\n/** better-sqlite3 から返る runs テーブルの行形状 */\ninterface RunRow {\n id: string;\n engagement_id: string;\n trigger_kind: string;\n trigger_ref: string | null;\n status: string;\n started_at: string | null;\n finished_at: string | null;\n summary_json: string;\n created_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Row → Run 変換\n// ---------------------------------------------------------------------------\n\n/** snake_case DB row を camelCase Run にマッピング */\nfunction rowToRun(row: RunRow): Run {\n return {\n id: row.id,\n engagementId: row.engagement_id,\n triggerKind: row.trigger_kind,\n ...(row.trigger_ref !== null ? { triggerRef: row.trigger_ref } : {}),\n status: row.status,\n ...(row.started_at !== null ? { startedAt: row.started_at } : {}),\n ...(row.finished_at !== null ? { finishedAt: row.finished_at } : {}),\n summaryJson: row.summary_json,\n createdAt: row.created_at,\n };\n}\n\n// ---------------------------------------------------------------------------\n// RunRepository\n// ---------------------------------------------------------------------------\n\n/**\n * runs テーブルの CRUD リポジトリ。\n *\n * - ID は crypto.randomUUID() で生成\n * - created_at / started_at / finished_at は状況に応じて自動設定\n */\nexport class RunRepository {\n private readonly db: Database.Database;\n\n private readonly insertStmt: Database.Statement;\n private readonly selectByIdStmt: Database.Statement;\n private readonly selectByEngagementStmt: Database.Statement;\n private readonly selectByStatusStmt: Database.Statement;\n private readonly deleteStmt: Database.Statement;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertStmt = this.db.prepare(\n `INSERT INTO runs (id, engagement_id, trigger_kind, trigger_ref, status, started_at, finished_at, summary_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectByIdStmt = this.db.prepare(\n `SELECT id, engagement_id, trigger_kind, trigger_ref, status, started_at, finished_at, summary_json, created_at\n FROM runs WHERE id = ?`,\n );\n\n this.selectByEngagementStmt = this.db.prepare(\n `SELECT id, engagement_id, trigger_kind, trigger_ref, status, started_at, finished_at, summary_json, created_at\n FROM runs WHERE engagement_id = ? ORDER BY created_at DESC LIMIT ?`,\n );\n\n this.selectByStatusStmt = this.db.prepare(\n `SELECT id, engagement_id, trigger_kind, trigger_ref, status, started_at, finished_at, summary_json, created_at\n FROM runs WHERE status = ?`,\n );\n\n this.deleteStmt = this.db.prepare(`DELETE FROM runs WHERE id = ?`);\n }\n\n /**\n * Run を新規作成して返す。\n *\n * - created_at は現在時刻に設定\n * - status が 'running' の場合、started_at も現在時刻に設定\n * - summaryJson はデフォルト '{}'\n */\n create(input: CreateRunInput): Run {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n const startedAt = input.status === 'running' ? now : null;\n\n this.insertStmt.run(\n id,\n input.engagementId,\n input.triggerKind,\n input.triggerRef ?? null,\n input.status,\n startedAt,\n null, // finished_at\n '{}', // summary_json\n now, // created_at\n );\n\n return {\n id,\n engagementId: input.engagementId,\n triggerKind: input.triggerKind,\n ...(input.triggerRef !== undefined ? { triggerRef: input.triggerRef } : {}),\n status: input.status,\n ...(startedAt !== null ? { startedAt } : {}),\n summaryJson: '{}',\n createdAt: now,\n };\n }\n\n /**\n * ID で Run を取得する。存在しなければ undefined。\n */\n findById(id: string): Run | undefined {\n const row = this.selectByIdStmt.get(id) as RunRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToRun(row);\n }\n\n /**\n * engagement_id で Run 一覧を取得する。\n * ORDER BY created_at DESC、LIMIT デフォルト 100。\n */\n findByEngagement(engagementId: string, limit?: number): Run[] {\n const rows = this.selectByEngagementStmt.all(engagementId, limit ?? 100) as RunRow[];\n return rows.map(rowToRun);\n }\n\n /**\n * status で Run 一覧を取得する。\n */\n findByStatus(status: string): Run[] {\n const rows = this.selectByStatusStmt.all(status) as RunRow[];\n return rows.map(rowToRun);\n }\n\n /**\n * Run のステータスを更新する。\n *\n * - 'succeeded' または 'failed' の場合、finished_at を現在時刻に設定\n * - 'running' の場合、started_at が未設定なら現在時刻に設定\n * - summaryJson が指定された場合、summary_json も更新\n *\n * @returns 更新後の Run。id が存在しなければ undefined。\n */\n updateStatus(id: string, status: string, summaryJson?: string): Run | undefined {\n // 既存レコードを確認\n const existing = this.findById(id);\n if (existing === undefined) {\n return undefined;\n }\n\n const now = new Date().toISOString();\n\n // 動的に SET 句を構築\n const setClauses: string[] = ['status = ?'];\n const params: unknown[] = [status];\n\n // finished_at: succeeded / failed の場合に設定\n if (status === 'succeeded' || status === 'failed') {\n setClauses.push('finished_at = ?');\n params.push(now);\n }\n\n // started_at: running に遷移し、まだ未設定の場合に設定\n if (status === 'running' && existing.startedAt === undefined) {\n setClauses.push('started_at = ?');\n params.push(now);\n }\n\n // summaryJson: 指定された場合に更新\n if (summaryJson !== undefined) {\n setClauses.push('summary_json = ?');\n params.push(summaryJson);\n }\n\n params.push(id);\n\n const sql = `UPDATE runs SET ${setClauses.join(', ')} WHERE id = ?`;\n this.db.prepare(sql).run(...params);\n\n return this.findById(id);\n }\n\n /**\n * Run を削除する。\n *\n * @returns 削除成功時 true、id が存在しない場合 false。\n */\n delete(id: string): boolean {\n const result = this.deleteStmt.run(id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — ActionQueueRepository\n *\n * action_queue テーブルに対する CRUD + ポーリング操作。\n * キューベースの非同期タスク管理を提供する。\n */\n\nimport type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { ActionQueueItem, CreateActionInput } from '../../types/operational.js';\n\n// ---------------------------------------------------------------------------\n// DB row 型\n// ---------------------------------------------------------------------------\n\n/** better-sqlite3 から返る action_queue テーブルの行形状 */\ninterface ActionQueueRow {\n id: string;\n engagement_id: string;\n run_id: string | null;\n parent_action_id: string | null;\n kind: string;\n priority: number;\n dedupe_key: string;\n params_json: string;\n state: string;\n attempt_count: number;\n max_attempts: number;\n available_at: string;\n lease_owner: string | null;\n lease_expires_at: string | null;\n last_error: string | null;\n created_at: string;\n updated_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Row → ActionQueueItem 変換\n// ---------------------------------------------------------------------------\n\n/** snake_case DB row を camelCase ActionQueueItem にマッピング */\nfunction rowToActionQueueItem(row: ActionQueueRow): ActionQueueItem {\n return {\n id: row.id,\n engagementId: row.engagement_id,\n ...(row.run_id !== null ? { runId: row.run_id } : {}),\n ...(row.parent_action_id !== null ? { parentActionId: row.parent_action_id } : {}),\n kind: row.kind,\n priority: row.priority,\n dedupeKey: row.dedupe_key,\n paramsJson: row.params_json,\n state: row.state,\n attemptCount: row.attempt_count,\n maxAttempts: row.max_attempts,\n availableAt: row.available_at,\n ...(row.lease_owner !== null ? { leaseOwner: row.lease_owner } : {}),\n ...(row.lease_expires_at !== null ? { leaseExpiresAt: row.lease_expires_at } : {}),\n ...(row.last_error !== null ? { lastError: row.last_error } : {}),\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n// ---------------------------------------------------------------------------\n// ActionQueueRepository\n// ---------------------------------------------------------------------------\n\n/**\n * action_queue テーブルの CRUD + ポーリング リポジトリ。\n *\n * - デフォルト値: priority=100, state='queued', paramsJson='{}', maxAttempts=3, availableAt=now\n * - ID は crypto.randomUUID() で生成\n * - poll() はアトミックな UPDATE ... RETURNING で排他的リース取得\n * - fail() はリトライ回数に応じてバックオフ or dead letter\n */\nexport class ActionQueueRepository {\n private readonly db: Database.Database;\n\n private readonly insertStmt: Database.Statement;\n private readonly selectByIdStmt: Database.Statement;\n private readonly pollStmt: Database.Statement;\n private readonly completeStmt: Database.Statement;\n private readonly requeueStmt: Database.Statement;\n private readonly deadLetterStmt: Database.Statement;\n private readonly selectByEngagementStmt: Database.Statement;\n private readonly selectByEngagementStateStmt: Database.Statement;\n private readonly cancelStmt: Database.Statement;\n\n private readonly failTx: Database.Transaction<(id: string, error: string) => boolean>;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertStmt = this.db.prepare(\n `INSERT INTO action_queue\n (id, engagement_id, run_id, parent_action_id, kind, priority,\n dedupe_key, params_json, state, attempt_count, max_attempts,\n available_at, lease_owner, lease_expires_at, last_error,\n created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectByIdStmt = this.db.prepare(\n `SELECT id, engagement_id, run_id, parent_action_id, kind, priority,\n dedupe_key, params_json, state, attempt_count, max_attempts,\n available_at, lease_owner, lease_expires_at, last_error,\n created_at, updated_at\n FROM action_queue WHERE id = ?`,\n );\n\n this.pollStmt = this.db.prepare(\n `UPDATE action_queue\n SET state = 'running',\n lease_owner = ?,\n lease_expires_at = ?,\n attempt_count = attempt_count + 1,\n updated_at = ?\n WHERE id = (\n SELECT id FROM action_queue\n WHERE state = 'queued' AND available_at <= ?\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n )\n RETURNING *`,\n );\n\n this.completeStmt = this.db.prepare(\n `UPDATE action_queue\n SET state = 'succeeded', updated_at = ?\n WHERE id = ? AND state = 'running'`,\n );\n\n this.requeueStmt = this.db.prepare(\n `UPDATE action_queue\n SET state = 'queued',\n available_at = ?,\n last_error = ?,\n lease_owner = NULL,\n lease_expires_at = NULL,\n updated_at = ?\n WHERE id = ?`,\n );\n\n this.deadLetterStmt = this.db.prepare(\n `UPDATE action_queue\n SET state = 'failed',\n last_error = ?,\n updated_at = ?\n WHERE id = ?`,\n );\n\n this.selectByEngagementStmt = this.db.prepare(\n `SELECT id, engagement_id, run_id, parent_action_id, kind, priority,\n dedupe_key, params_json, state, attempt_count, max_attempts,\n available_at, lease_owner, lease_expires_at, last_error,\n created_at, updated_at\n FROM action_queue\n WHERE engagement_id = ?\n ORDER BY created_at DESC`,\n );\n\n this.selectByEngagementStateStmt = this.db.prepare(\n `SELECT id, engagement_id, run_id, parent_action_id, kind, priority,\n dedupe_key, params_json, state, attempt_count, max_attempts,\n available_at, lease_owner, lease_expires_at, last_error,\n created_at, updated_at\n FROM action_queue\n WHERE engagement_id = ? AND state = ?\n ORDER BY created_at DESC`,\n );\n\n this.cancelStmt = this.db.prepare(\n `UPDATE action_queue SET state = 'cancelled', updated_at = ? WHERE id = ?`,\n );\n\n // fail トランザクション: リトライ可能か dead letter かを判定\n this.failTx = this.db.transaction((id: string, error: string): boolean => {\n const item = this.findById(id);\n if (!item || item.state !== 'running') return false;\n\n const now = new Date().toISOString();\n\n if (item.attemptCount < item.maxAttempts) {\n // リトライ可能: バックオフ付きで再キュー\n const backoffSec = item.attemptCount * 30;\n const availableAt = new Date(Date.now() + backoffSec * 1000).toISOString();\n this.requeueStmt.run(availableAt, error, now, id);\n } else {\n // Dead letter: 最大リトライ回数に到達\n this.deadLetterStmt.run(error, now, id);\n }\n\n return true;\n });\n }\n\n /**\n * アクションをキューに追加して返す。\n *\n * デフォルト値:\n * - priority: 100\n * - state: 'queued'\n * - paramsJson: '{}'\n * - maxAttempts: 3\n * - availableAt: 現在時刻\n */\n enqueue(input: CreateActionInput): ActionQueueItem {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const priority = input.priority ?? 100;\n const state = input.state ?? 'queued';\n const paramsJson = input.paramsJson ?? '{}';\n const maxAttempts = input.maxAttempts ?? 3;\n const availableAt = input.availableAt ?? now;\n\n this.insertStmt.run(\n id,\n input.engagementId,\n input.runId ?? null,\n input.parentActionId ?? null,\n input.kind,\n priority,\n input.dedupeKey,\n paramsJson,\n state,\n 0, // attempt_count\n maxAttempts,\n availableAt,\n null, // lease_owner\n null, // lease_expires_at\n null, // last_error\n now, // created_at\n now, // updated_at\n );\n\n return {\n id,\n engagementId: input.engagementId,\n ...(input.runId !== undefined ? { runId: input.runId } : {}),\n ...(input.parentActionId !== undefined ? { parentActionId: input.parentActionId } : {}),\n kind: input.kind,\n priority,\n dedupeKey: input.dedupeKey,\n paramsJson,\n state,\n attemptCount: 0,\n maxAttempts,\n availableAt,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * ID でアクションを取得する。存在しなければ undefined。\n */\n findById(id: string): ActionQueueItem | undefined {\n const row = this.selectByIdStmt.get(id) as ActionQueueRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToActionQueueItem(row);\n }\n\n /**\n * キューから次のアクションをポーリングする。\n *\n * アトミックに state='running' に遷移し、リースを設定する。\n * available_at が現在時刻以前のもののうち、priority ASC → created_at ASC で最初の1件を取得。\n *\n * @param leaseOwner リースオーナーの識別子(例: 'worker-1')\n * @param leaseDurationSec リース期間(秒)。デフォルト 300秒。\n * @returns ポーリングしたアクション。キューが空なら undefined。\n */\n poll(leaseOwner: string, leaseDurationSec?: number): ActionQueueItem | undefined {\n const duration = leaseDurationSec ?? 300;\n const now = new Date();\n const nowIso = now.toISOString();\n const leaseExpiresAt = new Date(now.getTime() + duration * 1000).toISOString();\n\n const row = this.pollStmt.get(\n leaseOwner,\n leaseExpiresAt,\n nowIso,\n nowIso,\n ) as ActionQueueRow | undefined;\n\n if (row === undefined) {\n return undefined;\n }\n return rowToActionQueueItem(row);\n }\n\n /**\n * アクションを正常完了にする。\n *\n * state を 'succeeded' に遷移する。running 状態でない場合は更新されない。\n *\n * @returns 更新成功時 true、id が存在しないか running でない場合 false。\n */\n complete(id: string): boolean {\n const now = new Date().toISOString();\n const result = this.completeStmt.run(now, id);\n return result.changes > 0;\n }\n\n /**\n * アクションを失敗にする。\n *\n * attempt_count < max_attempts の場合: state='queued' に戻し、バックオフ付きで再スケジュール。\n * attempt_count >= max_attempts の場合: state='failed'(dead letter)。\n *\n * @param id アクション ID\n * @param error エラーメッセージ\n * @returns 更新成功時 true、id が存在しないか running でない場合 false。\n */\n fail(id: string, error: string): boolean {\n return this.failTx(id, error);\n }\n\n /**\n * エンゲージメントに紐づくアクション一覧を取得する。\n *\n * @param engagementId エンゲージメント ID\n * @param state フィルタする状態(省略時は全状態)\n * @returns アクション一覧(created_at DESC 順)\n */\n findByEngagement(engagementId: string, state?: string): ActionQueueItem[] {\n if (state !== undefined) {\n const rows = this.selectByEngagementStateStmt.all(engagementId, state) as ActionQueueRow[];\n return rows.map(rowToActionQueueItem);\n }\n const rows = this.selectByEngagementStmt.all(engagementId) as ActionQueueRow[];\n return rows.map(rowToActionQueueItem);\n }\n\n /**\n * アクションをキャンセルする。\n *\n * @returns キャンセル成功時 true、id が存在しない場合 false。\n */\n cancel(id: string): boolean {\n const now = new Date().toISOString();\n const result = this.cancelStmt.run(now, id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — ActionExecutionRepository\n *\n * action_executions テーブルに対する CRUD 操作を提供する。\n * snake_case (DB) <-> camelCase (TypeScript) の変換を内部で行う。\n */\n\nimport type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { ActionExecution, CreateExecutionInput } from '../../types/operational.js';\n\n// ---------------------------------------------------------------------------\n// DB row 型\n// ---------------------------------------------------------------------------\n\n/** better-sqlite3 から返る action_executions テーブルの行形状 */\ninterface ActionExecutionRow {\n id: string;\n action_id: string;\n run_id: string | null;\n executor: string;\n command: string | null;\n input_json: string;\n output_json: string;\n stdout_artifact_id: string | null;\n stderr_artifact_id: string | null;\n exit_code: number | null;\n error_type: string | null;\n error_message: string | null;\n started_at: string;\n finished_at: string | null;\n duration_ms: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Row → ActionExecution 変換\n// ---------------------------------------------------------------------------\n\n/** snake_case DB row を camelCase ActionExecution にマッピング */\nfunction rowToExecution(row: ActionExecutionRow): ActionExecution {\n return {\n id: row.id,\n actionId: row.action_id,\n ...(row.run_id !== null ? { runId: row.run_id } : {}),\n executor: row.executor,\n ...(row.command !== null ? { command: row.command } : {}),\n inputJson: row.input_json,\n outputJson: row.output_json,\n ...(row.stdout_artifact_id !== null ? { stdoutArtifactId: row.stdout_artifact_id } : {}),\n ...(row.stderr_artifact_id !== null ? { stderrArtifactId: row.stderr_artifact_id } : {}),\n ...(row.exit_code !== null ? { exitCode: row.exit_code } : {}),\n ...(row.error_type !== null ? { errorType: row.error_type } : {}),\n ...(row.error_message !== null ? { errorMessage: row.error_message } : {}),\n startedAt: row.started_at,\n ...(row.finished_at !== null ? { finishedAt: row.finished_at } : {}),\n ...(row.duration_ms !== null ? { durationMs: row.duration_ms } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Complete output 型\n// ---------------------------------------------------------------------------\n\n/** complete() に渡す出力パラメータ */\nexport interface CompleteExecutionOutput {\n outputJson?: string;\n exitCode?: number;\n errorType?: string;\n errorMessage?: string;\n stdoutArtifactId?: string;\n stderrArtifactId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// ActionExecutionRepository\n// ---------------------------------------------------------------------------\n\n/**\n * action_executions テーブルの CRUD リポジトリ。\n *\n * - ID は crypto.randomUUID() で生成\n * - started_at は create 時に自動設定\n * - complete() で finished_at, duration_ms を自動計算\n */\nexport class ActionExecutionRepository {\n private readonly db: Database.Database;\n\n private readonly insertStmt: Database.Statement;\n private readonly selectByIdStmt: Database.Statement;\n private readonly selectByActionStmt: Database.Statement;\n private readonly selectByRunStmt: Database.Statement;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertStmt = this.db.prepare(\n `INSERT INTO action_executions (id, action_id, run_id, executor, command, input_json, output_json, started_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectByIdStmt = this.db.prepare(\n `SELECT id, action_id, run_id, executor, command, input_json, output_json,\n stdout_artifact_id, stderr_artifact_id, exit_code, error_type, error_message,\n started_at, finished_at, duration_ms\n FROM action_executions WHERE id = ?`,\n );\n\n this.selectByActionStmt = this.db.prepare(\n `SELECT id, action_id, run_id, executor, command, input_json, output_json,\n stdout_artifact_id, stderr_artifact_id, exit_code, error_type, error_message,\n started_at, finished_at, duration_ms\n FROM action_executions WHERE action_id = ? ORDER BY started_at DESC`,\n );\n\n this.selectByRunStmt = this.db.prepare(\n `SELECT id, action_id, run_id, executor, command, input_json, output_json,\n stdout_artifact_id, stderr_artifact_id, exit_code, error_type, error_message,\n started_at, finished_at, duration_ms\n FROM action_executions WHERE run_id = ? ORDER BY started_at DESC`,\n );\n }\n\n /**\n * ActionExecution を新規作成して返す。\n *\n * - started_at は現在時刻に自動設定\n * - inputJson デフォルト '{}'\n * - outputJson デフォルト '{}'\n */\n create(input: CreateExecutionInput): ActionExecution {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n const inputJson = input.inputJson ?? '{}';\n\n this.insertStmt.run(\n id,\n input.actionId,\n input.runId ?? null,\n input.executor,\n input.command ?? null,\n inputJson,\n '{}', // output_json default\n now, // started_at\n );\n\n return {\n id,\n actionId: input.actionId,\n ...(input.runId !== undefined ? { runId: input.runId } : {}),\n executor: input.executor,\n ...(input.command !== undefined ? { command: input.command } : {}),\n inputJson,\n outputJson: '{}',\n startedAt: now,\n };\n }\n\n /**\n * ID で ActionExecution を取得する。存在しなければ undefined。\n */\n findById(id: string): ActionExecution | undefined {\n const row = this.selectByIdStmt.get(id) as ActionExecutionRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToExecution(row);\n }\n\n /**\n * action_id で ActionExecution 一覧を取得する。\n * ORDER BY started_at DESC。\n */\n findByAction(actionId: string): ActionExecution[] {\n const rows = this.selectByActionStmt.all(actionId) as ActionExecutionRow[];\n return rows.map(rowToExecution);\n }\n\n /**\n * run_id で ActionExecution 一覧を取得する。\n * ORDER BY started_at DESC。\n */\n findByRun(runId: string): ActionExecution[] {\n const rows = this.selectByRunStmt.all(runId) as ActionExecutionRow[];\n return rows.map(rowToExecution);\n }\n\n /**\n * ActionExecution を完了状態にする。\n *\n * - finished_at を現在時刻に設定\n * - duration_ms を started_at からの差分で自動計算\n * - outputJson, exitCode, errorType, errorMessage を更新\n *\n * @returns 更新後の ActionExecution。id が存在しなければ undefined。\n */\n complete(id: string, output: CompleteExecutionOutput): ActionExecution | undefined {\n const existing = this.findById(id);\n if (existing === undefined) {\n return undefined;\n }\n\n const now = new Date();\n const finishedAt = now.toISOString();\n const durationMs = now.getTime() - new Date(existing.startedAt).getTime();\n\n const setClauses: string[] = ['finished_at = ?', 'duration_ms = ?'];\n const params: unknown[] = [finishedAt, durationMs];\n\n if (output.outputJson !== undefined) {\n setClauses.push('output_json = ?');\n params.push(output.outputJson);\n }\n if (output.exitCode !== undefined) {\n setClauses.push('exit_code = ?');\n params.push(output.exitCode);\n }\n if (output.errorType !== undefined) {\n setClauses.push('error_type = ?');\n params.push(output.errorType);\n }\n if (output.errorMessage !== undefined) {\n setClauses.push('error_message = ?');\n params.push(output.errorMessage);\n }\n if (output.stdoutArtifactId !== undefined) {\n setClauses.push('stdout_artifact_id = ?');\n params.push(output.stdoutArtifactId);\n }\n if (output.stderrArtifactId !== undefined) {\n setClauses.push('stderr_artifact_id = ?');\n params.push(output.stderrArtifactId);\n }\n\n params.push(id);\n\n const sql = `UPDATE action_executions SET ${setClauses.join(', ')} WHERE id = ?`;\n this.db.prepare(sql).run(...params);\n\n return this.findById(id);\n }\n}\n","/**\n * sonobat — MCP Findings Tool (unified)\n *\n * Single 'findings' tool with an 'action' parameter for managing\n * findings, finding events, and risk snapshots.\n *\n * Actions: upsert_finding, get_finding, list_findings,\n * update_finding_state, list_finding_events,\n * create_risk_snapshot, get_risk_snapshot,\n * list_risk_snapshots, latest_risk_snapshot\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { FindingRepository } from '../../db/repository/finding-repository.js';\nimport { RiskSnapshotRepository } from '../../db/repository/risk-snapshot-repository.js';\n\nexport function registerFindingsTools(server: McpServer, db: Database.Database): void {\n const findingRepo = new FindingRepository(db);\n const riskSnapshotRepo = new RiskSnapshotRepository(db);\n\n server.tool(\n 'findings',\n 'Manage findings and risk snapshots. Actions: upsert_finding, get_finding, list_findings, update_finding_state, list_finding_events, create_risk_snapshot, get_risk_snapshot, list_risk_snapshots, latest_risk_snapshot',\n {\n action: z.enum([\n 'upsert_finding',\n 'get_finding',\n 'list_findings',\n 'update_finding_state',\n 'list_finding_events',\n 'create_risk_snapshot',\n 'get_risk_snapshot',\n 'list_risk_snapshots',\n 'latest_risk_snapshot',\n ]),\n id: z.string().optional().describe('Entity ID'),\n engagementId: z.string().optional().describe('Engagement ID'),\n canonicalKey: z.string().optional().describe('Finding canonical key'),\n nodeId: z.string().optional().describe('Associated node ID'),\n title: z.string().optional().describe('Finding title'),\n severity: z.string().optional().describe('Finding severity'),\n confidence: z.string().optional().describe('Finding confidence'),\n state: z.string().optional().describe('Finding state'),\n stateReason: z.string().optional().describe('Reason for state change'),\n runId: z.string().optional().describe('Run ID'),\n findingId: z.string().optional().describe('Finding ID for events'),\n attrsJson: z.string().optional().describe('Attributes as JSON'),\n score: z.number().optional().describe('Risk score'),\n openCritical: z.number().optional().describe('Open critical count'),\n openHigh: z.number().optional().describe('Open high count'),\n openMedium: z.number().optional().describe('Open medium count'),\n openLow: z.number().optional().describe('Open low count'),\n openInfo: z.number().optional().describe('Open info count'),\n openTotal: z.number().optional().describe('Open total count'),\n attackPathCount: z.number().optional().describe('Attack path count'),\n exposedCredCount: z.number().optional().describe('Exposed credential count'),\n modelVersion: z.string().optional().describe('Risk model version'),\n limit: z.number().optional().describe('Result limit'),\n },\n async ({\n action,\n id,\n engagementId,\n canonicalKey,\n nodeId,\n title,\n severity,\n confidence,\n state,\n stateReason,\n runId,\n findingId,\n attrsJson,\n score,\n openCritical,\n openHigh,\n openMedium,\n openLow,\n openInfo,\n openTotal,\n attackPathCount,\n exposedCredCount,\n modelVersion,\n limit,\n }) => {\n switch (action) {\n // ----------------------------------------------------------------\n // Finding actions\n // ----------------------------------------------------------------\n case 'upsert_finding': {\n if (!engagementId) {\n return {\n content: [\n {\n type: 'text',\n text: 'engagementId parameter is required for upsert_finding',\n },\n ],\n isError: true,\n };\n }\n if (!canonicalKey) {\n return {\n content: [\n {\n type: 'text',\n text: 'canonicalKey parameter is required for upsert_finding',\n },\n ],\n isError: true,\n };\n }\n if (!title) {\n return {\n content: [\n { type: 'text', text: 'title parameter is required for upsert_finding' },\n ],\n isError: true,\n };\n }\n if (!severity) {\n return {\n content: [\n { type: 'text', text: 'severity parameter is required for upsert_finding' },\n ],\n isError: true,\n };\n }\n if (!confidence) {\n return {\n content: [\n {\n type: 'text',\n text: 'confidence parameter is required for upsert_finding',\n },\n ],\n isError: true,\n };\n }\n const result = findingRepo.upsert({\n engagementId,\n canonicalKey,\n title,\n severity,\n confidence,\n nodeId,\n state,\n runId,\n attrsJson,\n });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({ ...result.finding, created: result.created }, null, 2),\n },\n ],\n };\n }\n\n case 'get_finding': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for get_finding' },\n ],\n isError: true,\n };\n }\n const finding = findingRepo.findById(id);\n if (!finding) {\n return {\n content: [{ type: 'text', text: `Finding not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(finding, null, 2) }] };\n }\n\n case 'list_findings': {\n if (!engagementId) {\n return {\n content: [\n {\n type: 'text',\n text: 'engagementId parameter is required for list_findings',\n },\n ],\n isError: true,\n };\n }\n const opts: { state?: string; severity?: string } = {};\n if (state) opts.state = state;\n if (severity) opts.severity = severity;\n const findings = findingRepo.findByEngagement(engagementId, opts);\n return {\n content: [{ type: 'text', text: JSON.stringify(findings, null, 2) }],\n };\n }\n\n case 'update_finding_state': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for update_finding_state' },\n ],\n isError: true,\n };\n }\n if (!state) {\n return {\n content: [\n {\n type: 'text',\n text: 'state parameter is required for update_finding_state',\n },\n ],\n isError: true,\n };\n }\n const updated = findingRepo.updateState(id, state, stateReason);\n if (!updated) {\n return {\n content: [{ type: 'text', text: `Finding not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(updated, null, 2) }] };\n }\n\n case 'list_finding_events': {\n if (!findingId) {\n return {\n content: [\n {\n type: 'text',\n text: 'findingId parameter is required for list_finding_events',\n },\n ],\n isError: true,\n };\n }\n const events = findingRepo.getEvents(findingId);\n return { content: [{ type: 'text', text: JSON.stringify(events, null, 2) }] };\n }\n\n // ----------------------------------------------------------------\n // RiskSnapshot actions\n // ----------------------------------------------------------------\n case 'create_risk_snapshot': {\n if (!engagementId) {\n return {\n content: [\n {\n type: 'text',\n text: 'engagementId parameter is required for create_risk_snapshot',\n },\n ],\n isError: true,\n };\n }\n if (score === undefined || score === null) {\n return {\n content: [\n {\n type: 'text',\n text: 'score parameter is required for create_risk_snapshot',\n },\n ],\n isError: true,\n };\n }\n const snapshot = riskSnapshotRepo.create({\n engagementId,\n score,\n runId,\n openCritical,\n openHigh,\n openMedium,\n openLow,\n openInfo,\n openTotal,\n attackPathCount,\n exposedCredCount,\n modelVersion,\n attrsJson,\n });\n return {\n content: [{ type: 'text', text: JSON.stringify(snapshot, null, 2) }],\n };\n }\n\n case 'get_risk_snapshot': {\n if (!id) {\n return {\n content: [\n { type: 'text', text: 'id parameter is required for get_risk_snapshot' },\n ],\n isError: true,\n };\n }\n const snapshot = riskSnapshotRepo.findById(id);\n if (!snapshot) {\n return {\n content: [{ type: 'text', text: `Risk snapshot not found: ${id}` }],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(snapshot, null, 2) }] };\n }\n\n case 'list_risk_snapshots': {\n if (!engagementId) {\n return {\n content: [\n {\n type: 'text',\n text: 'engagementId parameter is required for list_risk_snapshots',\n },\n ],\n isError: true,\n };\n }\n const snapshots = riskSnapshotRepo.findByEngagement(engagementId, limit);\n return {\n content: [{ type: 'text', text: JSON.stringify(snapshots, null, 2) }],\n };\n }\n\n case 'latest_risk_snapshot': {\n if (!engagementId) {\n return {\n content: [\n {\n type: 'text',\n text: 'engagementId parameter is required for latest_risk_snapshot',\n },\n ],\n isError: true,\n };\n }\n const latest = riskSnapshotRepo.latest(engagementId);\n if (!latest) {\n return {\n content: [\n {\n type: 'text',\n text: `No risk snapshot found for engagement: ${engagementId}`,\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(latest, null, 2) }] };\n }\n }\n },\n );\n}\n","/**\n * sonobat — FindingRepository\n *\n * findings + finding_events テーブルに対する CRUD 操作。\n * 親子関係が強いため、一つの Repository で管理する。\n */\n\nimport type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type {\n Finding,\n UpsertFindingInput,\n FindingEvent,\n CreateFindingEventInput,\n} from '../../types/operational.js';\n\n// ---------------------------------------------------------------------------\n// DB row 型\n// ---------------------------------------------------------------------------\n\n/** better-sqlite3 から返る findings テーブルの行形状 */\ninterface FindingRow {\n id: string;\n engagement_id: string;\n canonical_key: string;\n node_id: string | null;\n title: string;\n severity: string;\n confidence: string;\n state: string;\n state_reason: string | null;\n owner: string | null;\n ticket_ref: string | null;\n first_seen_run_id: string | null;\n last_seen_run_id: string | null;\n first_seen_at: string;\n last_seen_at: string;\n sla_due_at: string | null;\n attrs_json: string;\n}\n\n/** better-sqlite3 から返る finding_events テーブルの行形状 */\ninterface FindingEventRow {\n id: string;\n finding_id: string;\n run_id: string | null;\n event_type: string;\n before_json: string;\n after_json: string;\n artifact_id: string | null;\n created_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Row → Entity 変換\n// ---------------------------------------------------------------------------\n\n/** snake_case DB row を camelCase Finding にマッピング */\nfunction rowToFinding(row: FindingRow): Finding {\n return {\n id: row.id,\n engagementId: row.engagement_id,\n canonicalKey: row.canonical_key,\n ...(row.node_id !== null ? { nodeId: row.node_id } : {}),\n title: row.title,\n severity: row.severity,\n confidence: row.confidence,\n state: row.state,\n ...(row.state_reason !== null ? { stateReason: row.state_reason } : {}),\n ...(row.owner !== null ? { owner: row.owner } : {}),\n ...(row.ticket_ref !== null ? { ticketRef: row.ticket_ref } : {}),\n ...(row.first_seen_run_id !== null ? { firstSeenRunId: row.first_seen_run_id } : {}),\n ...(row.last_seen_run_id !== null ? { lastSeenRunId: row.last_seen_run_id } : {}),\n firstSeenAt: row.first_seen_at,\n lastSeenAt: row.last_seen_at,\n ...(row.sla_due_at !== null ? { slaDueAt: row.sla_due_at } : {}),\n attrsJson: row.attrs_json,\n };\n}\n\n/** snake_case DB row を camelCase FindingEvent にマッピング */\nfunction rowToFindingEvent(row: FindingEventRow): FindingEvent {\n return {\n id: row.id,\n findingId: row.finding_id,\n ...(row.run_id !== null ? { runId: row.run_id } : {}),\n eventType: row.event_type,\n beforeJson: row.before_json,\n afterJson: row.after_json,\n ...(row.artifact_id !== null ? { artifactId: row.artifact_id } : {}),\n createdAt: row.created_at,\n };\n}\n\n// ---------------------------------------------------------------------------\n// FindingRepository\n// ---------------------------------------------------------------------------\n\n/**\n * findings + finding_events テーブルの CRUD リポジトリ。\n *\n * - upsert() は engagement_id + canonical_key の UNIQUE 制約を活用\n * - finding_events は Finding のライフサイクルイベントを記録\n * - ID は crypto.randomUUID() で生成\n */\nexport class FindingRepository {\n private readonly db: Database.Database;\n\n private readonly insertFindingStmt: Database.Statement;\n private readonly selectFindingByIdStmt: Database.Statement;\n private readonly deleteFindingStmt: Database.Statement;\n private readonly updateLastSeenStmt: Database.Statement;\n private readonly updateStateStmt: Database.Statement;\n private readonly insertEventStmt: Database.Statement;\n private readonly selectEventsByFindingStmt: Database.Statement;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertFindingStmt = this.db.prepare(\n `INSERT INTO findings\n (id, engagement_id, canonical_key, node_id, title, severity, confidence,\n state, state_reason, owner, ticket_ref,\n first_seen_run_id, last_seen_run_id, first_seen_at, last_seen_at,\n sla_due_at, attrs_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectFindingByIdStmt = this.db.prepare(\n `SELECT id, engagement_id, canonical_key, node_id, title, severity, confidence,\n state, state_reason, owner, ticket_ref,\n first_seen_run_id, last_seen_run_id, first_seen_at, last_seen_at,\n sla_due_at, attrs_json\n FROM findings WHERE id = ?`,\n );\n\n this.deleteFindingStmt = this.db.prepare(`DELETE FROM findings WHERE id = ?`);\n\n this.updateLastSeenStmt = this.db.prepare(\n `UPDATE findings\n SET last_seen_at = ?, last_seen_run_id = ?,\n title = ?, severity = ?, confidence = ?, attrs_json = ?\n WHERE id = ?`,\n );\n\n this.updateStateStmt = this.db.prepare(\n `UPDATE findings SET state = ?, state_reason = ? WHERE id = ?`,\n );\n\n this.insertEventStmt = this.db.prepare(\n `INSERT INTO finding_events\n (id, finding_id, run_id, event_type, before_json, after_json, artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectEventsByFindingStmt = this.db.prepare(\n `SELECT id, finding_id, run_id, event_type, before_json, after_json, artifact_id, created_at\n FROM finding_events\n WHERE finding_id = ?\n ORDER BY created_at DESC`,\n );\n }\n\n /**\n * Finding を upsert する。\n *\n * engagement_id + canonical_key が一致する既存レコードがあれば更新(re_observed)、\n * なければ新規作成(discovered)。いずれの場合も finding_events にイベントを追加する。\n *\n * @returns { finding, created } — created は新規作成時 true、既存更新時 false\n */\n upsert(input: UpsertFindingInput): { finding: Finding; created: boolean } {\n const upsertTx = this.db.transaction((inp: UpsertFindingInput) => {\n const now = new Date().toISOString();\n const existing = this.db\n .prepare('SELECT * FROM findings WHERE engagement_id = ? AND canonical_key = ?')\n .get(inp.engagementId, inp.canonicalKey) as FindingRow | undefined;\n\n if (existing) {\n // 既存 Finding を更新\n this.updateLastSeenStmt.run(\n now,\n inp.runId ?? null,\n inp.title,\n inp.severity,\n inp.confidence,\n inp.attrsJson ?? existing.attrs_json,\n existing.id,\n );\n\n // re_observed イベントを追加\n this.insertEventStmt.run(\n crypto.randomUUID(),\n existing.id,\n inp.runId ?? null,\n 're_observed',\n '{}',\n '{}',\n null,\n now,\n );\n\n return { finding: this.findById(existing.id)!, created: false };\n } else {\n // 新規 Finding を作成\n const id = crypto.randomUUID();\n this.insertFindingStmt.run(\n id,\n inp.engagementId,\n inp.canonicalKey,\n inp.nodeId ?? null,\n inp.title,\n inp.severity,\n inp.confidence,\n inp.state ?? 'open',\n null, // state_reason\n null, // owner\n null, // ticket_ref\n inp.runId ?? null, // first_seen_run_id\n inp.runId ?? null, // last_seen_run_id\n now, // first_seen_at\n now, // last_seen_at\n null, // sla_due_at\n inp.attrsJson ?? '{}',\n );\n\n // discovered イベントを追加\n this.insertEventStmt.run(\n crypto.randomUUID(),\n id,\n inp.runId ?? null,\n 'discovered',\n '{}',\n '{}',\n null,\n now,\n );\n\n return { finding: this.findById(id)!, created: true };\n }\n });\n\n return upsertTx(input);\n }\n\n /**\n * ID で Finding を取得する。存在しなければ undefined。\n */\n findById(id: string): Finding | undefined {\n const row = this.selectFindingByIdStmt.get(id) as FindingRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToFinding(row);\n }\n\n /**\n * エンゲージメント ID で Finding 一覧を取得する。\n *\n * opts で state, severity を指定して絞り込み可能。\n * ORDER BY last_seen_at DESC。\n */\n findByEngagement(\n engagementId: string,\n opts?: { state?: string; severity?: string },\n ): Finding[] {\n const whereClauses: string[] = ['engagement_id = ?'];\n const params: unknown[] = [engagementId];\n\n if (opts?.state !== undefined) {\n whereClauses.push('state = ?');\n params.push(opts.state);\n }\n\n if (opts?.severity !== undefined) {\n whereClauses.push('severity = ?');\n params.push(opts.severity);\n }\n\n const sql = `SELECT id, engagement_id, canonical_key, node_id, title, severity, confidence,\n state, state_reason, owner, ticket_ref,\n first_seen_run_id, last_seen_run_id, first_seen_at, last_seen_at,\n sla_due_at, attrs_json\n FROM findings\n WHERE ${whereClauses.join(' AND ')}\n ORDER BY last_seen_at DESC`;\n\n const rows = this.db.prepare(sql).all(...params) as FindingRow[];\n return rows.map(rowToFinding);\n }\n\n /**\n * Finding の状態を更新する。\n *\n * state と state_reason を更新し、'state_change' イベントを自動追加する。\n * 存在しない ID の場合 undefined を返す。\n */\n updateState(id: string, state: string, reason?: string): Finding | undefined {\n const updateStateTx = this.db.transaction(\n (findingId: string, newState: string, stateReason?: string) => {\n // 既存 Finding を取得\n const existing = this.findById(findingId);\n if (existing === undefined) {\n return undefined;\n }\n\n const oldState = existing.state;\n\n // state と state_reason を更新\n this.updateStateStmt.run(newState, stateReason ?? null, findingId);\n\n // state_change イベントを追加\n const now = new Date().toISOString();\n this.insertEventStmt.run(\n crypto.randomUUID(),\n findingId,\n null, // run_id\n 'state_change',\n JSON.stringify({ state: oldState }),\n JSON.stringify({ state: newState }),\n null, // artifact_id\n now,\n );\n\n return this.findById(findingId);\n },\n );\n\n return updateStateTx(id, state, reason);\n }\n\n /**\n * Finding にイベントを手動追加する。\n */\n addEvent(findingId: string, event: CreateFindingEventInput): FindingEvent {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n this.insertEventStmt.run(\n id,\n findingId,\n event.runId ?? null,\n event.eventType,\n event.beforeJson ?? '{}',\n event.afterJson ?? '{}',\n event.artifactId ?? null,\n now,\n );\n\n return {\n id,\n findingId,\n ...(event.runId !== undefined ? { runId: event.runId } : {}),\n eventType: event.eventType,\n beforeJson: event.beforeJson ?? '{}',\n afterJson: event.afterJson ?? '{}',\n ...(event.artifactId !== undefined ? { artifactId: event.artifactId } : {}),\n createdAt: now,\n };\n }\n\n /**\n * Finding のイベント一覧を取得する。\n *\n * ORDER BY created_at DESC(最新が先頭)。\n */\n getEvents(findingId: string): FindingEvent[] {\n const rows = this.selectEventsByFindingStmt.all(findingId) as FindingEventRow[];\n return rows.map(rowToFindingEvent);\n }\n\n /**\n * Finding を削除する。\n *\n * CASCADE により関連する finding_events も同時に削除される。\n *\n * @returns 削除成功時 true、id が存在しない場合 false。\n */\n delete(id: string): boolean {\n const result = this.deleteFindingStmt.run(id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — RiskSnapshotRepository\n *\n * risk_snapshots テーブルに対する CRUD 操作。\n * エンゲージメントごとのリスクスコア時系列管理を提供する。\n */\n\nimport type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { RiskSnapshot, CreateRiskSnapshotInput } from '../../types/operational.js';\n\n// ---------------------------------------------------------------------------\n// DB row 型\n// ---------------------------------------------------------------------------\n\n/** better-sqlite3 から返る risk_snapshots テーブルの行形状 */\ninterface RiskSnapshotRow {\n id: string;\n engagement_id: string;\n run_id: string | null;\n score: number;\n open_critical: number;\n open_high: number;\n open_medium: number;\n open_low: number;\n open_info: number;\n open_total: number;\n attack_path_count: number;\n exposed_cred_count: number;\n model_version: string | null;\n attrs_json: string;\n created_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Row → RiskSnapshot 変換\n// ---------------------------------------------------------------------------\n\n/** snake_case DB row を camelCase RiskSnapshot にマッピング */\nfunction rowToRiskSnapshot(row: RiskSnapshotRow): RiskSnapshot {\n return {\n id: row.id,\n engagementId: row.engagement_id,\n ...(row.run_id !== null ? { runId: row.run_id } : {}),\n score: row.score,\n openCritical: row.open_critical,\n openHigh: row.open_high,\n openMedium: row.open_medium,\n openLow: row.open_low,\n openInfo: row.open_info,\n openTotal: row.open_total,\n attackPathCount: row.attack_path_count,\n exposedCredCount: row.exposed_cred_count,\n ...(row.model_version !== null ? { modelVersion: row.model_version } : {}),\n attrsJson: row.attrs_json,\n createdAt: row.created_at,\n };\n}\n\n// ---------------------------------------------------------------------------\n// RiskSnapshotRepository\n// ---------------------------------------------------------------------------\n\n/**\n * risk_snapshots テーブルの CRUD リポジトリ。\n *\n * - デフォルト値: 全 integer フィールドは 0、attrsJson は '{}'、created_at は現在時刻\n * - ID は crypto.randomUUID() で生成\n * - findByEngagement() は created_at DESC でソートし、デフォルト limit は 100\n * - latest() は最新のスナップショットを1件返す\n */\nexport class RiskSnapshotRepository {\n private readonly db: Database.Database;\n\n private readonly insertStmt: Database.Statement;\n private readonly selectByIdStmt: Database.Statement;\n private readonly selectByEngagementStmt: Database.Statement;\n private readonly latestStmt: Database.Statement;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertStmt = this.db.prepare(\n `INSERT INTO risk_snapshots\n (id, engagement_id, run_id, score,\n open_critical, open_high, open_medium, open_low, open_info, open_total,\n attack_path_count, exposed_cred_count, model_version,\n attrs_json, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n this.selectByIdStmt = this.db.prepare(\n `SELECT id, engagement_id, run_id, score,\n open_critical, open_high, open_medium, open_low, open_info, open_total,\n attack_path_count, exposed_cred_count, model_version,\n attrs_json, created_at\n FROM risk_snapshots WHERE id = ?`,\n );\n\n this.selectByEngagementStmt = this.db.prepare(\n `SELECT id, engagement_id, run_id, score,\n open_critical, open_high, open_medium, open_low, open_info, open_total,\n attack_path_count, exposed_cred_count, model_version,\n attrs_json, created_at\n FROM risk_snapshots\n WHERE engagement_id = ?\n ORDER BY created_at DESC\n LIMIT ?`,\n );\n\n this.latestStmt = this.db.prepare(\n `SELECT id, engagement_id, run_id, score,\n open_critical, open_high, open_medium, open_low, open_info, open_total,\n attack_path_count, exposed_cred_count, model_version,\n attrs_json, created_at\n FROM risk_snapshots\n WHERE engagement_id = ?\n ORDER BY created_at DESC\n LIMIT 1`,\n );\n }\n\n /**\n * RiskSnapshot を新規作成して返す。\n *\n * デフォルト値:\n * - openCritical, openHigh, openMedium, openLow, openInfo, openTotal: 0\n * - attackPathCount: 0\n * - exposedCredCount: 0\n * - attrsJson: '{}'\n * - createdAt: 現在時刻(ISO 8601)\n */\n create(input: CreateRiskSnapshotInput): RiskSnapshot {\n const id = crypto.randomUUID();\n const createdAt = new Date().toISOString();\n\n const runId = input.runId ?? null;\n const openCritical = input.openCritical ?? 0;\n const openHigh = input.openHigh ?? 0;\n const openMedium = input.openMedium ?? 0;\n const openLow = input.openLow ?? 0;\n const openInfo = input.openInfo ?? 0;\n const openTotal = input.openTotal ?? 0;\n const attackPathCount = input.attackPathCount ?? 0;\n const exposedCredCount = input.exposedCredCount ?? 0;\n const modelVersion = input.modelVersion ?? null;\n const attrsJson = input.attrsJson ?? '{}';\n\n this.insertStmt.run(\n id,\n input.engagementId,\n runId,\n input.score,\n openCritical,\n openHigh,\n openMedium,\n openLow,\n openInfo,\n openTotal,\n attackPathCount,\n exposedCredCount,\n modelVersion,\n attrsJson,\n createdAt,\n );\n\n return {\n id,\n engagementId: input.engagementId,\n ...(runId !== null ? { runId } : {}),\n score: input.score,\n openCritical,\n openHigh,\n openMedium,\n openLow,\n openInfo,\n openTotal,\n attackPathCount,\n exposedCredCount,\n ...(modelVersion !== null ? { modelVersion } : {}),\n attrsJson,\n createdAt,\n };\n }\n\n /**\n * ID で RiskSnapshot を取得する。存在しなければ undefined。\n */\n findById(id: string): RiskSnapshot | undefined {\n const row = this.selectByIdStmt.get(id) as RiskSnapshotRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToRiskSnapshot(row);\n }\n\n /**\n * エンゲージメントに紐づく RiskSnapshot 一覧を取得する。\n *\n * created_at DESC でソートし、limit で件数を制限する(デフォルト 100)。\n *\n * @param engagementId エンゲージメント ID\n * @param limit 最大取得件数(デフォルト 100)\n * @returns RiskSnapshot 一覧(created_at DESC 順)\n */\n findByEngagement(engagementId: string, limit?: number): RiskSnapshot[] {\n const effectiveLimit = limit ?? 100;\n const rows = this.selectByEngagementStmt.all(engagementId, effectiveLimit) as RiskSnapshotRow[];\n return rows.map(rowToRiskSnapshot);\n }\n\n /**\n * エンゲージメントの最新 RiskSnapshot を取得する。\n *\n * スナップショットが存在しない場合は undefined を返す。\n *\n * @param engagementId エンゲージメント ID\n * @returns 最新の RiskSnapshot、または undefined\n */\n latest(engagementId: string): RiskSnapshot | undefined {\n const row = this.latestStmt.get(engagementId) as RiskSnapshotRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToRiskSnapshot(row);\n }\n}\n","/**\r\n * sonobat — MCP Resources\r\n *\r\n * Read-only resources for browsing the AttackDataGraph.\r\n * Uses the graph-native nodes/edges schema.\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\nimport { TechniqueDocRepository } from '../db/repository/technique-doc-repository.js';\r\nimport { NODE_KINDS } from '../types/graph.js';\r\n\r\nexport function registerResources(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n const techDocRepo = new TechniqueDocRepository(db);\r\n\r\n // 1. sonobat://nodes?kind=host — Node list (replaces sonobat://hosts)\r\n server.resource(\r\n 'nodes',\r\n 'sonobat://nodes',\r\n {\r\n description:\r\n 'List of all nodes in the AttackDataGraph (optionally filter by kind via query param)',\r\n },\r\n async (uri) => {\r\n const kindParam = uri.searchParams?.get('kind');\r\n let nodes;\r\n if (kindParam && NODE_KINDS.includes(kindParam as (typeof NODE_KINDS)[number])) {\r\n nodes = nodeRepo.findByKind(kindParam as (typeof NODE_KINDS)[number]);\r\n } else {\r\n // Return all nodes (summary view)\r\n nodes = NODE_KINDS.flatMap((k) => nodeRepo.findByKind(k));\r\n }\r\n const result = nodes.map((n) => ({ ...n, props: JSON.parse(n.propsJson) }));\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify(result, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 2. sonobat://nodes/{id} — Node detail (replaces sonobat://hosts/{id})\r\n server.resource(\r\n 'node-detail',\r\n 'sonobat://nodes/{id}',\r\n { description: 'Detailed node with adjacent edges and neighbor nodes' },\r\n async (uri) => {\r\n const nodeId = uri.pathname.split('/').pop() ?? '';\r\n const node = nodeRepo.findById(nodeId);\r\n if (!node) {\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify({ error: `Node not found: ${nodeId}` }),\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const outEdges = edgeRepo.findBySource(node.id);\r\n const inEdges = edgeRepo.findByTarget(node.id);\r\n const adjacentNodeIds = new Set([\r\n ...outEdges.map((e) => e.targetId),\r\n ...inEdges.map((e) => e.sourceId),\r\n ]);\r\n const adjacentNodes = [...adjacentNodeIds]\r\n .map((nid) => nodeRepo.findById(nid))\r\n .filter(Boolean)\r\n .map((n) => ({ ...n!, props: JSON.parse(n!.propsJson) }));\r\n\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n outEdges,\r\n inEdges,\r\n adjacentNodes,\r\n };\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify(result, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 3. sonobat://summary — Statistics summary\r\n server.resource(\r\n 'summary',\r\n 'sonobat://summary',\r\n { description: 'Summary statistics of the AttackDataGraph' },\r\n async () => {\r\n const nodeCounts: Record<string, number> = {};\r\n for (const k of NODE_KINDS) {\r\n nodeCounts[k] = nodeRepo.findByKind(k).length;\r\n }\r\n const edgeCount = (db.prepare('SELECT COUNT(*) AS cnt FROM edges').get() as { cnt: number })\r\n .cnt;\r\n const artifactCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM artifacts').get() as { cnt: number }\r\n ).cnt;\r\n\r\n return {\r\n contents: [\r\n {\r\n uri: 'sonobat://summary',\r\n mimeType: 'application/json',\r\n text: JSON.stringify(\r\n { nodes: nodeCounts, edges: edgeCount, artifacts: artifactCount },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 4. sonobat://techniques/categories — Technique categories\r\n server.resource(\r\n 'technique-categories',\r\n 'sonobat://techniques/categories',\r\n { description: 'List of all technique documentation categories' },\r\n async () => {\r\n const categories = techDocRepo.listCategories();\r\n return {\r\n contents: [\r\n {\r\n uri: 'sonobat://techniques/categories',\r\n mimeType: 'application/json',\r\n text: JSON.stringify(categories, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n}\r\n"],"mappings":";;;AAOA,SAAS,oBAAoB;AAC7B,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;AAC9B,OAAO,cAAc;AACrB,SAAS,4BAA4B;;;ACDrC,IAAM,YAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,aAAa;AAAA,EACb,GAAGA,KAA6B;AAC9B,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA4LP;AAAA,EACH;AACF;AAEA,IAAO,aAAQ;;;ACpMf,IAAMC,aAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,aAAa;AAAA,EACb,GAAGC,KAA6B;AAC9B,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAaP;AAAA,EACH;AACF;AAEA,IAAO,aAAQD;;;ACrBf,IAAME,aAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,aAAa;AAAA,EACb,GAAGC,KAA6B;AAC9B,IAAAA,IAAG,KAAK;AAAA;AAAA,KAEP;AAAA,EACH;AACF;AAEA,IAAO,aAAQD;;;ACVf,IAAME,aAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,aAAa;AAAA,EACb,GAAGC,KAA6B;AAC9B,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAmCP;AAAA,EACH;AACF;AAEA,IAAO,aAAQD;;;AC5Cf,SAAS,kBAAkB;AAE3B,IAAME,aAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,aAAa;AAAA,EACb,GAAGC,KAA6B;AAI9B,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA0BP;AAOD,UAAM,aAAaA,IAAG,QAAQ;AAAA;AAAA;AAAA,KAG7B;AAED,UAAM,aAAaA,IAAG,QAAQ;AAAA;AAAA;AAAA,KAG7B;AAGD,UAAM,cAAc,CAAC,SAA0B;AAC7C,YAAM,MAAMA,IACT,QAAQ,yEAAyE,EACjF,IAAI,IAAI;AACX,aAAO,IAAI,MAAM;AAAA,IACnB;AAGA,QAAI,YAAY,OAAO,GAAG;AACxB,YAAM,QAAQA,IAAG,QAAQ,qBAAqB,EAAE,IAAI;AACpD,iBAAW,KAAK,OAAO;AACrB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,eAAe,EAAE;AAAA,UACjB,WAAW,EAAE;AAAA,UACb,iBAAiB,EAAE,qBAAqB;AAAA,QAC1C,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,QAAQ,EAAE,SAAS;AAAA,UACnB;AAAA,UACA;AAAA;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,QAAQ,GAAG;AACzB,YAAM,SAASA,IAAG,QAAQ,sBAAsB,EAAE,IAAI;AACtD,iBAAW,KAAK,QAAQ;AACtB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,UAAU,EAAE;AAAA,UACZ,QAAQ,EAAE,UAAU;AAAA,QACtB,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,SAAS,EAAE,OAAO,IAAI,EAAE,QAAQ;AAAA,UAChC;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA;AAAA,QACJ;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,UAAU,GAAG;AAC3B,YAAM,WAAWA,IAAG,QAAQ,wBAAwB,EAAE,IAAI;AAC1D,iBAAW,KAAK,UAAU;AACxB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,WAAW,EAAE;AAAA,UACb,MAAM,EAAE;AAAA,UACR,UAAU,EAAE;AAAA,UACZ,iBAAiB,EAAE;AAAA,UACnB,QAAQ,EAAE,UAAU;AAAA,UACpB,SAAS,EAAE,WAAW;AAAA,UACtB,SAAS,EAAE,WAAW;AAAA,UACtB,OAAO,EAAE;AAAA,QACX,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,OAAO,EAAE,OAAO,IAAI,EAAE,SAAS,IAAI,EAAE,IAAI;AAAA,UACzC;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,sBAAsB,GAAG;AACvC,YAAM,SAASA,IAAG,QAAQ,oCAAoC,EAAE,IAAI;AAGpE,iBAAW,MAAM,QAAQ;AACvB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,KAAK,GAAG;AAAA,UACR,OAAO,GAAG;AAAA,UACV,YAAY,GAAG;AAAA,QACjB,CAAC;AACD,mBAAW;AAAA,UACT,GAAG;AAAA,UACH;AAAA,UACA,UAAU,GAAG,EAAE;AAAA,UACf;AAAA,UACA,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA;AAAA,QACL;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,gBAAgB,GAAG;AACjC,YAAM,YAAYA,IAAG,QAAQ,8BAA8B,EAAE,IAAI;AAGjE,iBAAW,MAAM,WAAW;AAC1B,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,SAAS,GAAG;AAAA,UACZ,QAAQ,GAAG;AAAA,UACX,MAAM,GAAG;AAAA,UACT,YAAY,GAAG,eAAe;AAAA,UAC9B,eAAe,GAAG,kBAAkB;AAAA,UACpC,OAAO,GAAG,SAAS;AAAA,UACnB,OAAO,GAAG,SAAS;AAAA,QACrB,CAAC;AACD,mBAAW;AAAA,UACT,GAAG;AAAA,UACH;AAAA,UACA,MAAM,GAAG,UAAU,IAAI,GAAG,MAAM,IAAI,GAAG,IAAI;AAAA,UAC3C;AAAA,UACA,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA;AAAA,QACL;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA,QACL;AAEA,YAAI,GAAG,UAAU;AACf,qBAAW;AAAA,YACT,WAAW;AAAA,YACX;AAAA,YACA,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,UACL;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,QAAQ,GAAG;AACzB,YAAM,SAASA,IAAG,QAAQ,sBAAsB,EAAE,IAAI;AACtD,iBAAW,OAAO,QAAQ;AACxB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,UAAU,IAAI;AAAA,UACd,MAAM,IAAI;AAAA,UACV,UAAU,IAAI,aAAa;AAAA,QAC7B,CAAC;AACD,mBAAW;AAAA,UACT,IAAI;AAAA,UACJ;AAAA,UACA,MAAM,IAAI,UAAU,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI;AAAA,UAChD;AAAA,UACA;AAAA;AAAA,UACA,IAAI;AAAA,UACJ,IAAI;AAAA,QACN;AACA,mBAAW,IAAI,WAAW,GAAG,iBAAiB,IAAI,YAAY,IAAI,IAAI,MAAM,IAAI,UAAU;AAAA,MAC5F;AAAA,IACF;AAGA,QAAI,YAAY,iBAAiB,GAAG;AAClC,YAAM,WAAWA,IAAG,QAAQ,+BAA+B,EAAE,IAAI;AAGjE,iBAAW,MAAM,UAAU;AACzB,mBAAW;AAAA,UACT,GAAG;AAAA,UACH;AAAA,UACA,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA,UACH,GAAG;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,cAAc,GAAG;AAC/B,YAAM,MAAMA,IAAG,QAAQ,4BAA4B,EAAE,IAAI;AACzD,iBAAW,KAAK,KAAK;AACnB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,UAAU,EAAE;AAAA,UACZ,WAAW,EAAE;AAAA,UACb,UAAU,EAAE,aAAa;AAAA,UACzB,QAAQ,EAAE;AAAA,UACV,YAAY,EAAE;AAAA,UACd,YAAY,EAAE;AAAA,QAChB,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,OAAO,EAAE,EAAE;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA;AAAA,QACJ;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,aAAa,GAAG;AAC9B,YAAM,QAAQA,IAAG,QAAQ,2BAA2B,EAAE,IAAI;AAC1D,iBAAW,KAAK,OAAO;AACrB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,UAAU,EAAE;AAAA,UACZ,QAAQ,EAAE;AAAA,UACV,YAAY,EAAE;AAAA,UACd,QAAQ,EAAE;AAAA,UACV,YAAY,EAAE;AAAA,QAChB,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,QAAQ,EAAE,EAAE;AAAA,UACZ;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA;AAAA,QACJ;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AACA,YAAI,EAAE,aAAa;AACjB,qBAAW;AAAA,YACT,WAAW;AAAA,YACX;AAAA,YACA,EAAE;AAAA,YACF,EAAE;AAAA,YACF,EAAE;AAAA,YACF,EAAE;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,iBAAiB,GAAG;AAClC,YAAM,QAAQA,IAAG,QAAQ,+BAA+B,EAAE,IAAI;AAG9D,iBAAW,KAAK,OAAO;AACrB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,UAAU,EAAE;AAAA,UACZ,OAAO,EAAE;AAAA,UACT,aAAa,EAAE,eAAe;AAAA,UAC9B,UAAU,EAAE;AAAA,UACZ,YAAY,EAAE;AAAA,UACd,QAAQ,EAAE,UAAU;AAAA,QACtB,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,QAAQ,EAAE,EAAE;AAAA,UACZ;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA;AAAA,QACJ;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,UACF,EAAE;AAAA,QACJ;AACA,YAAI,EAAE,aAAa;AACjB,qBAAW;AAAA,YACT,WAAW;AAAA,YACX;AAAA,YACA,EAAE;AAAA,YACF,EAAE;AAAA,YACF,EAAE;AAAA,YACF,EAAE;AAAA,UACJ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,MAAM,GAAG;AACvB,YAAM,OAAOA,IAAG,QAAQ,oBAAoB,EAAE,IAAI;AAClD,iBAAW,KAAK,MAAM;AACpB,cAAM,QAAQ,KAAK,UAAU;AAAA,UAC3B,OAAO,EAAE;AAAA,UACT,aAAa,EAAE,eAAe;AAAA,UAC9B,WAAW,EAAE,cAAc;AAAA,UAC3B,YAAY,EAAE,eAAe;AAAA,UAC7B,cAAc,EAAE,iBAAiB;AAAA,QACnC,CAAC;AACD,mBAAW;AAAA,UACT,EAAE;AAAA,UACF;AAAA,UACA,OAAO,EAAE,gBAAgB,IAAI,EAAE,MAAM;AAAA,UACrC;AAAA,UACA;AAAA;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA;AAAA,QACJ;AACA,mBAAW;AAAA,UACT,WAAW;AAAA,UACX;AAAA,UACA,EAAE;AAAA,UACF,EAAE;AAAA,UACF;AAAA,UACA,EAAE;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAOA,IAAAA,IAAG,OAAO,oBAAoB;AAE9B,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,eAAW,SAAS,cAAc;AAChC,UAAI,YAAY,KAAK,GAAG;AACtB,QAAAA,IAAG,KAAK,cAAc,KAAK,EAAE;AAAA,MAC/B;AAAA,IACF;AAGA,IAAAA,IAAG,OAAO,mBAAmB;AAAA,EAC/B;AACF;AAEA,IAAO,aAAQD;;;AC1af,SAAS,cAAAE,mBAAkB;AAG3B,IAAMC,aAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,aACE;AAAA,EACF,GAAGC,KAA6B;AAI9B,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,KAKP;AAOD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAaP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAeP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA8BP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAwBP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KA4BP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAgBP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAsBP;AAQD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA,KAIP;AAGD,IAAAA,IAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAMP;AAKD,UAAM,YACJA,IAAG,QAAQ,mCAAmC,EAAE,IAAI,EACpD;AAEF,QAAI,YAAY,GAAG;AACjB,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,YAAM,eAAeF,YAAW;AAGhC,MAAAE,IAAG;AAAA,QACD;AAAA;AAAA,MAEF,EAAE,IAAI,cAAc,WAAW,OAAO,MAAM,MAAM,UAAU,KAAK,GAAG;AAGpE,MAAAA,IAAG,QAAQ,gEAAgE,EAAE;AAAA,QAC3E;AAAA,MACF;AAGA,MAAAA,IAAG,QAAQ,oEAAoE,EAAE;AAAA,QAC/E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,aAAQD;;;ACnPf,IAAM,aAA0B,CAAC,YAAI,YAAI,YAAI,YAAI,YAAI,UAAE;AAGhD,IAAM,iBACX,WAAW,SAAS,IAAI,WAAW,WAAW,SAAS,CAAC,EAAE,UAAU;AAK/D,SAAS,iBAAiBE,KAA+B;AAC9D,QAAM,MAAMA,IAAG,QAAQ,qBAAqB,EAAE,IAAI;AAGlD,SAAO,IAAI;AACb;AAKO,SAAS,iBAAiBA,KAAuB,SAAuB;AAC7E,EAAAA,IAAG,OAAO,kBAAkB,OAAO,EAAE;AACvC;AAQO,SAAS,cAAcA,KAAuB,gBAA8B;AACjF,aAAWC,cAAa,YAAY;AAClC,QAAIA,WAAU,UAAU,gBAAgB;AACtC,YAAM,eAAeD,IAAG,YAAY,MAAM;AACxC,QAAAC,WAAU,GAAGD,GAAE;AAAA,MACjB,CAAC;AACD,mBAAa;AACb,uBAAiBA,KAAIC,WAAU,OAAO;AAAA,IACxC;AAAA,EACF;AACF;;;AClDO,SAAS,gBAAgBC,KAA6B;AAC3D,EAAAA,IAAG,OAAO,mBAAmB;AAE7B,QAAM,iBAAiB,iBAAiBA,GAAE;AAE1C,MAAI,kBAAkB,gBAAgB;AACpC;AAAA,EACF;AAEA,MAAI,mBAAmB,GAAG;AAExB,UAAM,aACJA,IACG;AAAA,MACC;AAAA,IACF,EACC,IAAI,EACP;AAEF,QAAI,eAAe,GAAG;AAEpB,oBAAcA,KAAI,EAAE;AACpB;AAAA,IACF;AAGA,kBAAcA,KAAI,CAAC;AACnB;AAAA,EACF;AAGA,gBAAcA,KAAI,cAAc;AAClC;;;ACrCA,SAAS,iBAAiB;;;ACK1B,SAAS,KAAAC,UAAS;;;ACHlB,OAAOC,aAAY;;;ACAnB,SAAS,SAAS;AAClB,SAAS,cAAAC,mBAAkB;AAMpB,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,eAAe,EAAE,KAAK,CAAC,MAAM,QAAQ,CAAC;AAAA,EACtC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,iBAAiB,EAAE,OAAO,EAAE,QAAQ,IAAI;AAC1C,CAAC;AAGM,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,QAAQ,EAAE,OAAO,EAAE,SAAS;AAC9B,CAAC;AAGM,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACnC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,iBAAiB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACjC,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AACzB,CAAC;AAGM,IAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACtC,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACnC,CAAC;AAGM,IAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,UAAU,EAAE,OAAO,EAAE,SAAS;AAChC,CAAC;AAGM,IAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,UAAU,EAAE,OAAO;AAAA,EACnB,WAAW,EAAE,OAAO;AAAA,EACpB,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAGM,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,UAAU,EAAE,OAAO;AAAA,EACnB,QAAQ,EAAE,OAAO;AAAA,EACjB,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAGM,IAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,YAAY;AAChD,CAAC;AAGM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,cAAc,EAAE,OAAO,EAAE,SAAS;AACpC,CAAC;AAGM,IAAM,4BAA4B,EAAE,OAAO;AAAA,EAChD,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACrB,OAAO,EAAE,OAAO;AAAA,EAChB,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAC9B,CAAC;AAID,IAAM,mBAAmD;AAAA,EACvD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,KAAK;AAAA,EACL,iBAAiB;AACnB;AAmCO,SAAS,cAAc,MAAgB,OAAgC;AAC5E,QAAM,SAAS,iBAAiB,IAAI;AACpC,QAAM,SAAS,OAAO,UAAU,KAAK;AACrC,MAAI,OAAO,SAAS;AAClB,WAAO,EAAE,IAAI,MAAM,MAAM,OAAO,KAAK;AAAA,EACvC;AACA,SAAO,EAAE,IAAI,OAAO,OAAO,OAAO,MAAM,QAAQ;AAClD;AAmBO,SAAS,gBAAgB,MAAgB,OAAgB,UAA2B;AACzF,QAAM,IAAI;AAEV,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,QAAQ,EAAE,SAAS;AAAA,IAE5B,KAAK;AACH,aAAO,SAAS,QAAQ,IAAI,EAAE,QAAQ;AAAA,IAExC,KAAK;AACH,aAAO,OAAO,QAAQ,IAAI,EAAE,SAAS,IAAI,EAAE,IAAI;AAAA,IAEjD,KAAK;AACH,aAAO,MAAM,QAAQ,IAAI,EAAE,MAAM,IAAI,EAAE,IAAI;AAAA,IAE7C,KAAK;AACH,aAAO,MAAM,QAAQ,IAAI,EAAE,QAAQ,IAAI,EAAE,IAAI;AAAA,IAE/C,KAAK;AACH,aAAO,OAAO,QAAQ,IAAI,EAAE,KAAK;AAAA,IAEnC,KAAK;AACH,aAAO,OAAOA,YAAW,CAAC;AAAA,IAE5B,KAAK;AACH,aAAO,QAAQA,YAAW,CAAC;AAAA,IAE7B,KAAK;AACH,aAAO,QAAQA,YAAW,CAAC;AAAA,IAE7B,KAAK;AACH,aAAO,UAAUA,YAAW,CAAC;AAAA,EACjC;AACF;;;ADrNA,SAAS,eAAe,KAAyB;AAC/C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,GAAI,IAAI,yBAAyB,OAAO,EAAE,oBAAoB,IAAI,qBAAqB,IAAI,CAAC;AAAA,IAC5F,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAaO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA,IAEF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA,IAEF;AAEA,SAAK,mBAAmB,KAAK,GAAG;AAAA,MAC9B;AAAA;AAAA,IAEF;AAEA,SAAK,yBAAyB,KAAK,GAAG;AAAA,MACpC;AAAA;AAAA,IAEF;AAEA,SAAK,kBAAkB,KAAK,GAAG;AAAA,MAC7B;AAAA,IACF;AAEA,SAAK,aAAa,KAAK,GAAG,QAAQ,gCAAgC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OACE,MACA,OACA,oBACA,UACW;AAEX,UAAM,aAAa,cAAc,MAAM,KAAK;AAC5C,QAAI,CAAC,WAAW,IAAI;AAClB,YAAM,IAAI,MAAM,qCAAqC,IAAI,MAAM,WAAW,KAAK,EAAE;AAAA,IACnF;AAEA,UAAM,KAAKC,QAAO,WAAW;AAC7B,UAAM,aAAa,gBAAgB,MAAM,WAAW,MAAM,QAAQ;AAClE,UAAM,YAAY,KAAK,UAAU,WAAW,IAAI;AAChD,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,SAAK,WAAW;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,sBAAsB;AAAA,MACtB;AAAA,MACA;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,uBAAuB,SAAY,EAAE,mBAAmB,IAAI,CAAC;AAAA,MACjE,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OACE,MACA,OACA,oBACA,UACuC;AAEvC,UAAM,aAAa,cAAc,MAAM,KAAK;AAC5C,QAAI,CAAC,WAAW,IAAI;AAClB,YAAM,IAAI,MAAM,qCAAqC,IAAI,MAAM,WAAW,KAAK,EAAE;AAAA,IACnF;AAEA,UAAM,aAAa,gBAAgB,MAAM,WAAW,MAAM,QAAQ;AAGlE,UAAM,WAAW,KAAK,iBAAiB,UAAU;AAEjD,QAAI,aAAa,QAAW;AAE1B,YAAM,YAAY,KAAK,UAAU,WAAW,IAAI;AAChD,YAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,WAAK,gBAAgB,IAAI,WAAW,WAAW,SAAS,EAAE;AAE1D,YAAM,UAAU,KAAK,SAAS,SAAS,EAAE;AACzC,aAAO,EAAE,MAAM,SAAS,SAAS,MAAM;AAAA,IACzC;AAGA,UAAM,OAAO,KAAK,OAAO,MAAM,OAAO,oBAAoB,QAAQ;AAClE,WAAO,EAAE,MAAM,SAAS,KAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAmC;AAC1C,UAAM,MAAM,KAAK,eAAe,IAAI,EAAE;AACtC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,eAAe,GAAG;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,WAAW,MAAgB,SAAgD;AACzE,QAAI,YAAY,UAAa,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AAC9D,YAAMC,QAAO,KAAK,iBAAiB,IAAI,IAAI;AAC3C,aAAOA,MAAK,IAAI,cAAc;AAAA,IAChC;AAGA,UAAM,aAAa,OAAO,KAAK,OAAO;AACtC,UAAM,eAAe,WAAW,IAAI,CAAC,SAAS,yCAAyC;AACvF,UAAM,MAAM;AAAA;AAAA,sCAEsB,aAAa,KAAK,OAAO,CAAC;AAE5D,UAAM,SAAoB,CAAC,IAAI;AAC/B,eAAW,OAAO,YAAY;AAC5B,aAAO,KAAK,KAAK,QAAQ,GAAG,CAAC;AAAA,IAC/B;AAEA,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAC/C,WAAO,KAAK,IAAI,cAAc;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,YAA2C;AAC1D,UAAM,MAAM,KAAK,uBAAuB,IAAI,UAAU;AACtD,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,eAAe,GAAG;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,IAAY,OAAuD;AAE7E,UAAM,WAAW,KAAK,SAAS,EAAE;AACjC,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AAGA,UAAM,aAAa,cAAc,SAAS,MAAM,KAAK;AACrD,QAAI,CAAC,WAAW,IAAI;AAClB,YAAM,IAAI,MAAM,qCAAqC,SAAS,IAAI,MAAM,WAAW,KAAK,EAAE;AAAA,IAC5F;AAEA,UAAM,YAAY,KAAK,UAAU,WAAW,IAAI;AAChD,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,SAAK,gBAAgB,IAAI,WAAW,WAAW,EAAE;AAEjD,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAqB;AAC1B,UAAM,SAAS,KAAK,WAAW,IAAI,EAAE;AACrC,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;;;AE5QA,OAAOC,aAAY;AAenB,SAAS,UAAU,KAAyB;AAC1C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,GAAI,IAAI,yBAAyB,OAAO,EAAE,oBAAoB,IAAI,qBAAqB,IAAI,CAAC;AAAA,IAC5F,WAAW,IAAI;AAAA,EACjB;AACF;AAQO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA,IACF;AAEA,SAAK,2BAA2B,KAAK,GAAG;AAAA,MACtC;AAAA,IACF;AAEA,SAAK,qBAAqB,KAAK,GAAG;AAAA,MAChC;AAAA,IACF;AAEA,SAAK,4BAA4B,KAAK,GAAG;AAAA,MACvC;AAAA,IACF;AAEA,SAAK,qBAAqB,KAAK,GAAG;AAAA,MAChC;AAAA,IACF;AAEA,SAAK,4BAA4B,KAAK,GAAG;AAAA,MACvC;AAAA,IACF;AAEA,SAAK,mBAAmB,KAAK,GAAG;AAAA,MAC9B;AAAA,IACF;AAEA,SAAK,aAAa,KAAK,GAAG,QAAQ,gCAAgC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OACE,MACA,UACA,UACA,oBACA,WACW;AACX,UAAM,KAAKD,QAAO,WAAW;AAC7B,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,QAAQ,aAAa;AAE3B,SAAK,WAAW,IAAI,IAAI,MAAM,UAAU,UAAU,OAAO,sBAAsB,MAAM,SAAS;AAE9F,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,GAAI,uBAAuB,SAAY,EAAE,mBAAmB,IAAI,CAAC;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OACE,MACA,UACA,UACA,oBACA,WACuC;AACvC,UAAM,WAAW,KAAK,yBAAyB,IAAI,MAAM,UAAU,QAAQ;AAI3E,QAAI,aAAa,QAAW;AAC1B,aAAO,EAAE,MAAM,UAAU,QAAQ,GAAG,SAAS,MAAM;AAAA,IACrD;AAEA,UAAM,OAAO,KAAK,OAAO,MAAM,UAAU,UAAU,oBAAoB,SAAS;AAChF,WAAO,EAAE,MAAM,SAAS,KAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,UAAkB,UAAkC;AAC/D,QAAI,aAAa,QAAW;AAC1B,YAAME,QAAO,KAAK,0BAA0B,IAAI,UAAU,QAAQ;AAClE,aAAOA,MAAK,IAAI,SAAS;AAAA,IAC3B;AACA,UAAM,OAAO,KAAK,mBAAmB,IAAI,QAAQ;AACjD,WAAO,KAAK,IAAI,SAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,UAAkB,UAAkC;AAC/D,QAAI,aAAa,QAAW;AAC1B,YAAMA,QAAO,KAAK,0BAA0B,IAAI,UAAU,QAAQ;AAClE,aAAOA,MAAK,IAAI,SAAS;AAAA,IAC3B;AACA,UAAM,OAAO,KAAK,mBAAmB,IAAI,QAAQ;AACjD,WAAO,KAAK,IAAI,SAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAA6B;AACtC,UAAM,OAAO,KAAK,iBAAiB,IAAI,IAAI;AAC3C,WAAO,KAAK,IAAI,SAAS;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAqB;AAC1B,UAAM,SAAS,KAAK,WAAW,IAAI,EAAE;AACrC,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;;;ACjJA,SAAS,UAAU,KAAyB;AAC1C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,oBAAoB,IAAI,wBAAwB;AAAA,IAChD,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,SAASC,WAAU,KAAyB;AAC1C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,oBAAoB,IAAI,wBAAwB;AAAA,IAChD,WAAW,IAAI;AAAA,EACjB;AACF;AA6BO,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,SAAS,SAAiB,UAAmB,WAA2C;AACtF,UAAM,QAAQ,YAAY;AAG1B,UAAM,YAAY,KAAK,GACpB,QAA2B,kCAAkC,EAC7D,IAAI,OAAO;AACd,QAAI,CAAC,WAAW;AACd,aAAO,CAAC;AAAA,IACV;AAGA,QAAI,aAAa;AACjB,UAAM,SAAoB,CAAC,SAAS,SAAS,KAAK;AAElD,QAAI,aAAa,UAAU,SAAS,GAAG;AACrC,YAAM,eAAe,UAAU,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACvD,mBAAa,kBAAkB,YAAY;AAC3C,aAAO,KAAK,GAAG,SAAS;AAAA,IAC1B;AAEA,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAQJ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAclB,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG;AAChC,UAAM,OAAO,KAAK,IAAI,GAAG,MAAM;AAG/B,UAAM,OAAO,oBAAI,IAA6B;AAE9C,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,KAAK,IAAI,IAAI,EAAE,KAAK,KAAK,IAAI,IAAI,EAAE,EAAG,QAAQ,IAAI,YAAY;AACjE,aAAK,IAAI,IAAI,IAAI;AAAA,UACf,MAAM,UAAU,GAAG;AAAA,UACnB,OAAO,IAAI;AAAA,UACX,MAAM,IAAI,UAAU,MAAM,GAAG;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAc,QAAgB,YAAoC;AAEhE,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,QAAkB,CAAC,MAAM;AAC/B,YAAQ,IAAI,MAAM;AAElB,UAAM,UAAuB,CAAC;AAE9B,UAAM,WAAW,KAAK,GAAG,QAA2B,yCAAyC;AAC7F,UAAM,WAAW,KAAK,GAAG,QAA2B,kCAAkC;AAEtF,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,YAAY,MAAM,MAAM;AAC9B,YAAM,QAAQ,SAAS,IAAI,SAAS;AAEpC,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,QAAQ,IAAI,KAAK,SAAS,GAAG;AAChC,kBAAQ,IAAI,KAAK,SAAS;AAC1B,gBAAM,KAAK,KAAK,SAAS;AAEzB,gBAAM,aAAa,SAAS,IAAI,KAAK,SAAS;AAC9C,cAAI,YAAY;AACd,kBAAM,YAAY,UAAU,UAAU;AACtC,gBAAI,eAAe,UAAa,UAAU,SAAS,YAAY;AAC7D,sBAAQ,KAAK,SAAS;AAAA,YACxB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,UAAkB,UAA0C;AACvE,UAAM,WAAW,KAAK,GAAG,QAA2B,kCAAkC;AAGtF,QAAI,aAAa,UAAU;AACzB,YAAM,OAAO,SAAS,IAAI,QAAQ;AAClC,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,OAAO,CAAC,UAAU,IAAI,CAAC;AAAA,QACvB,OAAO,CAAC;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,GAAG,QAA2B,yCAAyC;AAG7F,UAAM,SAAS,oBAAI,IAAiD;AACpE,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,QAAkB,CAAC,QAAQ;AACjC,YAAQ,IAAI,QAAQ;AAEpB,QAAI,QAAQ;AAEZ,WAAO,MAAM,SAAS,KAAK,CAAC,OAAO;AACjC,YAAMC,aAAY,MAAM,MAAM;AAC9B,YAAM,QAAQ,SAAS,IAAIA,UAAS;AAEpC,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,QAAQ,IAAI,KAAK,SAAS,GAAG;AAChC,kBAAQ,IAAI,KAAK,SAAS;AAC1B,iBAAO,IAAI,KAAK,WAAW,EAAE,UAAUA,YAAW,KAAK,CAAC;AACxD,cAAI,KAAK,cAAc,UAAU;AAC/B,oBAAQ;AACR;AAAA,UACF;AACA,gBAAM,KAAK,KAAK,SAAS;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAGA,UAAM,cAAwB,CAAC;AAC/B,UAAM,YAAyB,CAAC;AAEhC,QAAI,YAAY;AAChB,WAAO,cAAc,UAAU;AAC7B,kBAAY,QAAQ,SAAS;AAC7B,YAAM,OAAO,OAAO,IAAI,SAAS;AACjC,gBAAU,QAAQF,WAAU,KAAK,IAAI,CAAC;AACtC,kBAAY,KAAK;AAAA,IACnB;AACA,gBAAY,QAAQ,QAAQ;AAG5B,UAAM,QAAqB,CAAC;AAC5B,eAAW,OAAO,aAAa;AAC7B,YAAM,MAAM,SAAS,IAAI,GAAG;AAC5B,UAAI,KAAK;AACP,cAAM,KAAK,UAAU,GAAG,CAAC;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP,QAAQ,UAAU;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,UAAU,SAAiB,QAAgD;AACzE,YAAQ,SAAS;AAAA,MACf,KAAK;AACH,eAAO,KAAK,oBAAoB;AAAA,MAClC,KAAK;AACH,eAAO,KAAK,oBAAoB;AAAA,MAClC,KAAK;AACH,eAAO,KAAK,yBAAyB;AAAA,MACvC,KAAK;AACH,eAAO,KAAK,wBAAwB;AAAA,MACtC,KAAK;AACH,eAAO,KAAK,iBAAiB;AAAA,MAC/B,KAAK;AACH,eAAO,KAAK,wBAAwB,MAAM;AAAA,MAC5C;AACE,cAAM,IAAI,MAAM,2BAA2B,OAAO,EAAE;AAAA,IACxD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,sBAAoC;AAC1C,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBZ,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI;AAEtC,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,MACf,cAAc,IAAI;AAAA,MAClB,YAAY,IAAI;AAAA,MAChB,eAAe,IAAI;AAAA,MACnB,SAAS,IAAI,YAAY;AAAA,MACzB,YAAY,IAAI,eAAe;AAAA,IACjC,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAoC;AAC1C,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBZ,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI;AAEtC,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,MACf,cAAc,IAAI;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,UAAU,IAAI;AAAA,MACd,OAAO,IAAI;AAAA,IACb,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,2BAAyC;AAC/C,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaZ,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI;AAEtC,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,WAAW,IAAI;AAAA,MACf,cAAc,IAAI;AAAA,MAClB,cAAc,IAAI;AAAA,MAClB,iBAAiB,IAAI;AAAA,IACvB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,0BAAwC;AAC9C,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeZ,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI;AAEtC,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,WAAW,IAAI;AAAA,MACf,cAAc,IAAI;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,IACjB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAiC;AACvC,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeZ,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI;AAEtC,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,WAAW,IAAI;AAAA,IACjB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,wBAAwB,QAAgD;AAC9E,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AAEA,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYZ,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,MAAM;AAE5C,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,WAAW,IAAI;AAAA,MACf,cAAc,IAAI;AAAA,MAClB,UAAU,IAAI;AAAA,IAChB,EAAE;AAAA,EACJ;AACF;;;AJ/eO,SAAS,kBAAkBG,SAAmBC,KAA6B;AAChF,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,iBAAiB,IAAI,qBAAqBA,GAAE;AAElD,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQE,GAAE,KAAK,CAAC,cAAc,YAAY,YAAY,WAAW,cAAc,CAAC;AAAA;AAAA,MAEhF,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kDAAkD;AAAA;AAAA,MAEvF,IAAIA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,SAAS;AAAA;AAAA,MAE5C,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6BAA6B;AAAA,MACrE,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA,MAC3D,WAAWA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,sBAAsB;AAAA;AAAA,MAEzE,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA;AAAA,MAE7D,aAAaA,GACV,OAAO,EACP,SAAS,EACT,SAAS,+DAA+D;AAAA,IAC7E;AAAA,IACA,OAAO,EAAE,QAAQ,MAAM,IAAI,SAAS,OAAO,WAAW,SAAS,YAAY,MAAM;AAC/E,cAAQ,QAAQ;AAAA,QACd,KAAK,cAAc;AACjB,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,yCAAyC,CAAC;AAAA,cAC1E,SAAS;AAAA,YACX;AAAA,UACF;AAEA,cAAI,CAAC,WAAW,SAAS,IAAgB,GAAG;AAC1C,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,iBAAiB,IAAI,YAAY,WAAW,KAAK,IAAI,CAAC;AAAA,gBAC9D;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,UAAU,cACX,KAAK,MAAM,WAAW,IACvB;AACJ,gBAAM,QAAQ,SAAS,WAAW,MAAkB,OAAO;AAC3D,gBAAM,SAAS,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,EAAE;AAC1E,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA,QACA,KAAK,YAAY;AACf,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,qCAAqC,CAAC;AAAA,cACtE,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,OAAO,SAAS,SAAS,EAAE;AACjC,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,mBAAmB,EAAE,GAAG,CAAC;AAAA,cACzD,SAAS;AAAA,YACX;AAAA,UACF;AAEA,gBAAM,WAAW,SAAS,aAAa,KAAK,EAAE;AAC9C,gBAAM,UAAU,SAAS,aAAa,KAAK,EAAE;AAC7C,gBAAM,kBAAkB,oBAAI,IAAI;AAAA,YAC9B,GAAG,SAAS,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,YACjC,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,UAClC,CAAC;AACD,gBAAM,gBAAgB,CAAC,GAAG,eAAe,EACtC,IAAI,CAAC,QAAQ,SAAS,SAAS,GAAG,CAAC,EACnC,OAAO,OAAO;AACjB,gBAAM,SAAS;AAAA,YACb,GAAG;AAAA,YACH,OAAO,KAAK,MAAM,KAAK,SAAS;AAAA,YAChC;AAAA,YACA;AAAA,YACA,eAAe,cAAc,IAAI,CAAC,OAAO;AAAA,cACvC,GAAG;AAAA,cACH,OAAO,KAAK,MAAM,EAAG,SAAS;AAAA,YAChC,EAAE;AAAA,UACJ;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA,QACA,KAAK,YAAY;AACf,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,0CAA0C,CAAC;AAAA,cAC3E,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,iBAAiB,WAAW,OAAO,CAAC,OAAO,WAAW,SAAS,EAAc,CAAC;AAGpF,gBAAM,UAAU,eAAe,SAAS,SAAS,OAAO,cAAc;AACtE,gBAAM,WAAW,QAAQ,IAAI,CAAC,OAAO;AAAA,YACnC,GAAG;AAAA,YACH,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE;AAAA,UACzD,EAAE;AACF,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAChF;AAAA,QACA,KAAK,WAAW;AAEd,gBAAM,aAAqC,CAAC;AAC5C,qBAAW,KAAK,YAAY;AAC1B,uBAAW,CAAC,IAAI,SAAS,WAAW,CAAC,EAAE;AAAA,UACzC;AACA,gBAAM,aAAqC,CAAC;AAC5C,qBAAW,MAAM,YAAY;AAC3B,uBAAW,EAAE,IAAI,SAAS,WAAW,EAAE,EAAE;AAAA,UAC3C;AAEA,gBAAM,gBACJD,IAAG,QAAQ,uCAAuC,EAAE,IAAI,EACxD;AACF,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK;AAAA,kBACT,EAAE,OAAO,YAAY,OAAO,YAAY,WAAW,cAAc;AAAA,kBACjE;AAAA,kBACA;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QACA,KAAK,gBAAgB;AACnB,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8CAA8C,CAAC;AAAA,cAC/E,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI;AACF,kBAAM,UAAU,eAAe,UAAU,OAAO;AAChD,mBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,UAC/E,SAAS,OAAO;AACd,kBAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,uBAAuB,OAAO,GAAG,CAAC;AAAA,cAClE,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AKlKA,SAAS,KAAAE,UAAS;AAWlB,SAAS,0BAA0BC,KAA+B;AAChE,QAAM,MAAMA,IAAG,QAAQ,wDAAwD,EAAE,IAAI;AAGrF,MAAI,IAAK,QAAO,IAAI;AAEpB,QAAM,KAAK,OAAO,WAAW;AAC7B,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,EAAAA,IAAG;AAAA,IACD;AAAA,EACF,EAAE,IAAI,IAAI,GAAG;AACb,SAAO;AACT;AAEO,SAAS,mBAAmBC,SAAmBD,KAA6B;AACjF,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,WAAW,IAAI,eAAeA,GAAE;AAEtC,EAAAC,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQC,GAAE,KAAK,CAAC,YAAY,YAAY,eAAe,aAAa,CAAC;AAAA;AAAA,MAErE,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,2CAA2C;AAAA,MAChF,WAAWA,GACR,OAAO,EACP,SAAS,EACT;AAAA,QACC;AAAA,MACF;AAAA,MACF,UAAUA,GACP,OAAO,EACP,SAAS,EACT,SAAS,oEAAoE;AAAA;AAAA,MAEhF,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kDAAkD;AAAA,MAC3F,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yBAAyB;AAAA,MAClE,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yBAAyB;AAAA;AAAA,MAElE,IAAIA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8BAA8B;AAAA;AAAA,MAEjE,oBAAoBA,GACjB,OAAO,EACP,SAAS,EACT,SAAS,+EAA+E;AAAA,IAC7F;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAAM;AAEJ,UAAI;AACJ,UAAI,cAAc;AAChB,YAAI;AACF,kBAAQ,KAAK,MAAM,YAAY;AAAA,QACjC,QAAQ;AACN,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8BAA8B,YAAY,GAAG,CAAC;AAAA,YAC9E,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA,QAId,KAAK,YAAY;AACf,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,0CAA0C,CAAC;AAAA,cAC3E,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,WAAW,SAAS,IAAgB,GAAG;AAC1C,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,iBAAiB,IAAI,kBAAkB,WAAW,KAAK,IAAI,CAAC;AAAA,gBACpE;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,OAAO;AACV,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,+CAA+C,CAAC;AAAA,cAChF,SAAS;AAAA,YACX;AAAA,UACF;AAGA,gBAAM,aAAa,cAAc,MAAkB,KAAK;AACxD,cAAI,CAAC,WAAW,IAAI;AAClB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,qCAAqC,IAAI,MAAM,WAAW,KAAK;AAAA,gBACvE;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AAEA,gBAAM,aAAa,sBAAsB,0BAA0BF,GAAE;AACrE,gBAAM,EAAE,MAAM,QAAQ,IAAI,SAAS,OAAO,MAAkB,OAAO,YAAY,QAAQ;AACvF,gBAAM,SAAS;AAAA,YACb,GAAG;AAAA,YACH,OAAO,KAAK,MAAM,KAAK,SAAS;AAAA,YAChC;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,YAAY;AACf,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8CAA8C,CAAC;AAAA,cAC/E,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,WAAW,SAAS,QAAoB,GAAG;AAC9C,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,qBAAqB,QAAQ,kBAAkB,WAAW,KAAK,IAAI,CAAC;AAAA,gBAC5E;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8CAA8C,CAAC;AAAA,cAC/E,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8CAA8C,CAAC;AAAA,cAC/E,SAAS;AAAA,YACX;AAAA,UACF;AAEA,gBAAM,aAAa,sBAAsB,0BAA0BA,GAAE;AACrE,gBAAM,EAAE,MAAM,QAAQ,IAAI,SAAS;AAAA,YACjC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,EAAE,GAAG,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC;AAAA,UACjF;AAAA,QACF;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,eAAe;AAClB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,2CAA2C,CAAC;AAAA,cAC5E,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,OAAO;AACV,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,kDAAkD,CAAC;AAAA,cACnF,SAAS;AAAA,YACX;AAAA,UACF;AAGA,gBAAM,WAAW,SAAS,SAAS,EAAE;AACrC,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,mBAAmB,EAAE,GAAG,CAAC;AAAA,cACzD,SAAS;AAAA,YACX;AAAA,UACF;AAGA,gBAAM,gBAAgB,KAAK,MAAM,SAAS,SAAS;AACnD,gBAAM,cAAc,EAAE,GAAG,eAAe,GAAG,MAAM;AAGjD,gBAAM,aAAa,cAAc,SAAS,MAAM,WAAW;AAC3D,cAAI,CAAC,WAAW,IAAI;AAClB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,qCAAqC,SAAS,IAAI,MAAM,WAAW,KAAK;AAAA,gBAChF;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AAEA,gBAAM,UAAU,SAAS,YAAY,IAAI,WAAW;AACpD,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,0BAA0B,EAAE,GAAG,CAAC;AAAA,cAChE,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAS,EAAE,GAAG,SAAS,OAAO,KAAK,MAAM,QAAQ,SAAS,EAAE;AAClE,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,eAAe;AAClB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,2CAA2C,CAAC;AAAA,cAC5E,SAAS;AAAA,YACX;AAAA,UACF;AAEA,gBAAM,UAAU,SAAS,OAAO,EAAE;AAClC,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,mBAAmB,EAAE,GAAG,CAAC;AAAA,cACzD,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,EAAE,yBAAyB,CAAC;AAAA,UACtE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACxQA,SAAS,KAAAG,UAAS;;;ACClB,OAAO,QAAQ;AACf,OAAOC,aAAY;AACnB,OAAO,UAAU;;;ACJjB,SAAS,iBAAiB;;;ACwHnB,SAAS,mBAAgC;AAC9C,SAAO;AAAA,IACL,OAAO,CAAC;AAAA,IACR,UAAU,CAAC;AAAA,IACX,qBAAqB,CAAC;AAAA,IACtB,eAAe,CAAC;AAAA,IAChB,QAAQ,CAAC;AAAA,IACT,gBAAgB,CAAC;AAAA,IACjB,cAAc,CAAC;AAAA,IACf,iBAAiB,CAAC;AAAA,IAClB,MAAM,CAAC;AAAA,EACT;AACF;;;AD3DA,SAAS,YAAe,OAAwC;AAC9D,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO,CAAC;AAAA,EACV;AACA,SAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAC9C;AAGA,SAAS,kBAAkB,MAAkC;AAC3D,QAAM,IAAI,SAAS,SAAY,OAAO,IAAI,IAAI;AAC9C,MAAI,MAAM,GAAI,QAAO;AACrB,MAAI,KAAK,EAAG,QAAO;AACnB,SAAO;AACT;AAGA,SAAS,eAAe,UAA0B;AAChD,QAAM,IAAI,OAAO,QAAQ;AACzB,MAAI,KAAK,GAAI,QAAO;AACpB,MAAI,KAAK,GAAI,QAAO;AACpB,SAAO;AACT;AAGA,SAAS,QAAQ,SAAmC;AAClD,SAAO,QAAQ,QAAQ,MAAM,WAAW,QAAQ,UAAU,MAAM;AAClE;AAGA,SAAS,YAAY,SAA8C;AACjE,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ,WAAW,EAAG,OAAM,KAAK,QAAQ,WAAW,CAAC;AACzD,MAAI,QAAQ,WAAW,EAAG,OAAM,KAAK,QAAQ,WAAW,CAAC;AACzD,MAAI,QAAQ,aAAa,EAAG,OAAM,KAAK,QAAQ,aAAa,CAAC;AAC7D,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI;AAC9C;AAGA,SAAS,eAAe,WAA8C;AACpE,QAAM,OAAO,UAAU,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,MAAM;AAC7D,SAAO,OAAO,QAAQ;AACxB;AAYO,SAAS,aAAa,KAA0B;AACrD,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,wBAAwB;AAAA,EAC1B,CAAC;AAED,QAAM,SAAkB,OAAO,MAAM,GAAG;AACxC,QAAM,UAAU;AAEhB,QAAM,SAAS,iBAAiB;AAEhC,QAAM,QAAQ,YAAY,QAAQ,SAAS,IAAI;AAE/C,aAAW,QAAQ,OAAO;AACxB,gBAAY,MAAM,MAAM;AAAA,EAC1B;AAEA,SAAO;AACT;AAKA,SAAS,YAAY,MAAgB,QAA2B;AAE9D,QAAM,YAAY,YAAY,KAAK,OAAO;AAC1C,QAAM,YAAY,eAAe,SAAS;AAC1C,MAAI,cAAc,QAAW;AAC3B;AAAA,EACF;AAEA,QAAM,aAAyB;AAAA,IAC7B;AAAA,IACA,eAAe;AAAA,EACjB;AACA,SAAO,MAAM,KAAK,UAAU;AAG5B,QAAM,QAAQ,YAAY,KAAK,OAAO,IAAI;AAC1C,QAAM,WAA4B,CAAC;AAEnC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,YAAY,MAAM,SAAS;AAC3C,aAAS,KAAK,OAAO;AACrB,WAAO,SAAS,KAAK,OAAO;AAAA,EAC9B;AAGA,QAAM,YAAY,YAAY,KAAK,IAAI,OAAO;AAC9C,MAAI,UAAU,SAAS,GAAG;AACxB,qBAAiB,WAAW,WAAW,UAAU,MAAM;AAAA,EACzD;AACF;AAKA,SAAS,YAAY,MAAgB,eAAsC;AACzE,QAAM,UAAU,KAAK;AACrB,QAAM,cAAc,UAAU,QAAQ,KAAK;AAG3C,QAAM,WAAW,YAAY,UAAa,QAAQ,OAAO,IAAI,UAAU;AAEvE,QAAM,SAAS,YAAY,SAAY,YAAY,OAAO,IAAI;AAC9D,QAAM,kBAAkB,kBAAkB,UAAU,QAAQ,CAAC;AAE7D,SAAO;AAAA,IACL;AAAA,IACA,WAAW,KAAK,YAAY;AAAA,IAC5B,MAAM,OAAO,KAAK,UAAU,CAAC;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,UAAU,WAAW;AAAA,IAC9B,SAAS,UAAU,WAAW;AAAA,IAC9B,OAAO,KAAK,MAAM,SAAS;AAAA,EAC7B;AACF;AAMA,SAAS,iBACP,WACA,eACA,UACA,QACM;AACN,QAAM,eAAe,SAAS,SAAS,IAAI,SAAS,CAAC,IAAI;AACzD,QAAMC,aAAY,cAAc,aAAa;AAC7C,QAAM,OAAO,cAAc,QAAQ;AAEnC,aAAW,WAAW,WAAW;AAC/B,UAAM,cAAwC;AAAA,MAC5C;AAAA,MACA,WAAAA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,OAAO,QAAQ,QAAQ;AAAA,MACvB,YAAY,eAAe,QAAQ,YAAY,CAAC;AAAA,IAClD;AACA,WAAO,oBAAoB,KAAK,WAAW;AAAA,EAC7C;AACF;;;AEhMA,IAAM,WAAW;AAEjB,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAiB,KAAwB;AAChD,MAAI,CAAC,SAAS,GAAG,GAAG;AAClB,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAEA,MAAI,OAAO,IAAI,aAAa,MAAM,UAAU;AAC1C,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,QAAM,SAAS,IAAI,QAAQ;AAC3B,MAAI,CAAC,SAAS,MAAM,GAAG;AACrB,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,MAAI,OAAO,OAAO,KAAK,MAAM,YAAY,OAAO,OAAO,QAAQ,MAAM,UAAU;AAC7E,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AAEA,MAAI,CAAC,MAAM,QAAQ,IAAI,SAAS,CAAC,GAAG;AAClC,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,IAAI,SAAS,GAAgB;AAC9C,QAAI,CAAC,SAAS,IAAI,GAAG;AACnB,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AACA,YAAQ,KAAK;AAAA,MACX,OAAO,SAAS,KAAK,OAAO,CAAC,IACzB,OAAO;AAAA,QACL,OAAO,QAAQ,KAAK,OAAO,CAA4B,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM;AAAA,UACvE;AAAA,UACA,OAAO,CAAC;AAAA,QACV,CAAC;AAAA,MACH,IACA,CAAC;AAAA,MACL,QAAQ,OAAO,KAAK,QAAQ,MAAM,WAAW,KAAK,QAAQ,IAAI;AAAA,MAC9D,QAAQ,OAAO,KAAK,QAAQ,MAAM,WAAW,KAAK,QAAQ,IAAI;AAAA,MAC9D,OAAO,OAAO,KAAK,OAAO,MAAM,WAAW,KAAK,OAAO,IAAI;AAAA,MAC3D,OAAO,OAAO,KAAK,OAAO,MAAM,WAAW,KAAK,OAAO,IAAI;AAAA,MAC3D,KAAK,OAAO,KAAK,KAAK,MAAM,WAAW,KAAK,KAAK,IAAI;AAAA,MACrD,MAAM,OAAO,KAAK,MAAM,MAAM,WAAW,KAAK,MAAM,IAAI;AAAA,IAC1D,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,aAAa,IAAI,aAAa;AAAA,IAC9B,QAAQ;AAAA,MACN,KAAK,OAAO,KAAK;AAAA,MACjB,QAAQ,OAAO,QAAQ;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AACF;AAcA,SAAS,SAAS,QAA2B;AAC3C,QAAM,SAAS,IAAI,IAAI,MAAM;AAC7B,QAAM,SAAS,OAAO,SAAS,QAAQ,KAAK,EAAE;AAE9C,MAAI;AACJ,MAAI,OAAO,SAAS,IAAI;AACtB,WAAO,OAAO,OAAO,IAAI;AAAA,EAC3B,OAAO;AACL,WAAO,WAAW,UAAU,MAAM;AAAA,EACpC;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO;AAAA,IACjB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,cAAc,OAAO;AAAA,EACvB;AACF;AAEA,SAAS,uBAAuB,UAAmC;AACjE,SAAO,SAAS,KAAK,QAAQ,IAAI,OAAO;AAC1C;AAYO,SAAS,cAAc,aAAkC;AAC9D,QAAM,MAAe,KAAK,MAAM,WAAW;AAC3C,QAAM,OAAO,iBAAiB,GAAG;AACjC,QAAM,SAAS,KAAK,OAAO;AAG3B,MAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,WAAO,iBAAiB;AAAA,EAC1B;AAIA,QAAM,WAAW,oBAAI,IAAwB;AAE7C,QAAM,cAAc,oBAAI,IAA2B;AAEnD,QAAM,eAAe,oBAAI,IAAgC;AAEzD,QAAM,YAAY,oBAAI,IAAyB;AAE/C,QAAM,oBAAoB,oBAAI,IAAiC;AAE/D,QAAM,kBAAkB,oBAAI,IAA+B;AAE3D,aAAW,UAAU,KAAK,SAAS;AACjC,QAAI,OAAO,QAAQ,IAAI;AACrB;AAAA,IACF;AAEA,UAAM,SAAS,SAAS,OAAO,GAAG;AAClC,UAAM,EAAE,QAAQ,UAAU,MAAM,UAAU,aAAa,IAAI;AAC3D,UAAM,gBAAgB,uBAAuB,QAAQ;AACrD,UAAM,UAAU,GAAG,MAAM,MAAM,QAAQ,IAAI,IAAI;AAG/C,QAAI,CAAC,SAAS,IAAI,QAAQ,GAAG;AAC3B,eAAS,IAAI,UAAU;AAAA,QACrB,WAAW;AAAA,QACX;AAAA,MACF,CAAC;AAAA,IACH;AAGA,UAAM,aAAa,GAAG,QAAQ,IAAI,IAAI;AACtC,QAAI,CAAC,YAAY,IAAI,UAAU,GAAG;AAChC,kBAAY,IAAI,YAAY;AAAA,QAC1B,eAAe;AAAA,QACf,WAAW;AAAA,QACX;AAAA,QACA,UAAU;AAAA,QACV,iBAAiB;AAAA,QACjB,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAGA,UAAM,cAAc,GAAG,QAAQ,IAAI,IAAI,IAAI,MAAM,IAAI,QAAQ;AAC7D,QAAI,CAAC,aAAa,IAAI,WAAW,GAAG;AAClC,mBAAa,IAAI,aAAa;AAAA,QAC5B,eAAe;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,YAAY,OAAO;AAAA,QACnB,eAAe,OAAO;AAAA,QACtB,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,MAChB,CAAC;AAAA,IACH;AAGA,eAAW,CAAC,WAAW,UAAU,KAAK,aAAa,QAAQ,GAAG;AAE5D,YAAM,WAAW,GAAG,QAAQ,IAAI,IAAI,UAAU,SAAS;AACvD,UAAI,CAAC,UAAU,IAAI,QAAQ,GAAG;AAC5B,kBAAU,IAAI,UAAU;AAAA,UACtB,eAAe;AAAA,UACf;AAAA,UACA,UAAU;AAAA,UACV,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAGA,YAAM,QAAQ,GAAG,QAAQ,IAAI,IAAI,IAAI,MAAM,IAAI,QAAQ,UAAU,SAAS;AAC1E,UAAI,CAAC,kBAAkB,IAAI,KAAK,GAAG;AACjC,0BAAkB,IAAI,OAAO;AAAA,UAC3B,eAAe;AAAA,UACf;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN,eAAe;AAAA,UACf,WAAW;AAAA,QACb,CAAC;AAAA,MACH;AAGA,YAAM,SAAS,GAAG,QAAQ,IAAI,IAAI,UAAU,SAAS,IAAI,UAAU;AACnE,UAAI,CAAC,gBAAgB,IAAI,MAAM,GAAG;AAChC,wBAAgB,IAAI,QAAQ;AAAA,UAC1B,eAAe;AAAA,UACf;AAAA,UACA,eAAe;AAAA,UACf,WAAW;AAAA,UACX,UAAU;AAAA,UACV,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC;AAAA,IAC5B,UAAU,CAAC,GAAG,YAAY,OAAO,CAAC;AAAA,IAClC,qBAAqB,CAAC;AAAA,IACtB,eAAe,CAAC,GAAG,aAAa,OAAO,CAAC;AAAA,IACxC,QAAQ,CAAC,GAAG,UAAU,OAAO,CAAC;AAAA,IAC9B,gBAAgB,CAAC,GAAG,kBAAkB,OAAO,CAAC;AAAA,IAC9C,cAAc,CAAC,GAAG,gBAAgB,OAAO,CAAC;AAAA,IAC1C,iBAAiB,CAAC;AAAA,IAClB,MAAM,CAAC;AAAA,EACT;AACF;;;ACpOA,SAASC,UAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,cAAc,OAAmC;AACxD,SAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,SAAS,OAAO,SAAS,QAAQ;AAC/E;AAEA,SAAS,uBAAuB,OAA+C;AAC7E,MAAI,CAACA,UAAS,KAAK,EAAG,QAAO;AAE7B,MAAI,YAAY,SAAS,CAAC,cAAc,MAAM,QAAQ,CAAC,EAAG,QAAO;AACjE,MACE,kBAAkB,SAClB,OAAO,MAAM,cAAc,MAAM,YACjC,MAAM,cAAc,MAAM;AAE1B,WAAO;AACT,MACE,gBAAgB,SAChB,OAAO,MAAM,YAAY,MAAM,YAC/B,MAAM,YAAY,MAAM;AAExB,WAAO;AACT,SAAO;AACT;AAEA,SAAS,aAAa,OAAqC;AACzD,MAAI,CAACA,UAAS,KAAK,EAAG,QAAO;AAC7B,MAAI,OAAO,MAAM,SAAS,SAAU,QAAO;AAC3C,MAAI,OAAO,MAAM,aAAa,SAAU,QAAO;AAC/C,MAAI,CAAC,cAAc,MAAM,IAAI,EAAG,QAAO;AACvC,MACE,oBAAoB,SACpB,MAAM,mBAAmB,UACzB,CAAC,uBAAuB,MAAM,cAAc;AAE5C,WAAO;AACT,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAwC;AAC/D,MAAI,CAACA,UAAS,KAAK,EAAG,QAAO;AAC7B,MAAI,OAAO,MAAM,aAAa,MAAM,SAAU,QAAO;AACrD,MAAI,CAAC,aAAa,MAAM,IAAI,EAAG,QAAO;AACtC,MAAI,OAAO,MAAM,SAAS,SAAU,QAAO;AAC3C,MAAI,OAAO,MAAM,SAAS,SAAU,QAAO;AAC3C,MAAI,OAAO,MAAM,YAAY,MAAM,SAAU,QAAO;AACpD,MAAI,OAAO,MAAM,OAAO,SAAU,QAAO;AACzC,MAAI,OAAO,MAAM,SAAS,SAAU,QAAO;AAC3C,MAAI,OAAO,MAAM,WAAW,SAAU,QAAO;AAC7C,MAAI,OAAO,MAAM,QAAQ,SAAU,QAAO;AAC1C,SAAO;AACT;AAWA,SAAS,mBAAmB,QAAwB;AAGlD,QAAM,YAAY,OAAO,QAAQ,KAAK;AACtC,MAAI,cAAc,IAAI;AACpB,WAAO;AAAA,EACT;AACA,QAAM,iBAAiB,OAAO,QAAQ,KAAK,YAAY,CAAC;AACxD,MAAI,mBAAmB,IAAI;AACzB,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,OAAO,QAAQ,KAAK,cAAc;AACrD,QAAM,gBAAgB,OAAO,QAAQ,KAAK,cAAc;AACxD,MAAI,UAAU,OAAO;AACrB,MAAI,eAAe,MAAM,aAAa,SAAS;AAC7C,cAAU;AAAA,EACZ;AACA,MAAI,kBAAkB,MAAM,gBAAgB,SAAS;AACnD,cAAU;AAAA,EACZ;AACA,SAAO,OAAO,UAAU,gBAAgB,OAAO;AACjD;AAOA,SAAS,cAAc,MAAwB;AAC7C,QAAM,eAAe,CAAC,QAAQ,OAAO,OAAO,OAAO,MAAM;AACzD,aAAW,OAAO,cAAc;AAC9B,QAAI,KAAK,SAAS,GAAG,GAAG;AACtB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAYO,SAAS,iBAAiB,OAA4B;AAC3D,QAAM,SAAS,iBAAiB;AAEhC,MAAI,MAAM,KAAK,MAAM,IAAI;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,eAAe,oBAAI,IAAY;AAErC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,YAAY,IAAI;AAClB;AAAA,IACF;AAEA,UAAM,SAAkB,KAAK,MAAM,OAAO;AAC1C,QAAI,CAAC,gBAAgB,MAAM,GAAG;AAC5B;AAAA,IACF;AAEA,mBAAe,QAAQ,QAAQ,WAAW,YAAY;AAAA,EACxD;AAEA,SAAO;AACT;AAKA,SAAS,eACP,SACA,QACA,WACA,cACM;AACN,QAAM,KAAK,QAAQ;AACnB,QAAM,OAAO,OAAO,QAAQ,IAAI;AAChC,QAAM,SAAS,QAAQ;AACvB,QAAM,YAAY,QAAQ,YAAY;AAGtC,MAAI,CAAC,UAAU,IAAI,EAAE,GAAG;AACtB,cAAU,IAAI,EAAE;AAChB,UAAM,OAAmB;AAAA,MACvB,WAAW;AAAA,MACX,eAAe;AAAA,IACjB;AACA,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAGA,QAAM,aAAa,GAAG,EAAE,IAAI,IAAI;AAChC,MAAI,CAAC,aAAa,IAAI,UAAU,GAAG;AACjC,iBAAa,IAAI,UAAU;AAC3B,UAAM,UAAyB;AAAA,MAC7B,eAAe;AAAA,MACf,WAAW;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,OAAO;AAAA,IACT;AACA,WAAO,SAAS,KAAK,OAAO;AAAA,EAC9B;AAGA,QAAM,UAAU,mBAAmB,SAAS;AAC5C,QAAM,UAAU,GAAG,MAAM,MAAM,EAAE,IAAI,IAAI;AAEzC,QAAM,WAA+B;AAAA,IACnC,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,MAAM;AAAA,EACR;AACA,SAAO,cAAc,KAAK,QAAQ;AAGlC,QAAM,OAAO,QAAQ;AACrB,QAAM,gBAAqC;AAAA,IACzC,eAAe;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,UAAU,cAAc,KAAK,IAAI;AAAA,IACjC,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,YAAY;AAAA,EACd;AACA,SAAO,gBAAgB,KAAK,aAAa;AAGzC,QAAM,iBAAiB,KAAK;AAC5B,MACE,mBAAmB,UACnBA,UAAS,cAAc,KACvB,YAAY,kBACZ,cAAc,eAAe,QAAQ,CAAC,KACtC,eAAe,QAAQ,EAAE,SAAS,GAClC;AACA,eAAW,SAAS,eAAe,QAAQ,GAAG;AAC5C,YAAM,MAAiB;AAAA,QACrB,oBAAoB,KAAK;AAAA,QACzB;AAAA,QACA,WACE,OAAO,eAAe,YAAY,MAAM,WACpC,eAAe,YAAY,IAC3B;AAAA,QACN,YACE,OAAO,eAAe,cAAc,MAAM,WACtC,eAAe,cAAc,IAC7B;AAAA,MACR;AACA,aAAO,KAAK,KAAK,GAAG;AAAA,IACtB;AAAA,EACF;AACF;;;AC5OO,SAAS,UACdC,KACA,YACA,aACiB;AACjB,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,WAAW,IAAI,eAAeA,GAAE;AAEtC,QAAM,MAAMA,IAAG,YAAY,MAAuB;AAChD,UAAM,SAA0B;AAAA,MAC9B,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,4BAA4B;AAAA,MAC5B,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,uBAAuB;AAAA,MACvB,qBAAqB;AAAA,MACrB,wBAAwB;AAAA,MACxB,aAAa;AAAA,IACf;AAKA,UAAM,oBAAoB,oBAAI,IAAoB;AAElD,UAAM,iBAAiB,oBAAI,IAAoB;AAE/C,UAAM,kBAAkB,oBAAI,IAAoB;AAEhD,UAAM,eAAe,oBAAI,IAAoB;AAE7C,UAAM,gBAAgB,oBAAI,IAAoB;AAK9C,eAAW,UAAU,YAAY,OAAO;AACtC,YAAM,EAAE,MAAM,QAAQ,IAAI,SAAS,OAAO,QAAQ;AAAA,QAChD,eAAe,OAAO;AAAA,QACtB,WAAW,OAAO;AAAA,QAClB,iBAAiB,KAAK,UAAU,OAAO,eAAe,CAAC,CAAC;AAAA,MAC1D,CAAC;AAED,wBAAkB,IAAI,OAAO,WAAW,KAAK,EAAE;AAC/C,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF;AAKA,eAAW,UAAU,YAAY,UAAU;AACzC,YAAM,SAAS,kBAAkB,IAAI,OAAO,aAAa;AACzD,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,GAAG,MAAM,IAAI,OAAO,SAAS,IAAI,OAAO,IAAI;AAC3D,UAAI,eAAe,IAAI,MAAM,EAAG;AAEhC,YAAM,EAAE,MAAM,QAAQ,IAAI,SAAS;AAAA,QACjC;AAAA,QACA;AAAA,UACE,WAAW,OAAO;AAAA,UAClB,MAAM,OAAO;AAAA,UACb,UAAU,OAAO;AAAA,UACjB,iBAAiB,OAAO;AAAA,UACxB,QAAQ,OAAO;AAAA,UACf,SAAS,OAAO;AAAA,UAChB,SAAS,OAAO;AAAA,UAChB,OAAO,OAAO;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,qBAAe,IAAI,QAAQ,KAAK,EAAE;AAGlC,eAAS,OAAO,gBAAgB,QAAQ,KAAK,IAAI,UAAU;AAE3D,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF;AAMA,aAAS,iBAAiB,eAAuB,MAAkC;AACjF,YAAM,SAAS,kBAAkB,IAAI,aAAa;AAClD,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,eAAe,IAAI,GAAG,MAAM,QAAQ,IAAI,EAAE;AAAA,IACnD;AAKA,eAAW,UAAU,YAAY,qBAAqB;AACpD,YAAM,SAAS,kBAAkB,IAAI,OAAO,aAAa;AACzD,UAAI,CAAC,OAAQ;AAEb,YAAM,SAAS,GAAG,MAAM,IAAI,OAAO,SAAS,IAAI,OAAO,IAAI;AAC3D,YAAM,YAAY,eAAe,IAAI,MAAM;AAC3C,UAAI,CAAC,UAAW;AAEhB,YAAM,UAAU,SAAS;AAAA,QACvB;AAAA,QACA;AAAA,UACE,KAAK,OAAO;AAAA,UACZ,OAAO,OAAO;AAAA,UACd,YAAY,OAAO;AAAA,QACrB;AAAA,QACA;AAAA,MACF;AAEA,eAAS,OAAO,uBAAuB,WAAW,QAAQ,IAAI,UAAU;AACxE,aAAO;AAAA,IACT;AAKA,eAAW,UAAU,YAAY,eAAe;AAC9C,YAAM,YAAY,iBAAiB,OAAO,eAAe,OAAO,IAAI;AACpE,UAAI,CAAC,UAAW;AAEhB,YAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,MAAM,IAAI,OAAO,IAAI;AAC1D,UAAI,gBAAgB,IAAI,KAAK,EAAG;AAEhC,YAAM,EAAE,MAAM,QAAQ,IAAI,SAAS;AAAA,QACjC;AAAA,QACA;AAAA,UACE,SAAS,OAAO;AAAA,UAChB,QAAQ,OAAO;AAAA,UACf,MAAM,OAAO;AAAA,UACb,YAAY,OAAO;AAAA,UACnB,eAAe,OAAO;AAAA,UACtB,OAAO,OAAO;AAAA,UACd,OAAO,OAAO;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,sBAAgB,IAAI,OAAO,KAAK,EAAE;AAGlC,eAAS,OAAO,oBAAoB,WAAW,KAAK,IAAI,UAAU;AAElE,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF;AAKA,eAAW,UAAU,YAAY,QAAQ;AACvC,YAAM,YAAY,iBAAiB,OAAO,eAAe,OAAO,IAAI;AACpE,UAAI,CAAC,UAAW;AAEhB,YAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,QAAQ,IAAI,OAAO,IAAI;AAC5D,UAAI,aAAa,IAAI,KAAK,EAAG;AAE7B,YAAM,EAAE,MAAM,QAAQ,IAAI,SAAS;AAAA,QACjC;AAAA,QACA;AAAA,UACE,UAAU,OAAO;AAAA,UACjB,MAAM,OAAO;AAAA,UACb,UAAU,OAAO;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,mBAAa,IAAI,OAAO,KAAK,EAAE;AAG/B,eAAS,OAAO,iBAAiB,WAAW,KAAK,EAAE;AAEnD,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF;AAKA,eAAW,UAAU,YAAY,gBAAgB;AAC/C,YAAM,YAAY,iBAAiB,OAAO,eAAe,OAAO,IAAI;AACpE,UAAI,CAAC,UAAW;AAEhB,YAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,MAAM,IAAI,OAAO,IAAI;AAC1D,YAAM,aAAa,gBAAgB,IAAI,KAAK;AAC5C,UAAI,CAAC,WAAY;AAEjB,YAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS;AACtE,YAAM,UAAU,aAAa,IAAI,KAAK;AACtC,UAAI,CAAC,QAAS;AAEd,YAAM,EAAE,QAAQ,IAAI,SAAS,OAAO,kBAAkB,YAAY,SAAS,UAAU;AACrF,UAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,IACF;AAKA,eAAW,UAAU,YAAY,cAAc;AAC7C,YAAM,YAAY,iBAAiB,OAAO,eAAe,OAAO,IAAI;AACpE,UAAI,CAAC,UAAW;AAEhB,YAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,aAAa,IAAI,OAAO,SAAS;AACtE,YAAM,UAAU,aAAa,IAAI,KAAK;AACtC,UAAI,CAAC,QAAS;AAEd,YAAM,UAAU,SAAS;AAAA,QACvB;AAAA,QACA;AAAA,UACE,UAAU,OAAO;AAAA,UACjB,WAAW,OAAO;AAAA,UAClB,QAAQ,OAAO;AAAA,UACf,YAAY,OAAO;AAAA,UACnB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,QACrC;AAAA,QACA;AAAA,MACF;AAEA,eAAS,OAAO,qBAAqB,SAAS,QAAQ,IAAI,UAAU;AACpE,aAAO;AAAA,IACT;AAKA,eAAW,UAAU,YAAY,iBAAiB;AAChD,YAAM,YAAY,iBAAiB,OAAO,eAAe,OAAO,IAAI;AACpE,UAAI,CAAC,UAAW;AAGhB,UAAI;AACJ,UAAI,OAAO,UAAU,OAAO,MAAM;AAChC,cAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,MAAM,IAAI,OAAO,IAAI;AAC1D,qBAAa,gBAAgB,IAAI,KAAK;AAAA,MACxC;AAEA,YAAM,WAAW,SAAS;AAAA,QACxB;AAAA,QACA;AAAA,UACE,UAAU,OAAO;AAAA,UACjB,OAAO,OAAO;AAAA,UACd,aAAa,OAAO;AAAA,UACpB,UAAU,OAAO;AAAA,UACjB,YAAY,OAAO;AAAA,QACrB;AAAA,QACA;AAAA,MACF;AAEA,oBAAc,IAAI,OAAO,OAAO,SAAS,EAAE;AAG3C,eAAS,OAAO,yBAAyB,WAAW,SAAS,IAAI,UAAU;AAG3E,UAAI,YAAY;AACd,iBAAS,OAAO,0BAA0B,YAAY,SAAS,IAAI,UAAU;AAAA,MAC/E;AAEA,aAAO;AAAA,IACT;AAKA,eAAW,UAAU,YAAY,MAAM;AACrC,YAAM,SAAS,cAAc,IAAI,OAAO,kBAAkB;AAC1D,UAAI,CAAC,OAAQ;AAEb,YAAM,UAAU,SAAS;AAAA,QACvB;AAAA,QACA;AAAA,UACE,OAAO,OAAO;AAAA,UACd,aAAa,OAAO;AAAA,UACpB,WAAW,OAAO;AAAA,UAClB,YAAY,OAAO;AAAA,UACnB,cAAc,OAAO;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,eAAS,OAAO,qBAAqB,QAAQ,QAAQ,EAAE;AACvD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,IAAI;AACb;;;ALhUO,SAAS,cACdC,KACA,MACA,SACA,UACc;AAEd,QAAM,SAASC,QAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAGvE,QAAM,aAAaA,QAAO,WAAW;AACrC,QAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,EAAAD,IAAG;AAAA,IACD;AAAA,EACF,EAAE,IAAI,YAAY,MAAM,eAAe,UAAU,QAAQ,UAAU;AAGnE,MAAI;AACJ,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,oBAAc,aAAa,OAAO;AAClC;AAAA,IACF,KAAK;AACH,oBAAc,cAAc,OAAO;AACnC;AAAA,IACF,KAAK;AACH,oBAAc,iBAAiB,OAAO;AACtC;AAAA,IACF,SAAS;AAEP,YAAM,cAAqB;AAC3B,YAAM,IAAI,MAAM,iBAAiB,OAAO,WAAW,CAAC,EAAE;AAAA,IACxD;AAAA,EACF;AAGA,QAAM,kBAAkB,UAAUA,KAAI,YAAY,WAAW;AAG7D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAUO,SAAS,OAAOA,KAAuB,OAAkC;AAC9E,QAAM,WAAW,KAAK,QAAQ,MAAM,IAAI;AACxC,QAAM,UAAU,GAAG,aAAa,UAAU,OAAO;AACjD,SAAO,cAAcA,KAAI,MAAM,MAAM,SAAS,QAAQ;AACxD;;;AD1EO,SAAS,mBAAmBE,SAAmBC,KAA6B;AACjF,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,MAAME,GAAE,OAAO,EAAE,SAAS,uCAAuC;AAAA,MACjE,MAAMA,GAAE,KAAK,CAAC,QAAQ,QAAQ,QAAQ,CAAC,EAAE,SAAS,+BAA+B;AAAA,IACnF;AAAA,IACA,OAAO,EAAE,MAAAC,OAAM,KAAK,MAAM;AACxB,UAAI;AACF,cAAM,SAAS,OAAOF,KAAI,EAAE,MAAAE,OAAM,KAAK,CAAC;AACxC,cAAM,KAAK,OAAO;AAClB,cAAM,UAAU;AAAA,UACd,YAAY,IAAI,gBAAgBA,KAAI;AAAA,UACpC,gBAAgB,OAAO,UAAU;AAAA,UACjC,YAAY,GAAG,YAAY,WAAW,GAAG,eAAe,cAAc,GAAG,oBAAoB,eAAe,GAAG,aAAa,YAAY,GAAG,mBAAmB,kBAAkB,GAAG,sBAAsB,qBAAqB,GAAG,WAAW;AAAA,QAC9O,EAAE,KAAK,IAAI;AACX,eAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAE;AAAA,MACtD,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,eAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,kBAAkB,OAAO,GAAG,CAAC,GAAG,SAAS,KAAK;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AACF;;;AO3BA,SAAS,KAAAC,UAAS;;;ACQlB,SAAS,WAAW,MAA0C;AAC5D,SAAO,KAAK,MAAM,KAAK,SAAS;AAClC;AAUO,SAAS,QAAQC,KAAuB,QAA2B;AACxE,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,WAAW,IAAI,eAAeA,GAAE;AAEtC,QAAM,UAAoB,CAAC;AAG3B,MAAI;AACJ,MAAI,WAAW,QAAW;AACxB,UAAM,OAAO,SAAS,SAAS,MAAM;AACrC,QAAI,SAAS,QAAW;AACtB,aAAO,CAAC;AAAA,IACV;AACA,YAAQ,CAAC,IAAI;AAAA,EACf,OAAO;AACL,YAAQ,SAAS,WAAW,MAAM;AAAA,EACpC;AAEA,aAAW,QAAQ,OAAO;AACxB,UAAM,YAAY,WAAW,IAAI;AACjC,UAAM,YAAY,UAAU;AAG5B,UAAM,eAAe,SAAS,aAAa,KAAK,IAAI,cAAc;AAGlE,QAAI,aAAa,WAAW,GAAG;AAC7B,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,aAAa,aAAa,SAAS;AAAA,QACnC,SAAS,gBAAgB,SAAS;AAAA,QAClC,QAAQ,EAAE,QAAQ,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AAGA,eAAW,eAAe,cAAc;AACtC,YAAM,cAAc,SAAS,SAAS,YAAY,QAAQ;AAC1D,UAAI,gBAAgB,QAAW;AAC7B;AAAA,MACF;AAEA,YAAM,eAAe,WAAW,WAAW;AAC3C,YAAM,WAAW,aAAa;AAC9B,YAAM,OAAO,aAAa;AAC1B,YAAM,QAAQ,aAAa;AAE3B,UAAI,aAAa,UAAU,aAAa,SAAS;AAC/C;AAAA,MACF;AACA,UAAI,UAAU,UAAa,UAAU,QAAQ;AAC3C;AAAA,MACF;AAEA,YAAM,UAAU,GAAG,QAAQ,MAAM,SAAS,IAAI,IAAI;AAElD,4BAAsB,SAAS,MAAM,aAAa,WAAW,SAAS,UAAU,QAAQ;AAAA,IAC1F;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,sBACP,SACA,MACA,SACA,WACA,SACA,UACA,UACM;AAEN,QAAM,gBAAgB,SAAS,aAAa,QAAQ,IAAI,kBAAkB;AAG1E,MAAI,cAAc,WAAW,GAAG;AAC9B,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,aAAa,yBAAyB,OAAO;AAAA,MAC7C,SAAS,WAAW,OAAO;AAAA,MAC3B,QAAQ,EAAE,QAAQ,KAAK,IAAI,WAAW,QAAQ,GAAG;AAAA,IACnD,CAAC;AAAA,EACH;AAKA,QAAM,YAAY,SAAS,aAAa,QAAQ,IAAI,uBAAuB;AAC3E,QAAM,cAAc,UAAU,OAAO,CAAC,OAAO;AAC3C,UAAM,WAAW,SAAS,SAAS,GAAG,QAAQ;AAC9C,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AACA,UAAM,YAAY,WAAW,QAAQ;AACrC,WAAO,UAAU,WAAW;AAAA,EAC9B,CAAC;AAGD,aAAW,gBAAgB,eAAe;AACxC,UAAM,eAAe,SAAS,SAAS,aAAa,QAAQ;AAC5D,QAAI,iBAAiB,QAAW;AAC9B;AAAA,IACF;AAEA,UAAM,gBAAgB,WAAW,YAAY;AAC7C,UAAM,eAAe,cAAc;AAGnC,UAAM,aAAa,SAAS,aAAa,aAAa,IAAI,gBAAgB;AAE1E,QAAI,WAAW,WAAW,GAAG;AAC3B,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,aAAa,iCAAiC,OAAO,GAAG,YAAY;AAAA,QACpE,QAAQ,EAAE,QAAQ,KAAK,IAAI,WAAW,QAAQ,IAAI,YAAY,aAAa,GAAG;AAAA,MAChF,CAAC;AAAA,IACH;AAGA,eAAW,aAAa,YAAY;AAClC,YAAM,YAAY,SAAS,SAAS,UAAU,QAAQ;AACtD,UAAI,cAAc,QAAW;AAC3B;AAAA,MACF;AAEA,YAAM,aAAa,WAAW,SAAS;AACvC,YAAM,YAAY,WAAW;AAC7B,YAAM,gBAAgB,WAAW;AAGjC,YAAM,mBAAmB,SAAS,aAAa,UAAU,IAAI,mBAAmB;AAEhF,UAAI,iBAAiB,WAAW,GAAG;AACjC,gBAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN,aAAa,sCAAsC,SAAS,MAAM,aAAa;AAAA,UAC/E,QAAQ;AAAA,YACN,QAAQ,KAAK;AAAA,YACb,WAAW,QAAQ;AAAA,YACnB,YAAY,aAAa;AAAA,YACzB,SAAS,UAAU;AAAA,UACrB;AAAA,QACF,CAAC;AAAA,MACH,WAAW,YAAY,WAAW,GAAG;AAEnC,gBAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN,aAAa,eAAe,SAAS,MAAM,aAAa,QAAQ,OAAO,GAAG,YAAY;AAAA,UACtF,QAAQ;AAAA,YACN,QAAQ,KAAK;AAAA,YACb,WAAW,QAAQ;AAAA,YACnB,YAAY,aAAa;AAAA,YACzB,SAAS,UAAU;AAAA,UACrB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,SAAS,aAAa,KAAK,IAAI,YAAY;AAC9D,MAAI,WAAW,WAAW,GAAG;AAC3B,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,aAAa,8BAA8B,SAAS;AAAA,MACpD,QAAQ,EAAE,QAAQ,KAAK,IAAI,WAAW,QAAQ,GAAG;AAAA,IACnD,CAAC;AAAA,EACH;AAGA,MAAI,YAAY,WAAW,GAAG;AAC5B,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,aAAa,QAAQ,OAAO;AAAA,MAC5B,SAAS,aAAa,OAAO;AAAA,MAC7B,QAAQ,EAAE,QAAQ,KAAK,IAAI,WAAW,QAAQ,GAAG;AAAA,IACnD,CAAC;AAAA,EACH;AACF;;;ADzMO,SAAS,oBAAoBC,SAAmBC,KAA6B;AAClF,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQE,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,+CAA+C;AAAA,IACxF;AAAA,IACA,OAAO,EAAE,OAAO,MAAM;AACpB,YAAM,UAAU,QAAQD,KAAI,MAAM;AAClC,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO;AAAA,UACL,SAAS;AAAA,YACP,EAAE,MAAM,QAAQ,MAAM,6DAA6D;AAAA,UACrF;AAAA,QACF;AAAA,MACF;AACA,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,IAC/E;AAAA,EACF;AACF;;;AEjBA,OAAOE,SAAQ;AACf,SAAS,KAAAC,UAAS;;;ACblB,OAAOC,aAAY;AAiDnB,SAAS,kBAAkB,KAAoC;AAC7D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ,UAAU,IAAI;AAAA,IACd,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,SAAS,IAAI;AAAA,IACb,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAaA,IAAM,aAAa;AAOZ,IAAM,yBAAN,MAA6B;AAAA,EACjB;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAyC;AAC7C,QAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,OAAO,KAAK,GAAG;AAAA,MACnB;AAAA;AAAA,IAEF;AAEA,QAAI,WAAW;AAEf,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,YAAY;AAChD,YAAM,QAAQ,KAAK,MAAM,GAAG,IAAI,UAAU;AAC1C,YAAM,cAAc,KAAK,GAAG,YAAY,MAAM;AAC5C,mBAAW,OAAO,OAAO;AACvB,gBAAM,KAAKD,QAAO,WAAW;AAC7B,eAAK;AAAA,YACH;AAAA,YACA,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ;AAAA,YACA,IAAI,aAAa;AAAA,UACnB;AACA;AAAA,QACF;AAAA,MACF,CAAC;AACD,kBAAY;AAAA,IACd;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,OAAe,UAAyB,CAAC,GAA+B;AAC7E,UAAM,EAAE,QAAQ,IAAI,SAAS,IAAI;AAKjC,UAAM,QAAQ,MACX,KAAK,EACL,MAAM,KAAK,EACX,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,UAAM,YAAY,MAAM,IAAI,CAAC,MAAM,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG;AAEzE,QAAI;AACJ,UAAM,SAAoB,CAAC,SAAS;AAEpC,QAAI,UAAU;AACZ,YAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAON,aAAO,KAAK,UAAU,KAAK;AAAA,IAC7B,OAAO;AACL,YAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAMN,aAAO,KAAK,KAAK;AAAA,IACnB;AAEA,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAE/C,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,GAAG,kBAAkB,GAAG;AAAA,MACxB,OAAO,IAAI,OAAO;AAAA,IACpB,EAAE;AAAA,EACJ;AAAA;AAAA,EAGA,iBAA2B;AACzB,UAAM,OAAO,KAAK,GACf,QAAQ,gEAAgE,EACxE,IAAI;AACP,WAAO,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,EACnC;AAAA;AAAA,EAGA,eAAe,UAAkC;AAC/C,UAAM,OAAO,KAAK,GACf;AAAA,MACC;AAAA;AAAA;AAAA;AAAA,IAIF,EACC,IAAI,QAAQ;AACf,WAAO,KAAK,IAAI,iBAAiB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,QAAwB;AACrC,UAAM,SAAS,KAAK,GAAG,QAAQ,6CAA6C,EAAE,IAAI,MAAM;AACxF,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA,EAGA,QAAgB;AACd,UAAM,MAAM,KAAK,GAAG,QAAQ,4CAA4C,EAAE,IAAI;AAG9E,WAAO,IAAI;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmB,QAA4C;AAC7D,UAAM,OAAO,KAAK,GACf;AAAA,MACC;AAAA,IACF,EACC,IAAI,MAAM;AAEb,UAAM,MAAM,oBAAI,IAA2B;AAC3C,eAAW,OAAO,MAAM;AACtB,UAAI,IAAI,IAAI,WAAW,IAAI,UAAU;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,2BAA2B,QAAgB,WAA6B;AACtE,QAAI,UAAU,WAAW,EAAG,QAAO;AAGnC,UAAM,eAAe,UAAU,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACvD,UAAM,OAAO,KAAK,GAAG;AAAA,MACnB,iEAAiE,YAAY;AAAA,IAC/E;AAEA,UAAM,SAAS,KAAK,IAAI,QAAQ,GAAG,SAAS;AAC5C,WAAO,OAAO;AAAA,EAChB;AACF;;;AChPA,OAAOE,SAAQ;AACf,OAAOC,WAAU;AA6BV,SAAS,oBAAoB,UAAkB,UAAmC;AACvF,QAAM,UAAU,SAAS,KAAK;AAC9B,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAElC,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAGhC,MAAI;AACJ,MAAI,oBAAoB;AAExB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,MAAM,CAAC,EAAE,MAAM,YAAY;AACzC,QAAI,OAAO;AACT,cAAQ,MAAM,CAAC,EAAE,KAAK;AACtB,0BAAoB,IAAI;AACxB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,OAAO;AAEV,YAAQC,MAAK,SAAS,UAAUA,MAAK,QAAQ,QAAQ,CAAC;AACtD,wBAAoB;AAAA,EACtB;AAEA,QAAM,eAAe,MAAM,MAAM,iBAAiB;AAGlD,QAAM,WAA+D,CAAC;AACtE,MAAI,iBAA8D,EAAE,SAAS,MAAM,OAAO,CAAC,EAAE;AAE7F,aAAW,QAAQ,cAAc;AAC/B,UAAM,UAAU,KAAK,MAAM,aAAa;AACxC,QAAI,SAAS;AAEX,eAAS,KAAK,cAAc;AAC5B,uBAAiB,EAAE,SAAS,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,EAAE;AAAA,IAC3D,OAAO;AACL,qBAAe,MAAM,KAAK,IAAI;AAAA,IAChC;AAAA,EACF;AACA,WAAS,KAAK,cAAc;AAG5B,QAAM,SAA0B,CAAC;AACjC,MAAI,aAAa;AAEjB,aAAW,WAAW,UAAU;AAC9B,UAAM,iBAAiB,QAAQ,UAC3B,MAAM,QAAQ,OAAO;AAAA;AAAA,EAAO,QAAQ,MAAM,KAAK,IAAI,EAAE,KAAK,CAAC,KAC3D,QAAQ,MAAM,KAAK,IAAI,EAAE,KAAK;AAElC,QAAI,eAAe,WAAW,EAAG;AAEjC,WAAO,KAAK;AAAA,MACV;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AACD;AAAA,EACF;AAEA,SAAO;AACT;AAWO,SAAS,gBAAgB,UAA0B;AAExD,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,QAAM,MAAMA,MAAK,MAAM,QAAQ,UAAU;AACzC,SAAO,QAAQ,MAAM,KAAK;AAC5B;AAKA,SAAS,qBAAqB,KAAa,SAA2B;AACpE,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAUC,IAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAE3D,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAWD,MAAK,KAAK,KAAK,MAAM,IAAI;AAC1C,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,KAAK,GAAG,qBAAqB,UAAU,OAAO,CAAC;AAAA,IACvD,WAAW,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,KAAK,GAAG;AAEvD,UAAI,MAAM,KAAK,YAAY,MAAM,YAAa;AAC9C,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,IAAM,cAAc;AAYb,SAAS,gBAAgBE,KAAuB,eAAoC;AACzF,QAAM,OAAO,IAAI,uBAAuBA,GAAE;AAG1C,QAAM,iBAAiB,KAAK,mBAAmB,WAAW;AAG1D,QAAM,UAAU,qBAAqB,eAAe,aAAa;AACjE,QAAM,YAAY,oBAAI,IAAoB;AAE1C,aAAW,YAAY,SAAS;AAC9B,UAAM,eAAeF,MAAK,SAAS,eAAe,QAAQ,EAAE,QAAQ,OAAO,GAAG;AAC9E,UAAM,OAAOC,IAAG,SAAS,QAAQ;AACjC,cAAU,IAAI,cAAc,KAAK,MAAM,YAAY,CAAC;AAAA,EACtD;AAGA,QAAM,WAAqB,CAAC;AAC5B,QAAM,eAAyB,CAAC;AAChC,QAAM,eAAyB,CAAC;AAEhC,aAAW,CAAC,cAAc,SAAS,KAAK,WAAW;AACjD,UAAM,gBAAgB,eAAe,IAAI,YAAY;AACrD,QAAI,kBAAkB,QAAW;AAE/B,eAAS,KAAK,YAAY;AAAA,IAC5B,WAAW,kBAAkB,WAAW;AAEtC,mBAAa,KAAK,YAAY;AAAA,IAChC,OAAO;AAEL,mBAAa,KAAK,YAAY;AAAA,IAChC;AAAA,EACF;AAGA,QAAM,eAAyB,CAAC;AAChC,aAAW,gBAAgB,eAAe,KAAK,GAAG;AAChD,QAAI,CAAC,UAAU,IAAI,YAAY,GAAG;AAChC,mBAAa,KAAK,YAAY;AAAA,IAChC;AAAA,EACF;AAGA,QAAM,gBAAgB,CAAC,GAAG,cAAc,GAAG,YAAY;AACvD,MAAI,cAAc,SAAS,GAAG;AAC5B,SAAK,2BAA2B,aAAa,aAAa;AAAA,EAC5D;AAGA,QAAM,gBAAgB,CAAC,GAAG,UAAU,GAAG,YAAY;AACnD,QAAM,UAAqC,CAAC;AAE5C,aAAW,gBAAgB,eAAe;AACxC,UAAM,WAAWD,MAAK,KAAK,eAAe,YAAY;AACtD,UAAM,UAAUC,IAAG,aAAa,UAAU,OAAO;AACjD,UAAM,WAAW,gBAAgB,YAAY;AAC7C,UAAM,SAAS,oBAAoB,SAASD,MAAK,SAAS,QAAQ,CAAC;AACnE,UAAM,YAAY,UAAU,IAAI,YAAY;AAE5C,eAAW,SAAS,QAAQ;AAC1B,cAAQ,KAAK;AAAA,QACX,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,OAAO,MAAM;AAAA,QACb;AAAA,QACA,SAAS,MAAM;AAAA,QACf,YAAY,MAAM;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,iBAAiB,KAAK,MAAM,OAAO;AAEzC,SAAO;AAAA,IACL,aAAa;AAAA,IACb,UAAU,SAAS;AAAA,IACnB,cAAc,aAAa;AAAA,IAC3B,cAAc,aAAa;AAAA,IAC3B,cAAc,aAAa;AAAA,EAC7B;AACF;;;ACnOA,OAAOG,SAAQ;AACf,OAAO,QAAQ;AACf,OAAOC,WAAU;AAOV,SAAS,aAAqB;AACnC,QAAM,SAAS,QAAQ,IAAI,kBAAkB;AAC7C,MAAI,OAAQ,QAAO;AACnB,SAAOA,MAAK,KAAK,GAAG,QAAQ,GAAG,YAAY,MAAM;AACnD;AAMO,SAAS,mBAA2B;AACzC,SAAOA,MAAK,KAAK,WAAW,GAAG,YAAY;AAC7C;AAKO,SAAS,gBAAsB;AACpC,EAAAD,IAAG,UAAU,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD;;;AC7BA,SAAS,YAAY,kBAAkB;AACvC,SAAS,iBAAiB;AAC1B,OAAOE,SAAQ;AACf,OAAOC,WAAU;AAEjB,IAAM,WAAW,UAAU,UAAU;AAErC,IAAM,kBAAkB;AACxB,IAAM,aAAa,IAAI,KAAK;AAoB5B,eAAsB,iBAAmC;AACvD,MAAI;AACF,UAAM,SAAS,OAAO,CAAC,WAAW,GAAG,EAAE,SAAS,IAAO,CAAC;AACxD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,cAAc,KAAc,WAAwC;AAC3E,QAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACpE,QAAM,SACJ,QAAQ,QAAQ,OAAO,QAAQ,YAAY,YAAY,MACnD,OAAQ,IAA4B,MAAM,IAC1C;AAEN,QAAM,WAAW,GAAG,YAAY,IAAI,MAAM,GAAG,YAAY;AAEzD,MAAI,SAAS,SAAS,QAAQ,KAAK,SAAS,SAAS,gBAAgB,KAAK,SAAS,SAAS,WAAW,GAAG;AACxG,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,EAAE,MAAM,iBAAiB,SAAS,uCAAuC,OAAO,aAAa;AAAA,IACtG;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,mBAAmB,KAAK,SAAS,SAAS,eAAe,GAAG;AAChF,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,EAAE,MAAM,qBAAqB,SAAS,4BAA4B,SAAS,IAAI,OAAO,aAAa;AAAA,IAC5G;AAAA,EACF;AAEA,MACE,SAAS,SAAS,mBAAmB,KACrC,SAAS,SAAS,kBAAkB,KACpC,SAAS,SAAS,SAAS,KAC3B,SAAS,SAAS,WAAW,GAC7B;AACA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,EAAE,MAAM,iBAAiB,SAAS,wBAAwB,SAAS,IAAI,OAAO,aAAa;AAAA,IACpG;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,MACL,MAAM,cAAc,UAAU,iBAAiB;AAAA,MAC/C,SAAS,OAAO,SAAS;AAAA,MACzB,OAAO;AAAA,IACT;AAAA,EACF;AACF;AAOA,eAAsB,gBAAgB,WAAuC;AAE3E,QAAM,YAAYA,MAAK,QAAQ,SAAS;AAExC,MAAI,CAACD,IAAG,WAAW,SAAS,GAAG;AAC7B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,oCAAoC,SAAS;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM;AAAA,MACvB;AAAA,MACA,CAAC,SAAS,WAAW,KAAK,iBAAiB,SAAS;AAAA,MACpD,EAAE,SAAS,WAAW;AAAA,IACxB;AACA,WAAO,EAAE,IAAI,MAAM,SAAS,OAAO,KAAK,KAAK,+BAA+B;AAAA,EAC9E,SAAS,KAAK;AACZ,WAAO,cAAc,KAAK,OAAO;AAAA,EACnC;AACF;AAMA,eAAsB,eAAe,SAAqC;AAExE,MAAI,CAACA,IAAG,WAAW,OAAO,GAAG;AAC3B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,wBAAwB,OAAO;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,SAAS,OAAO,CAAC,QAAQ,WAAW,GAAG;AAAA,MAC9D,KAAK;AAAA,MACL,SAAS;AAAA,IACX,CAAC;AACD,WAAO,EAAE,IAAI,MAAM,SAAS,OAAO,KAAK,KAAK,qBAAqB;AAAA,EACpE,SAAS,KAAK;AACZ,WAAO,cAAc,KAAK,MAAM;AAAA,EAClC;AACF;;;AJ5HA,SAAS,kBAAkB,QAAqB,KAAqB;AACnE,QAAM,QAAkB;AAAA,IACtB,6BAA6B,GAAG;AAAA,IAChC,sBAAsB,OAAO,WAAW;AAAA,IACxC,gBAAgB,OAAO,QAAQ;AAAA,IAC/B,oBAAoB,OAAO,YAAY;AAAA,IACvC,oBAAoB,OAAO,YAAY;AAAA,IACvC,0BAA0B,OAAO,YAAY;AAAA,EAC/C;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,gBAAgBE,SAAmBC,KAA6B;AAC9E,QAAM,OAAO,IAAI,uBAAuBA,GAAE;AAG1C,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,OAAOE,GAAE,OAAO,EAAE,SAAS,wDAAwD;AAAA,MACnF,UAAUA,GACP,OAAO,EACP,SAAS,EACT,SAAS,oDAAoD;AAAA,MAChE,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yCAAyC;AAAA,IACjF;AAAA,IACA,OAAO,EAAE,OAAO,UAAU,MAAM,MAAM;AACpC,YAAM,UAAU,KAAK,OAAO,OAAO,EAAE,OAAO,SAAS,IAAI,SAAS,CAAC;AAEnE,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,yBAAyB,KAAK,KAAK,KAAK,MAAM,MAAM,IAAI,gEAAgE,EAAE;AAAA,YAClI;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,YAAY,QAAQ,IAAI,CAAC,OAAO;AAAA,QACpC,OAAO,EAAE;AAAA,QACT,UAAU,EAAE;AAAA,QACZ,UAAU,EAAE;AAAA,QACZ,YAAY,EAAE;AAAA,QACd,OAAO,EAAE;AAAA,QACT,SAAS,EAAE;AAAA,MACb,EAAE;AAEF,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,WAAW,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,IACjF;AAAA,EACF;AAGA,EAAAF,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,MAAME,GACH,OAAO,EACP,SAAS,EACT;AAAA,QACC;AAAA,MACF;AAAA,MACF,QAAQA,GACL,QAAQ,EACR,SAAS,EACT;AAAA,QACC;AAAA,MACF;AAAA,IACJ;AAAA,IACA,OAAO,EAAE,MAAM,gBAAgB,OAAO,MAAM;AAC1C,UAAI;AACF,YAAI;AACJ,cAAM,WAAqB,CAAC;AAE5B,YAAI,gBAAgB;AAElB,gBAAM;AAAA,QACR,OAAO;AAEL,gBAAM,iBAAiB;AACvB,wBAAc;AAEd,cAAI,CAACC,IAAG,WAAW,GAAG,GAAG;AAEvB,kBAAM,eAAe,MAAM,eAAe;AAC1C,gBAAI,CAAC,cAAc;AACjB,qBAAO;AAAA,gBACL,SAAS;AAAA,kBACP;AAAA,oBACE,MAAM;AAAA,oBACN,MAAM;AAAA,kBACR;AAAA,gBACF;AAAA,gBACA,SAAS;AAAA,cACX;AAAA,YACF;AAEA,qBAAS,KAAK,yBAAyB,GAAG,KAAK;AAC/C,kBAAM,cAAc,MAAM,gBAAgB,GAAG;AAC7C,gBAAI,CAAC,YAAY,IAAI;AACnB,qBAAO;AAAA,gBACL,SAAS;AAAA,kBACP;AAAA,oBACE,MAAM;AAAA,oBACN,MAAM,+BAA+B,YAAY,MAAM,OAAO,GAAG,YAAY,MAAM,QAAQ,KAAK,YAAY,MAAM,KAAK,MAAM,EAAE;AAAA,kBACjI;AAAA,gBACF;AAAA,gBACA,SAAS;AAAA,cACX;AAAA,YACF;AACA,qBAAS,KAAK,kBAAkB;AAAA,UAClC,WAAW,WAAW,OAAO;AAE3B,kBAAM,aAAa,MAAM,eAAe,GAAG;AAC3C,gBAAI,WAAW,IAAI;AACjB,uBAAS,KAAK,YAAY,WAAW,OAAO,EAAE;AAAA,YAChD,OAAO;AACL,uBAAS;AAAA,gBACP,6BAA6B,WAAW,MAAM,OAAO;AAAA,cACvD;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,cAAM,SAAS,gBAAgBF,KAAI,GAAG;AACtC,iBAAS,KAAK,kBAAkB,QAAQ,GAAG,CAAC;AAE5C,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,SAAS,KAAK,IAAI,EAAE,CAAC;AAAA,QACvD;AAAA,MACF,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,eAAO;AAAA,UACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,+BAA+B,OAAO,GAAG,CAAC;AAAA,UAC1E,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AKvJA,SAAS,KAAAG,UAAS;;;ACPlB,OAAOC,aAAY;AAyBnB,SAAS,gBAAgB,KAAgC;AACvD,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,IAChB,GAAI,IAAI,kBAAkB,OAAO,EAAE,cAAc,IAAI,cAAc,IAAI,CAAC;AAAA,IACxE,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAOA,IAAM,kBAA0C;AAAA,EAC9C,MAAM;AAAA,EACN,aAAa;AAAA,EACb,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,QAAQ;AACV;AAaO,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA,IAEF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA,IAEF;AAEA,SAAK,qBAAqB,KAAK,GAAG;AAAA,MAChC;AAAA;AAAA,IAEF;AAEA,SAAK,gBAAgB,KAAK,GAAG;AAAA,MAC3B;AAAA;AAAA,IAEF;AAEA,SAAK,aAAa,KAAK,GAAG,QAAQ,sCAAsC;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,OAA0C;AAC/C,UAAM,KAAKD,QAAO,WAAW;AAC7B,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,UAAM,cAAc,MAAM,eAAe;AACzC,UAAM,YAAY,MAAM,aAAa;AACrC,UAAM,aAAa,MAAM,cAAc;AACvC,UAAM,eAAe,MAAM,gBAAgB;AAC3C,UAAM,SAAS,MAAM,UAAU;AAE/B,SAAK,WAAW;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,MAAM,MAAM;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,iBAAiB,OAAO,EAAE,aAAa,IAAI,CAAC;AAAA,MAChD;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAoC;AAC3C,UAAM,MAAM,KAAK,eAAe,IAAI,EAAE;AACtC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,gBAAgB,GAAG;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAA8B;AACzC,UAAM,OAAO,KAAK,mBAAmB,IAAI,MAAM;AAC/C,WAAO,KAAK,IAAI,eAAe;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAqB;AACnB,UAAM,OAAO,KAAK,cAAc,IAAI;AACpC,WAAO,KAAK,IAAI,eAAe;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,IAAY,QAAgE;AAEjF,UAAM,aAAuB,CAAC;AAC9B,UAAM,SAAoB,CAAC;AAE3B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAM,SAAS,gBAAgB,GAAG;AAClC,UAAI,WAAW,QAAW;AACxB,mBAAW,KAAK,GAAG,MAAM,MAAM;AAC/B,eAAO,KAAK,SAAS,IAAI;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,eAAW,KAAK,gBAAgB;AAChC,WAAO,KAAK,SAAS;AAGrB,WAAO,KAAK,EAAE;AAEd,UAAM,MAAM,0BAA0B,WAAW,KAAK,IAAI,CAAC;AAC3D,UAAM,SAAS,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAEjD,QAAI,OAAO,YAAY,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,IAAqB;AAC1B,UAAM,SAAS,KAAK,WAAW,IAAI,EAAE;AACrC,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;;;AC1NA,OAAOE,aAAY;AAyBnB,SAAS,SAAS,KAAkB;AAClC,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,cAAc,IAAI;AAAA,IAClB,aAAa,IAAI;AAAA,IACjB,GAAI,IAAI,gBAAgB,OAAO,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,IAClE,QAAQ,IAAI;AAAA,IACZ,GAAI,IAAI,eAAe,OAAO,EAAE,WAAW,IAAI,WAAW,IAAI,CAAC;AAAA,IAC/D,GAAI,IAAI,gBAAgB,OAAO,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,IAClE,aAAa,IAAI;AAAA,IACjB,WAAW,IAAI;AAAA,EACjB;AACF;AAYO,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA,IAEF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA,IAEF;AAEA,SAAK,yBAAyB,KAAK,GAAG;AAAA,MACpC;AAAA;AAAA,IAEF;AAEA,SAAK,qBAAqB,KAAK,GAAG;AAAA,MAChC;AAAA;AAAA,IAEF;AAEA,SAAK,aAAa,KAAK,GAAG,QAAQ,+BAA+B;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAA4B;AACjC,UAAM,KAAKD,QAAO,WAAW;AAC7B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,YAAY,MAAM,WAAW,YAAY,MAAM;AAErD,SAAK,WAAW;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,cAAc;AAAA,MACpB,MAAM;AAAA,MACN;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,cAAc,MAAM;AAAA,MACpB,aAAa,MAAM;AAAA,MACnB,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,MACzE,QAAQ,MAAM;AAAA,MACd,GAAI,cAAc,OAAO,EAAE,UAAU,IAAI,CAAC;AAAA,MAC1C,aAAa;AAAA,MACb,WAAW;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAA6B;AACpC,UAAM,MAAM,KAAK,eAAe,IAAI,EAAE;AACtC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,SAAS,GAAG;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,cAAsB,OAAuB;AAC5D,UAAM,OAAO,KAAK,uBAAuB,IAAI,cAAc,SAAS,GAAG;AACvE,WAAO,KAAK,IAAI,QAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAAuB;AAClC,UAAM,OAAO,KAAK,mBAAmB,IAAI,MAAM;AAC/C,WAAO,KAAK,IAAI,QAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,aAAa,IAAY,QAAgB,aAAuC;AAE9E,UAAM,WAAW,KAAK,SAAS,EAAE;AACjC,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AAEA,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAGnC,UAAM,aAAuB,CAAC,YAAY;AAC1C,UAAM,SAAoB,CAAC,MAAM;AAGjC,QAAI,WAAW,eAAe,WAAW,UAAU;AACjD,iBAAW,KAAK,iBAAiB;AACjC,aAAO,KAAK,GAAG;AAAA,IACjB;AAGA,QAAI,WAAW,aAAa,SAAS,cAAc,QAAW;AAC5D,iBAAW,KAAK,gBAAgB;AAChC,aAAO,KAAK,GAAG;AAAA,IACjB;AAGA,QAAI,gBAAgB,QAAW;AAC7B,iBAAW,KAAK,kBAAkB;AAClC,aAAO,KAAK,WAAW;AAAA,IACzB;AAEA,WAAO,KAAK,EAAE;AAEd,UAAM,MAAM,mBAAmB,WAAW,KAAK,IAAI,CAAC;AACpD,SAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAElC,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAqB;AAC1B,UAAM,SAAS,KAAK,WAAW,IAAI,EAAE;AACrC,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;;;AC7MA,OAAOE,aAAY;AAiCnB,SAAS,qBAAqB,KAAsC;AAClE,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,cAAc,IAAI;AAAA,IAClB,GAAI,IAAI,WAAW,OAAO,EAAE,OAAO,IAAI,OAAO,IAAI,CAAC;AAAA,IACnD,GAAI,IAAI,qBAAqB,OAAO,EAAE,gBAAgB,IAAI,iBAAiB,IAAI,CAAC;AAAA,IAChF,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,IAChB,OAAO,IAAI;AAAA,IACX,cAAc,IAAI;AAAA,IAClB,aAAa,IAAI;AAAA,IACjB,aAAa,IAAI;AAAA,IACjB,GAAI,IAAI,gBAAgB,OAAO,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,IAClE,GAAI,IAAI,qBAAqB,OAAO,EAAE,gBAAgB,IAAI,iBAAiB,IAAI,CAAC;AAAA,IAChF,GAAI,IAAI,eAAe,OAAO,EAAE,WAAW,IAAI,WAAW,IAAI,CAAC;AAAA,IAC/D,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAcO,IAAM,wBAAN,MAA4B;AAAA,EAChB;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AAEA,SAAK,WAAW,KAAK,GAAG;AAAA,MACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAaF;AAEA,SAAK,eAAe,KAAK,GAAG;AAAA,MAC1B;AAAA;AAAA;AAAA,IAGF;AAEA,SAAK,cAAc,KAAK,GAAG;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AAEA,SAAK,yBAAyB,KAAK,GAAG;AAAA,MACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOF;AAEA,SAAK,8BAA8B,KAAK,GAAG;AAAA,MACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOF;AAEA,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA,IACF;AAGA,SAAK,SAAS,KAAK,GAAG,YAAY,CAAC,IAAY,UAA2B;AACxE,YAAM,OAAO,KAAK,SAAS,EAAE;AAC7B,UAAI,CAAC,QAAQ,KAAK,UAAU,UAAW,QAAO;AAE9C,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAI,KAAK,eAAe,KAAK,aAAa;AAExC,cAAM,aAAa,KAAK,eAAe;AACvC,cAAM,cAAc,IAAI,KAAK,KAAK,IAAI,IAAI,aAAa,GAAI,EAAE,YAAY;AACzE,aAAK,YAAY,IAAI,aAAa,OAAO,KAAK,EAAE;AAAA,MAClD,OAAO;AAEL,aAAK,eAAe,IAAI,OAAO,KAAK,EAAE;AAAA,MACxC;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,QAAQ,OAA2C;AACjD,UAAM,KAAKD,QAAO,WAAW;AAC7B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,aAAa,MAAM,cAAc;AACvC,UAAM,cAAc,MAAM,eAAe;AACzC,UAAM,cAAc,MAAM,eAAe;AAEzC,SAAK,WAAW;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,MAAM,SAAS;AAAA,MACf,MAAM,kBAAkB;AAAA,MACxB,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,cAAc,MAAM;AAAA,MACpB,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAC1D,GAAI,MAAM,mBAAmB,SAAY,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;AAAA,MACrF,MAAM,MAAM;AAAA,MACZ;AAAA,MACA,WAAW,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAyC;AAChD,UAAM,MAAM,KAAK,eAAe,IAAI,EAAE;AACtC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,qBAAqB,GAAG;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,KAAK,YAAoB,kBAAwD;AAC/E,UAAM,WAAW,oBAAoB;AACrC,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,SAAS,IAAI,YAAY;AAC/B,UAAM,iBAAiB,IAAI,KAAK,IAAI,QAAQ,IAAI,WAAW,GAAI,EAAE,YAAY;AAE7E,UAAM,MAAM,KAAK,SAAS;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,qBAAqB,GAAG;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAS,IAAqB;AAC5B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK,EAAE;AAC5C,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,KAAK,IAAY,OAAwB;AACvC,WAAO,KAAK,OAAO,IAAI,KAAK;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiB,cAAsB,OAAmC;AACxE,QAAI,UAAU,QAAW;AACvB,YAAME,QAAO,KAAK,4BAA4B,IAAI,cAAc,KAAK;AACrE,aAAOA,MAAK,IAAI,oBAAoB;AAAA,IACtC;AACA,UAAM,OAAO,KAAK,uBAAuB,IAAI,YAAY;AACzD,WAAO,KAAK,IAAI,oBAAoB;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAqB;AAC1B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,SAAS,KAAK,WAAW,IAAI,KAAK,EAAE;AAC1C,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;;;ACnVA,OAAOC,aAAY;AA+BnB,SAAS,eAAe,KAA0C;AAChE,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,UAAU,IAAI;AAAA,IACd,GAAI,IAAI,WAAW,OAAO,EAAE,OAAO,IAAI,OAAO,IAAI,CAAC;AAAA,IACnD,UAAU,IAAI;AAAA,IACd,GAAI,IAAI,YAAY,OAAO,EAAE,SAAS,IAAI,QAAQ,IAAI,CAAC;AAAA,IACvD,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,IAChB,GAAI,IAAI,uBAAuB,OAAO,EAAE,kBAAkB,IAAI,mBAAmB,IAAI,CAAC;AAAA,IACtF,GAAI,IAAI,uBAAuB,OAAO,EAAE,kBAAkB,IAAI,mBAAmB,IAAI,CAAC;AAAA,IACtF,GAAI,IAAI,cAAc,OAAO,EAAE,UAAU,IAAI,UAAU,IAAI,CAAC;AAAA,IAC5D,GAAI,IAAI,eAAe,OAAO,EAAE,WAAW,IAAI,WAAW,IAAI,CAAC;AAAA,IAC/D,GAAI,IAAI,kBAAkB,OAAO,EAAE,cAAc,IAAI,cAAc,IAAI,CAAC;AAAA,IACxE,WAAW,IAAI;AAAA,IACf,GAAI,IAAI,gBAAgB,OAAO,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,IAClE,GAAI,IAAI,gBAAgB,OAAO,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,EACpE;AACF;AA2BO,IAAM,4BAAN,MAAgC;AAAA,EACpB;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA,IAEF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA;AAAA;AAAA,IAIF;AAEA,SAAK,qBAAqB,KAAK,GAAG;AAAA,MAChC;AAAA;AAAA;AAAA;AAAA,IAIF;AAEA,SAAK,kBAAkB,KAAK,GAAG;AAAA,MAC7B;AAAA;AAAA;AAAA;AAAA,IAIF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAA8C;AACnD,UAAM,KAAKD,QAAO,WAAW;AAC7B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,YAAY,MAAM,aAAa;AAErC,SAAK,WAAW;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN,MAAM,SAAS;AAAA,MACf,MAAM;AAAA,MACN,MAAM,WAAW;AAAA,MACjB;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAC1D,UAAU,MAAM;AAAA,MAChB,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MAChE;AAAA,MACA,YAAY;AAAA,MACZ,WAAW;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAyC;AAChD,UAAM,MAAM,KAAK,eAAe,IAAI,EAAE;AACtC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,eAAe,GAAG;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,UAAqC;AAChD,UAAM,OAAO,KAAK,mBAAmB,IAAI,QAAQ;AACjD,WAAO,KAAK,IAAI,cAAc;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,OAAkC;AAC1C,UAAM,OAAO,KAAK,gBAAgB,IAAI,KAAK;AAC3C,WAAO,KAAK,IAAI,cAAc;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAS,IAAY,QAA8D;AACjF,UAAM,WAAW,KAAK,SAAS,EAAE;AACjC,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,aAAa,IAAI,YAAY;AACnC,UAAM,aAAa,IAAI,QAAQ,IAAI,IAAI,KAAK,SAAS,SAAS,EAAE,QAAQ;AAExE,UAAM,aAAuB,CAAC,mBAAmB,iBAAiB;AAClE,UAAM,SAAoB,CAAC,YAAY,UAAU;AAEjD,QAAI,OAAO,eAAe,QAAW;AACnC,iBAAW,KAAK,iBAAiB;AACjC,aAAO,KAAK,OAAO,UAAU;AAAA,IAC/B;AACA,QAAI,OAAO,aAAa,QAAW;AACjC,iBAAW,KAAK,eAAe;AAC/B,aAAO,KAAK,OAAO,QAAQ;AAAA,IAC7B;AACA,QAAI,OAAO,cAAc,QAAW;AAClC,iBAAW,KAAK,gBAAgB;AAChC,aAAO,KAAK,OAAO,SAAS;AAAA,IAC9B;AACA,QAAI,OAAO,iBAAiB,QAAW;AACrC,iBAAW,KAAK,mBAAmB;AACnC,aAAO,KAAK,OAAO,YAAY;AAAA,IACjC;AACA,QAAI,OAAO,qBAAqB,QAAW;AACzC,iBAAW,KAAK,wBAAwB;AACxC,aAAO,KAAK,OAAO,gBAAgB;AAAA,IACrC;AACA,QAAI,OAAO,qBAAqB,QAAW;AACzC,iBAAW,KAAK,wBAAwB;AACxC,aAAO,KAAK,OAAO,gBAAgB;AAAA,IACrC;AAEA,WAAO,KAAK,EAAE;AAEd,UAAM,MAAM,gCAAgC,WAAW,KAAK,IAAI,CAAC;AACjE,SAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAElC,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AACF;;;AJ3NO,SAAS,iBAAiBE,SAAmBC,KAA6B;AAC/E,QAAM,iBAAiB,IAAI,qBAAqBA,GAAE;AAClD,QAAM,UAAU,IAAI,cAAcA,GAAE;AACpC,QAAM,kBAAkB,IAAI,sBAAsBA,GAAE;AACpD,QAAM,iBAAiB,IAAI,0BAA0BA,GAAE;AAEvD,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQE,GAAE,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACD,IAAIA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,WAAW;AAAA,MAC9C,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MAC5D,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,QAAQ;AAAA,MAC9C,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,sBAAsB;AAAA,MAC/D,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,iBAAiB;AAAA,MACtD,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8BAA8B;AAAA,MAC1E,QAAQA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,cAAc;AAAA,MACrD,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kBAAkB;AAAA,MAC9D,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,uBAAuB;AAAA,MAClE,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,aAAa;AAAA,MAClD,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0BAA0B;AAAA,MACpE,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,sBAAsB;AAAA,MACjE,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MACxD,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAAA,MACrE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MACzD,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,gBAAgB;AAAA,MAC3D,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0BAA0B;AAAA,MACvE,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,MACtE,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA,MACjE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yBAAyB;AAAA,MACnE,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kCAAkC;AAAA,MAC3E,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,MAChE,kBAAkBA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,MAC5E,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,cAAc;AAAA,MACpD,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA,MAC3D,gBAAgBA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kBAAkB;AAAA,MACjE,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAAA,IACtE;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAAM;AACJ,cAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA,QAId,KAAK,qBAAqB;AACxB,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,mDAAmD;AAAA,cAC3E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,aAAa,eAAe,OAAO;AAAA,YACvC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AACD,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAClF;AAAA,QAEA,KAAK,oBAAoB;AACvB,gBAAM,cAAc,SAChB,eAAe,aAAa,MAAM,IAClC,eAAe,KAAK;AACxB,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,aAAa,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QACnF;AAAA,QAEA,KAAK,kBAAkB;AACrB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,8CAA8C;AAAA,cACtE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,aAAa,eAAe,SAAS,EAAE;AAC7C,cAAI,CAAC,YAAY;AACf,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,yBAAyB,EAAE,GAAG,CAAC;AAAA,cAC/D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAClF;AAAA,QAEA,KAAK,qBAAqB;AACxB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,iDAAiD;AAAA,cACzE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAkC,CAAC;AACzC,cAAI,SAAS,OAAW,QAAO,OAAO;AACtC,cAAI,gBAAgB,OAAW,QAAO,cAAc;AACpD,cAAI,cAAc,OAAW,QAAO,YAAY;AAChD,cAAI,eAAe,OAAW,QAAO,aAAa;AAClD,cAAI,iBAAiB,OAAW,QAAO,eAAe;AACtD,cAAI,WAAW,OAAW,QAAO,SAAS;AAE1C,gBAAM,UAAU,eAAe,OAAO,IAAI,MAAM;AAChD,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,yBAAyB,EAAE,GAAG,CAAC;AAAA,cAC/D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC/E;AAAA,QAEA,KAAK,qBAAqB;AACxB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,iDAAiD;AAAA,cACzE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,UAAU,eAAe,OAAO,EAAE;AACxC,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,yBAAyB,EAAE,GAAG,CAAC;AAAA,cAC/D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,cAAc,EAAE,yBAAyB,CAAC;AAAA,UAC5E;AAAA,QACF;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,cAAc;AACjB,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,oDAAoD;AAAA,cAC5E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,aAAa;AAChB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,mDAAmD;AAAA,cAC3E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,QAAQ;AACX,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,8CAA8C;AAAA,cACtE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,MAAM,QAAQ,OAAO;AAAA,YACzB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AACD,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC3E;AAAA,QAEA,KAAK,aAAa;AAChB,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,mDAAmD;AAAA,cAC3E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,OAAO,QAAQ,iBAAiB,cAAc,KAAK;AACzD,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC5E;AAAA,QAEA,KAAK,WAAW;AACd,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,uCAAuC,CAAC;AAAA,cACxE,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,MAAM,QAAQ,SAAS,EAAE;AAC/B,cAAI,CAAC,KAAK;AACR,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,kBAAkB,EAAE,GAAG,CAAC;AAAA,cACxD,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC3E;AAAA,QAEA,KAAK,qBAAqB;AACxB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,iDAAiD;AAAA,cACzE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,QAAQ;AACX,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,qDAAqD;AAAA,cAC7E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,aAAa,QAAQ,aAAa,IAAI,QAAQ,WAAW;AAC/D,cAAI,CAAC,YAAY;AACf,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,kBAAkB,EAAE,GAAG,CAAC;AAAA,cACxD,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAClF;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,kBAAkB;AACrB,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,MAAM;AACT,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,gDAAgD;AAAA,cACxE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,WAAW;AACd,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,qDAAqD;AAAA,cAC7E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,OAAO,gBAAgB,QAAQ;AAAA,YACnC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AACD,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC5E;AAAA,QAEA,KAAK,eAAe;AAClB,cAAI,CAAC,YAAY;AACf,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,mDAAmD;AAAA,cAC3E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAS,gBAAgB,KAAK,YAAY,gBAAgB;AAChE,cAAI,CAAC,QAAQ;AACX,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,8BAA8B,CAAC;AAAA,YACjE;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA,QAEA,KAAK,mBAAmB;AACtB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,+CAA+C;AAAA,cACvE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,YAAY,gBAAgB,SAAS,EAAE;AAC7C,cAAI,CAAC,WAAW;AACd,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,6CAA6C,EAAE,GAAG;AAAA,cAC1E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,EAAE,2BAA2B,CAAC;AAAA,UAC1E;AAAA,QACF;AAAA,QAEA,KAAK,eAAe;AAClB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,2CAA2C;AAAA,cACnE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,qDAAqD;AAAA,cAC7E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAS,gBAAgB,KAAK,IAAI,YAAY;AACpD,cAAI,CAAC,QAAQ;AACX,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,6CAA6C,EAAE,GAAG;AAAA,cAC1E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,YAAY,gBAAgB,SAAS,EAAE;AAC7C,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,WAAW,MAAM,CAAC,EAAE,CAAC;AAAA,UACtE;AAAA,QACF;AAAA,QAEA,KAAK,iBAAiB;AACpB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,6CAA6C;AAAA,cACrE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,YAAY,gBAAgB,OAAO,EAAE;AAC3C,cAAI,CAAC,WAAW;AACd,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,qBAAqB,EAAE,GAAG,CAAC;AAAA,cAC3D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,EAAE,2BAA2B,CAAC;AAAA,UAC1E;AAAA,QACF;AAAA,QAEA,KAAK,gBAAgB;AACnB,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,sDAAsD;AAAA,cAC9E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,UAAU,gBAAgB,iBAAiB,cAAc,KAAK;AACpE,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC/E;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,iBAAiB;AACpB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,6CAA6C;AAAA,cACrE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,YAAY,eAAe,SAAS,EAAE;AAC5C,cAAI,CAAC,WAAW;AACd,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,wBAAwB,EAAE,GAAG,CAAC;AAAA,cAC9D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,WAAW,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QACjF;AAAA,QAEA,KAAK,mBAAmB;AACtB,cAAI,UAAU;AACZ,kBAAM,aAAa,eAAe,aAAa,QAAQ;AACvD,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,CAAC;AAAA,YACvE;AAAA,UACF;AACA,cAAI,OAAO;AACT,kBAAM,aAAa,eAAe,UAAU,KAAK;AACjD,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE,CAAC;AAAA,YACvE;AAAA,UACF;AACA,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM;AAAA,cACR;AAAA,YACF;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AKreA,SAAS,KAAAC,UAAS;;;ACNlB,OAAOC,cAAY;AAkDnB,SAAS,aAAa,KAA0B;AAC9C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,cAAc,IAAI;AAAA,IAClB,cAAc,IAAI;AAAA,IAClB,GAAI,IAAI,YAAY,OAAO,EAAE,QAAQ,IAAI,QAAQ,IAAI,CAAC;AAAA,IACtD,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,YAAY,IAAI;AAAA,IAChB,OAAO,IAAI;AAAA,IACX,GAAI,IAAI,iBAAiB,OAAO,EAAE,aAAa,IAAI,aAAa,IAAI,CAAC;AAAA,IACrE,GAAI,IAAI,UAAU,OAAO,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC;AAAA,IACjD,GAAI,IAAI,eAAe,OAAO,EAAE,WAAW,IAAI,WAAW,IAAI,CAAC;AAAA,IAC/D,GAAI,IAAI,sBAAsB,OAAO,EAAE,gBAAgB,IAAI,kBAAkB,IAAI,CAAC;AAAA,IAClF,GAAI,IAAI,qBAAqB,OAAO,EAAE,eAAe,IAAI,iBAAiB,IAAI,CAAC;AAAA,IAC/E,aAAa,IAAI;AAAA,IACjB,YAAY,IAAI;AAAA,IAChB,GAAI,IAAI,eAAe,OAAO,EAAE,UAAU,IAAI,WAAW,IAAI,CAAC;AAAA,IAC9D,WAAW,IAAI;AAAA,EACjB;AACF;AAGA,SAAS,kBAAkB,KAAoC;AAC7D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,GAAI,IAAI,WAAW,OAAO,EAAE,OAAO,IAAI,OAAO,IAAI,CAAC;AAAA,IACnD,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,GAAI,IAAI,gBAAgB,OAAO,EAAE,YAAY,IAAI,YAAY,IAAI,CAAC;AAAA,IAClE,WAAW,IAAI;AAAA,EACjB;AACF;AAaO,IAAM,oBAAN,MAAwB;AAAA,EACZ;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,oBAAoB,KAAK,GAAG;AAAA,MAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMF;AAEA,SAAK,wBAAwB,KAAK,GAAG;AAAA,MACnC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AAEA,SAAK,oBAAoB,KAAK,GAAG,QAAQ,mCAAmC;AAE5E,SAAK,qBAAqB,KAAK,GAAG;AAAA,MAChC;AAAA;AAAA;AAAA;AAAA,IAIF;AAEA,SAAK,kBAAkB,KAAK,GAAG;AAAA,MAC7B;AAAA,IACF;AAEA,SAAK,kBAAkB,KAAK,GAAG;AAAA,MAC7B;AAAA;AAAA;AAAA,IAGF;AAEA,SAAK,4BAA4B,KAAK,GAAG;AAAA,MACvC;AAAA;AAAA;AAAA;AAAA,IAIF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,OAAmE;AACxE,UAAM,WAAW,KAAK,GAAG,YAAY,CAAC,QAA4B;AAChE,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,YAAM,WAAW,KAAK,GACnB,QAAQ,sEAAsE,EAC9E,IAAI,IAAI,cAAc,IAAI,YAAY;AAEzC,UAAI,UAAU;AAEZ,aAAK,mBAAmB;AAAA,UACtB;AAAA,UACA,IAAI,SAAS;AAAA,UACb,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI,aAAa,SAAS;AAAA,UAC1B,SAAS;AAAA,QACX;AAGA,aAAK,gBAAgB;AAAA,UACnBD,SAAO,WAAW;AAAA,UAClB,SAAS;AAAA,UACT,IAAI,SAAS;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAEA,eAAO,EAAE,SAAS,KAAK,SAAS,SAAS,EAAE,GAAI,SAAS,MAAM;AAAA,MAChE,OAAO;AAEL,cAAM,KAAKA,SAAO,WAAW;AAC7B,aAAK,kBAAkB;AAAA,UACrB;AAAA,UACA,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI,UAAU;AAAA,UACd,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,IAAI,SAAS;AAAA,UACb;AAAA;AAAA,UACA;AAAA;AAAA,UACA;AAAA;AAAA,UACA,IAAI,SAAS;AAAA;AAAA,UACb,IAAI,SAAS;AAAA;AAAA,UACb;AAAA;AAAA,UACA;AAAA;AAAA,UACA;AAAA;AAAA,UACA,IAAI,aAAa;AAAA,QACnB;AAGA,aAAK,gBAAgB;AAAA,UACnBA,SAAO,WAAW;AAAA,UAClB;AAAA,UACA,IAAI,SAAS;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAEA,eAAO,EAAE,SAAS,KAAK,SAAS,EAAE,GAAI,SAAS,KAAK;AAAA,MACtD;AAAA,IACF,CAAC;AAED,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAiC;AACxC,UAAM,MAAM,KAAK,sBAAsB,IAAI,EAAE;AAC7C,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,aAAa,GAAG;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBACE,cACA,MACW;AACX,UAAM,eAAyB,CAAC,mBAAmB;AACnD,UAAM,SAAoB,CAAC,YAAY;AAEvC,QAAI,MAAM,UAAU,QAAW;AAC7B,mBAAa,KAAK,WAAW;AAC7B,aAAO,KAAK,KAAK,KAAK;AAAA,IACxB;AAEA,QAAI,MAAM,aAAa,QAAW;AAChC,mBAAa,KAAK,cAAc;AAChC,aAAO,KAAK,KAAK,QAAQ;AAAA,IAC3B;AAEA,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,yBAKS,aAAa,KAAK,OAAO,CAAC;AAAA;AAG/C,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAC/C,WAAO,KAAK,IAAI,YAAY;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,IAAY,OAAe,QAAsC;AAC3E,UAAM,gBAAgB,KAAK,GAAG;AAAA,MAC5B,CAAC,WAAmB,UAAkB,gBAAyB;AAE7D,cAAM,WAAW,KAAK,SAAS,SAAS;AACxC,YAAI,aAAa,QAAW;AAC1B,iBAAO;AAAA,QACT;AAEA,cAAM,WAAW,SAAS;AAG1B,aAAK,gBAAgB,IAAI,UAAU,eAAe,MAAM,SAAS;AAGjE,cAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,aAAK,gBAAgB;AAAA,UACnBA,SAAO,WAAW;AAAA,UAClB;AAAA,UACA;AAAA;AAAA,UACA;AAAA,UACA,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,UAClC,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,UAClC;AAAA;AAAA,UACA;AAAA,QACF;AAEA,eAAO,KAAK,SAAS,SAAS;AAAA,MAChC;AAAA,IACF;AAEA,WAAO,cAAc,IAAI,OAAO,MAAM;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,WAAmB,OAA8C;AACxE,UAAM,KAAKA,SAAO,WAAW;AAC7B,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,SAAK,gBAAgB;AAAA,MACnB;AAAA,MACA;AAAA,MACA,MAAM,SAAS;AAAA,MACf,MAAM;AAAA,MACN,MAAM,cAAc;AAAA,MACpB,MAAM,aAAa;AAAA,MACnB,MAAM,cAAc;AAAA,MACpB;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAC1D,WAAW,MAAM;AAAA,MACjB,YAAY,MAAM,cAAc;AAAA,MAChC,WAAW,MAAM,aAAa;AAAA,MAC9B,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,MACzE,WAAW;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,WAAmC;AAC3C,UAAM,OAAO,KAAK,0BAA0B,IAAI,SAAS;AACzD,WAAO,KAAK,IAAI,iBAAiB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,IAAqB;AAC1B,UAAM,SAAS,KAAK,kBAAkB,IAAI,EAAE;AAC5C,WAAO,OAAO,UAAU;AAAA,EAC1B;AACF;;;ACtXA,OAAOE,cAAY;AA+BnB,SAAS,kBAAkB,KAAoC;AAC7D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,cAAc,IAAI;AAAA,IAClB,GAAI,IAAI,WAAW,OAAO,EAAE,OAAO,IAAI,OAAO,IAAI,CAAC;AAAA,IACnD,OAAO,IAAI;AAAA,IACX,cAAc,IAAI;AAAA,IAClB,UAAU,IAAI;AAAA,IACd,YAAY,IAAI;AAAA,IAChB,SAAS,IAAI;AAAA,IACb,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,iBAAiB,IAAI;AAAA,IACrB,kBAAkB,IAAI;AAAA,IACtB,GAAI,IAAI,kBAAkB,OAAO,EAAE,cAAc,IAAI,cAAc,IAAI,CAAC;AAAA,IACxE,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAcO,IAAM,yBAAN,MAA6B;AAAA,EACjB;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAYC,KAAuB;AACjC,SAAK,KAAKA;AAEV,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMF;AAEA,SAAK,iBAAiB,KAAK,GAAG;AAAA,MAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AAEA,SAAK,yBAAyB,KAAK,GAAG;AAAA,MACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQF;AAEA,SAAK,aAAa,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,OAAO,OAA8C;AACnD,UAAM,KAAKD,SAAO,WAAW;AAC7B,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,eAAe,MAAM,gBAAgB;AAC3C,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,aAAa,MAAM,cAAc;AACvC,UAAM,UAAU,MAAM,WAAW;AACjC,UAAM,WAAW,MAAM,YAAY;AACnC,UAAM,YAAY,MAAM,aAAa;AACrC,UAAM,kBAAkB,MAAM,mBAAmB;AACjD,UAAM,mBAAmB,MAAM,oBAAoB;AACnD,UAAM,eAAe,MAAM,gBAAgB;AAC3C,UAAM,YAAY,MAAM,aAAa;AAErC,SAAK,WAAW;AAAA,MACd;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,cAAc,MAAM;AAAA,MACpB,GAAI,UAAU,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,MAClC,OAAO,MAAM;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,iBAAiB,OAAO,EAAE,aAAa,IAAI,CAAC;AAAA,MAChD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,IAAsC;AAC7C,UAAM,MAAM,KAAK,eAAe,IAAI,EAAE;AACtC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,kBAAkB,GAAG;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,iBAAiB,cAAsB,OAAgC;AACrE,UAAM,iBAAiB,SAAS;AAChC,UAAM,OAAO,KAAK,uBAAuB,IAAI,cAAc,cAAc;AACzE,WAAO,KAAK,IAAI,iBAAiB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,cAAgD;AACrD,UAAM,MAAM,KAAK,WAAW,IAAI,YAAY;AAC5C,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,kBAAkB,GAAG;AAAA,EAC9B;AACF;;;AFhNO,SAAS,sBAAsBE,SAAmBC,KAA6B;AACpF,QAAM,cAAc,IAAI,kBAAkBA,GAAE;AAC5C,QAAM,mBAAmB,IAAI,uBAAuBA,GAAE;AAEtD,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,QAAQE,GAAE,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACD,IAAIA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,WAAW;AAAA,MAC9C,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MAC5D,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,uBAAuB;AAAA,MACpE,QAAQA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,MAC3D,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MACrD,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kBAAkB;AAAA,MAC3D,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,MAC/D,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,eAAe;AAAA,MACrD,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yBAAyB;AAAA,MACrE,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,QAAQ;AAAA,MAC9C,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,uBAAuB;AAAA,MACjE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,MAC9D,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,YAAY;AAAA,MAClD,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA,MAClE,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,iBAAiB;AAAA,MAC1D,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mBAAmB;AAAA,MAC9D,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,gBAAgB;AAAA,MACxD,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,iBAAiB;AAAA,MAC1D,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kBAAkB;AAAA,MAC5D,iBAAiBA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mBAAmB;AAAA,MACnE,kBAAkBA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0BAA0B;AAAA,MAC3E,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,MACjE,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,cAAc;AAAA,IACtD;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAAM;AACJ,cAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA,QAId,KAAK,kBAAkB;AACrB,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,OAAO;AACV,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,iDAAiD;AAAA,cACzE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,oDAAoD;AAAA,cAC5E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,YAAY;AACf,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAS,YAAY,OAAO;AAAA,YAChC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AACD,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU,EAAE,GAAG,OAAO,SAAS,SAAS,OAAO,QAAQ,GAAG,MAAM,CAAC;AAAA,cAC9E;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QAEA,KAAK,eAAe;AAClB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,2CAA2C;AAAA,cACnE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,UAAU,YAAY,SAAS,EAAE;AACvC,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,sBAAsB,EAAE,GAAG,CAAC;AAAA,cAC5D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC/E;AAAA,QAEA,KAAK,iBAAiB;AACpB,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,OAA8C,CAAC;AACrD,cAAI,MAAO,MAAK,QAAQ;AACxB,cAAI,SAAU,MAAK,WAAW;AAC9B,gBAAM,WAAW,YAAY,iBAAiB,cAAc,IAAI;AAChE,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,CAAC;AAAA,UACrE;AAAA,QACF;AAAA,QAEA,KAAK,wBAAwB;AAC3B,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,oDAAoD;AAAA,cAC5E;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,CAAC,OAAO;AACV,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,UAAU,YAAY,YAAY,IAAI,OAAO,WAAW;AAC9D,cAAI,CAAC,SAAS;AACZ,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,sBAAsB,EAAE,GAAG,CAAC;AAAA,cAC5D,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC/E;AAAA,QAEA,KAAK,uBAAuB;AAC1B,cAAI,CAAC,WAAW;AACd,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAS,YAAY,UAAU,SAAS;AAC9C,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA;AAAA;AAAA;AAAA,QAKA,KAAK,wBAAwB;AAC3B,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,cAAI,UAAU,UAAa,UAAU,MAAM;AACzC,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,WAAW,iBAAiB,OAAO;AAAA,YACvC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AACD,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,CAAC;AAAA,UACrE;AAAA,QACF;AAAA,QAEA,KAAK,qBAAqB;AACxB,cAAI,CAAC,IAAI;AACP,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP,EAAE,MAAM,QAAQ,MAAM,iDAAiD;AAAA,cACzE;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,WAAW,iBAAiB,SAAS,EAAE;AAC7C,cAAI,CAAC,UAAU;AACb,mBAAO;AAAA,cACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,4BAA4B,EAAE,GAAG,CAAC;AAAA,cAClE,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAChF;AAAA,QAEA,KAAK,uBAAuB;AAC1B,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,YAAY,iBAAiB,iBAAiB,cAAc,KAAK;AACvE,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,WAAW,MAAM,CAAC,EAAE,CAAC;AAAA,UACtE;AAAA,QACF;AAAA,QAEA,KAAK,wBAAwB;AAC3B,cAAI,CAAC,cAAc;AACjB,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM;AAAA,gBACR;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,gBAAM,SAAS,iBAAiB,OAAO,YAAY;AACnD,cAAI,CAAC,QAAQ;AACX,mBAAO;AAAA,cACL,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,0CAA0C,YAAY;AAAA,gBAC9D;AAAA,cACF;AAAA,cACA,SAAS;AAAA,YACX;AAAA,UACF;AACA,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,QAC9E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AG1VO,SAAS,kBAAkBC,SAAmBC,KAA6B;AAChF,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,WAAW,IAAI,eAAeA,GAAE;AACtC,QAAM,cAAc,IAAI,uBAAuBA,GAAE;AAGjD,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,aACE;AAAA,IACJ;AAAA,IACA,OAAO,QAAQ;AACb,YAAM,YAAY,IAAI,cAAc,IAAI,MAAM;AAC9C,UAAI;AACJ,UAAI,aAAa,WAAW,SAAS,SAAwC,GAAG;AAC9E,gBAAQ,SAAS,WAAW,SAAwC;AAAA,MACtE,OAAO;AAEL,gBAAQ,WAAW,QAAQ,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AAAA,MAC1D;AACA,YAAM,SAAS,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,EAAE;AAC1E,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK,IAAI;AAAA,YACT,UAAU;AAAA,YACV,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAAA,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,aAAa,uDAAuD;AAAA,IACtE,OAAO,QAAQ;AACb,YAAM,SAAS,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK;AAChD,YAAM,OAAO,SAAS,SAAS,MAAM;AACrC,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,UACL,UAAU;AAAA,YACR;AAAA,cACE,KAAK,IAAI;AAAA,cACT,UAAU;AAAA,cACV,MAAM,KAAK,UAAU,EAAE,OAAO,mBAAmB,MAAM,GAAG,CAAC;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,WAAW,SAAS,aAAa,KAAK,EAAE;AAC9C,YAAM,UAAU,SAAS,aAAa,KAAK,EAAE;AAC7C,YAAM,kBAAkB,oBAAI,IAAI;AAAA,QAC9B,GAAG,SAAS,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,QACjC,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,MAClC,CAAC;AACD,YAAM,gBAAgB,CAAC,GAAG,eAAe,EACtC,IAAI,CAAC,QAAQ,SAAS,SAAS,GAAG,CAAC,EACnC,OAAO,OAAO,EACd,IAAI,CAAC,OAAO,EAAE,GAAG,GAAI,OAAO,KAAK,MAAM,EAAG,SAAS,EAAE,EAAE;AAE1D,YAAM,SAAS;AAAA,QACb,GAAG;AAAA,QACH,OAAO,KAAK,MAAM,KAAK,SAAS;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK,IAAI;AAAA,YACT,UAAU;AAAA,YACV,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAAA,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,aAAa,4CAA4C;AAAA,IAC3D,YAAY;AACV,YAAM,aAAqC,CAAC;AAC5C,iBAAW,KAAK,YAAY;AAC1B,mBAAW,CAAC,IAAI,SAAS,WAAW,CAAC,EAAE;AAAA,MACzC;AACA,YAAM,YAAaC,IAAG,QAAQ,mCAAmC,EAAE,IAAI,EACpE;AACH,YAAM,gBACJA,IAAG,QAAQ,uCAAuC,EAAE,IAAI,EACxD;AAEF,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,YACV,MAAM,KAAK;AAAA,cACT,EAAE,OAAO,YAAY,OAAO,WAAW,WAAW,cAAc;AAAA,cAChE;AAAA,cACA;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAAD,QAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,aAAa,iDAAiD;AAAA,IAChE,YAAY;AACV,YAAM,aAAa,YAAY,eAAe;AAC9C,aAAO;AAAA,QACL,UAAU;AAAA,UACR;AAAA,YACE,KAAK;AAAA,YACL,UAAU;AAAA,YACV,MAAM,KAAK,UAAU,YAAY,MAAM,CAAC;AAAA,UAC1C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;A7B7HO,SAAS,gBAAgBE,KAAuB,SAA6B;AAClF,QAAMC,UAAS,IAAI,UAAU;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,EACtB,CAAC;AAGD,oBAAkBA,SAAQD,GAAE;AAC5B,qBAAmBC,SAAQD,GAAE;AAC7B,qBAAmBC,SAAQD,GAAE;AAC7B,sBAAoBC,SAAQD,GAAE;AAC9B,kBAAgBC,SAAQD,GAAE;AAC1B,mBAAiBC,SAAQD,GAAE;AAC3B,wBAAsBC,SAAQD,GAAE;AAGhC,oBAAkBC,SAAQD,GAAE;AAE5B,SAAOC;AACT;;;AT5BA,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,MAAM,KAAK,MAAM,aAAa,QAAQ,WAAW,iBAAiB,GAAG,OAAO,CAAC;AAInF,IAAM,UAAU,QAAQ,IAAI,iBAAiB,KAAK;AAClD,IAAM,KAAK,IAAI,SAAS,OAAO;AAC/B,gBAAgB,EAAE;AAElB,IAAM,SAAS,gBAAgB,IAAI,IAAI,OAAO;AAC9C,IAAM,YAAY,IAAI,qBAAqB;AAC3C,MAAM,OAAO,QAAQ,SAAS;","names":["db","migration","db","migration","db","migration","db","migration","db","randomUUID","migration","db","db","migration","db","z","crypto","randomUUID","db","crypto","rows","crypto","db","rows","rowToEdge","db","currentId","server","db","z","z","db","server","z","z","crypto","transport","isRecord","db","db","crypto","server","db","z","path","z","db","server","db","z","fs","z","crypto","db","fs","path","path","fs","db","fs","path","fs","path","server","db","z","fs","z","crypto","db","crypto","db","crypto","db","rows","crypto","db","server","db","z","z","crypto","db","crypto","db","server","db","z","server","db","db","server"]}
|