diff --git a/.gitignore b/.gitignore index 0534703f..32e696c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .env +.claude/ +optimized_basis_v3.bin +4-Infrastructure/deploy/ + **/node_modules/ **/__pycache__/ *.db diff --git a/4-Infrastructure/infra/credential_provider.py b/4-Infrastructure/infra/credential_provider.py new file mode 100644 index 00000000..4bb90c88 --- /dev/null +++ b/4-Infrastructure/infra/credential_provider.py @@ -0,0 +1,305 @@ +from __future__ import annotations + +import json +import os +import socket +import urllib.parse +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +CREDENTIAL_PROVIDER_VERSION = "0.4" + +# Remote credential server (microVM) — all nodes query this as primary +_cred_server_url = os.environ.get("RS_CREDENTIAL_SERVER", "http://100.101.247.127:8444") + +# Config file path — local fallback if remote is unreachable +_cred_config_path = os.environ.get( + "RS_CREDENTIAL_CONFIG", + "/etc/rs-surface/credentials.json", +) + +RDS_HOST = os.environ.get( + "RDS_HOST", + "database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com", +) +RDS_PORT = int(os.environ.get("RDS_PORT", "5432")) +RDS_USER = os.environ.get("RDS_USER", "postgres") +RDS_IAM_TOKEN = os.environ.get("RDS_IAM_TOKEN", "") + + +@dataclass +class Credential: + provider: str + key_name: str + value: str + metadata: dict[str, Any] = field(default_factory=dict) + + +PROVIDER_ENV_MAP: dict[str, dict[str, str]] = { + "deepseek": { + "env_var": "DEEPSEEK_API_KEY", + "description": "DeepSeek LLM API", + }, + "quandela": { + "env_var": "QUANDELA_API_KEY", + "description": "Quandela quantum cloud", + }, + "wolfram_alpha": { + "env_var": "WOLFRAM_ALPHA_APPID", + "description": "Wolfram Alpha API", + }, + "notion": { + "env_var": "NOTION_API_KEY", + "description": "Notion API", + }, + "linear": { + "env_var": "LINEAR_API_KEY", + "description": "Linear API", + }, + "gemini": { + "env_var": "GEMINI_API_KEY", + "description": "Google Gemini API", + }, + "ollama": { + "env_var": "OLLAMA_API_KEY", + "description": "Ollama local API", + }, + "brave_search": { + "env_var": "BRAVE_API_KEY", + "description": "Brave Search API", + }, + "neural_endeavor": { + "env_var": "ENE_ENCRYPTION_KEY", + "description": "ENE encryption master key", + }, + "bedrock": { + "env_var": "AWS_BEARER_TOKEN_BEDROCK", + "description": "Amazon Bedrock API", + }, + "venice": { + "env_var": "VENICE_API_KEY", + "description": "Venice AI API", + }, +} + + +def _rds_connect(): + import psycopg2 + import os + + # Primary check: RDS_IAM_TOKEN env var + password = RDS_IAM_TOKEN + + if not password: + # Generate dynamic IAM authentication token using boto3 + try: + import boto3 + region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1") + client = boto3.client('rds', region_name=region) + password = client.generate_db_auth_token( + DBHostname=RDS_HOST, + Port=RDS_PORT, + DBUsername=RDS_USER, + Region=region + ) + except Exception as e: + # Fallback to standard environment password or log warning + pass + + if not password: + password = os.environ.get("RDS_PASSWORD", "") + + return psycopg2.connect( + host=RDS_HOST, + port=RDS_PORT, + user=RDS_USER, + password=password, + dbname="postgres", + connect_timeout=10, + sslmode="require", + ) + + +def _load_from_rds() -> list[Credential]: + try: + conn = _rds_connect() + cur = conn.cursor() + cur.execute(""" + SELECT pkg, provider, encode(encrypted_payload, 'escape'), classification + FROM credential_store.credentials + WHERE is_active = TRUE + ORDER BY provider + """) + creds = [] + for pkg, provider, payload_raw, classification in cur.fetchall(): + creds.append(Credential( + provider=provider, + key_name=pkg, + value=payload_raw, + metadata={ + "source": "rds", + "pkg": pkg, + "classification": classification, + }, + )) + conn.close() + return creds + except Exception as e: + return [] + + +def _load_from_remote() -> list[Credential]: + """Fetch credentials from the remote credential server (microVM).""" + import urllib.request + import urllib.error + + # Skip self-query: don't try to connect to ourselves + self_hosts = {"localhost", "127.0.0.1", "0.0.0.0", "", socket.gethostname()} + netloc = urllib.parse.urlparse(_cred_server_url).hostname or "" + if netloc in self_hosts: + return [] + + try: + req = urllib.request.Request(_cred_server_url + "/credentials") + with urllib.request.urlopen(req, timeout=5) as resp: + manifest = json.loads(resp.read().decode("utf-8")) + except (urllib.error.URLError, json.JSONDecodeError, OSError): + return [] + + providers = manifest.get("providers", []) + creds: list[Credential] = [] + for p in providers: + name = p.get("name") + if not name: + continue + try: + req = urllib.request.Request(f"{_cred_server_url}/credentials/{name}") + with urllib.request.urlopen(req, timeout=5) as resp: + data = json.loads(resp.read().decode("utf-8")) + except (urllib.error.URLError, json.JSONDecodeError, OSError): + continue + if data.get("ok"): + creds.append(Credential( + provider=name, + key_name=data.get("key_name", f"remote/{name}"), + value=data["key"], + metadata={"source": "remote", "server": _cred_server_url}, + )) + return creds + + +def _load_from_config() -> list[Credential]: + """Read credentials from a JSON config file. + + Format: + { + "deepseek": "sk-...", + "bedrock": "ABSK...", + "ollama": "..." + } + """ + path = Path(_cred_config_path) + if not path.exists(): + return [] + try: + with path.open("r", encoding="utf-8") as f: + data = json.load(f) + except (json.JSONDecodeError, OSError): + return [] + if not isinstance(data, dict): + return [] + creds: list[Credential] = [] + for provider_name, value in data.items(): + if not isinstance(value, str) or not value.strip(): + continue + cfg = PROVIDER_ENV_MAP.get(provider_name, {}) + creds.append(Credential( + provider=provider_name, + key_name=f"config/{provider_name}", + value=value.strip(), + metadata={ + "description": cfg.get("description", provider_name), + "source": "config_file", + "file": str(path), + }, + )) + return creds + + +def _load_from_env() -> list[Credential]: + creds: list[Credential] = [] + for provider, cfg in PROVIDER_ENV_MAP.items(): + value = os.getenv(cfg["env_var"]) + if not value: + continue + creds.append(Credential( + provider=provider, + key_name=cfg["env_var"], + value=value, + metadata={"description": cfg["description"], "source": "env"}, + )) + return creds + + +def load_credentials() -> list[Credential]: + creds = _load_from_rds() + if creds: + return creds + creds = _load_from_remote() + if creds: + return creds + creds = _load_from_config() + if creds: + return creds + return _load_from_env() + + +def _detect_backend() -> str: + if _load_from_rds(): + return "rds" + if _load_from_remote(): + return "remote" + if _load_from_config(): + return "config_file" + return "env" + + +def credential_status() -> dict[str, Any]: + creds = load_credentials() + return { + "ok": True, + "node": os.environ.get("RS_SURFACE_NODE_ID", "unknown"), + "provider_version": CREDENTIAL_PROVIDER_VERSION, + "backend": _detect_backend(), + "available_providers": [c.provider for c in creds], + "count": len(creds), + "remote_server": _cred_server_url, + } + + +def resolve_credential(provider: str) -> Credential | None: + creds = load_credentials() + for c in creds: + if c.provider == provider: + return c + return None + + +def provider_manifest() -> dict[str, Any]: + creds = load_credentials() + return { + "ok": True, + "service_kind": "apiProvider", + "backend": _detect_backend(), + "providers": [ + { + "name": c.provider, + "description": c.metadata.get("description", ""), + "key_name": c.key_name, + "available": True, + } + for c in creds + ], + "node_id": os.environ.get("RS_SURFACE_NODE_ID", "unknown"), + } diff --git a/4-Infrastructure/infra/credential_server.py b/4-Infrastructure/infra/credential_server.py new file mode 100644 index 00000000..7bdb3144 --- /dev/null +++ b/4-Infrastructure/infra/credential_server.py @@ -0,0 +1,319 @@ +"""Credential server — REST + OpenAPI access point for API keys. + +Reads credentials from credential_provider (env vars + config file + RDS) +and serves them via HTTP so no script needs direct env var access. + +Usage: + # Standalone + python3 credential_server.py [--port 8444] [--bind 0.0.0.0] + + # Via existing embedded surface (if RS_CREDENTIAL_SERVER_PORT is set) +""" + +from __future__ import annotations + +import json +import os +import re +import sys +import urllib.request +import urllib.parse +from http import HTTPStatus +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Any + +from credential_provider import ( + credential_status, + provider_manifest, + resolve_credential, + _rds_connect, + _cred_server_url +) + +VERSION = "0.4" +OPENAPI_VERSION = "3.0.3" + +OPENAPI_SPEC: dict[str, Any] = { + "openapi": OPENAPI_VERSION, + "info": { + "title": "Research Stack Credential Provider", + "description": "Central API key access point for all stack services. " + "Set keys once on the microVM, consume from anywhere.", + "version": VERSION, + }, + "servers": [ + {"url": "http://localhost:8444", "description": "Local / Tailnet default"}, + ], + "paths": { + "/": { + "get": { + "summary": "Service root", + "responses": {"200": {"description": "Service info + links"}}, + } + }, + "/health": { + "get": { + "summary": "Health check", + "responses": {"200": {"description": "OK"}}, + } + }, + "/openapi.json": { + "get": { + "summary": "OpenAPI 3.0 specification", + "responses": {"200": {"description": "This document"}}, + } + }, + "/credentials": { + "get": { + "summary": "List all available credential providers", + "responses": { + "200": { + "description": "Provider manifest", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "description": {"type": "string"}, + "available": {"type": "boolean"}, + }, + }, + } + }, + } + } + }, + } + }, + } + }, + "/credentials/{provider}": { + "get": { + "summary": "Resolve a specific provider credential", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": True, + "schema": {"type": "string"}, + "description": "Provider name (deepseek, bedrock, etc.)", + } + ], + "responses": { + "200": {"description": "Credential value"}, + "404": {"description": "Provider not found"}, + }, + } + }, + "/status": { + "get": { + "summary": "Credential provider status", + "responses": {"200": {"description": "Backend info + counts"}}, + } + }, + }, +} + + +SERVICE_INFO: dict[str, Any] = { + "service": "credential-provider", + "version": VERSION, + "docs": "/openapi.json", + "credentials": "/credentials", + "status": "/status", + "health": "/health", +} + + +class CredentialHTTPHandler(BaseHTTPRequestHandler): + + def log_message(self, fmt, *args): + sys.stderr.write(f"[credential-server] {args[0]} {args[1]} {args[2]}\n") + + def _send_json(self, data: Any, status: int = 200): + body = json.dumps(data, sort_keys=True, indent=2).encode("utf-8") + b"\n" + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(body) + + def _send_error(self, status: int, message: str): + self._send_json({"ok": False, "error": message}, status) + + def do_GET(self): + path = self.path.rstrip("/") or "/" + + if path in ("/", "/api"): + return self._send_json(SERVICE_INFO) + + if path in ("/health", "/api/health"): + return self._send_json({"status": "ok", "version": VERSION}) + + if path == "/openapi.json": + return self._send_json(OPENAPI_SPEC) + + if path in ("/status", "/api/status"): + return self._send_json(credential_status()) + + if path == "/credentials" or path == "/api/credentials": + return self._send_json(provider_manifest()) + + m = re.match(r"^(?:/api)?/credentials/([a-zA-Z0-9_-]+)$", path) + if m: + provider = m.group(1).lower() + cred = resolve_credential(provider) + if cred is None: + return self._send_error(404, f"provider '{provider}' not found") + return self._send_json({ + "ok": True, + "provider": cred.provider, + "key": cred.value, + "key_name": cred.key_name, + }) + + return self._send_error(404, f"not found: {path}") + + def do_POST(self): + path = self.path.rstrip("/") or "/" + + if path in ("/api/webhooks/linear", "/webhooks/linear"): + # Read raw body + content_length = int(self.headers.get("Content-Length", 0)) + raw_body = self.rfile.read(content_length) + + # Node identification logic: proxy or process + node_id = os.environ.get("RS_SURFACE_NODE_ID", "") + if node_id != "MicroVM-Racknerd": + # Act as Proxy: Forward POST request to the central microVM + target_url = f"{_cred_server_url}/api/webhooks/linear" + req = urllib.request.Request(target_url, data=raw_body, method="POST") + for k, v in self.headers.items(): + if k.lower() in ("content-type", "linear-signature"): + req.add_header(k, v) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + resp_data = json.loads(resp.read().decode("utf-8")) + return self._send_json(resp_data, resp.status) + except urllib.error.HTTPError as e: + try: + err_data = json.loads(e.read().decode("utf-8")) + return self._send_json(err_data, e.code) + except Exception: + return self._send_error(e.code, f"Forwarding failed: {e}") + except Exception as e: + return self._send_error(500, f"Forwarding connection error: {e}") + + # Act as Server: Process Linear webhook body and upsert into Aurora RDS ENE packages table + try: + data = json.loads(raw_body.decode("utf-8")) + except Exception as e: + return self._send_error(400, f"Invalid JSON payload: {e}") + + action = data.get("action") + issue_data = data.get("data", {}) + payload_type = data.get("type") + + if payload_type != "Issue": + return self._send_json({"ok": True, "message": f"Ignored non-Issue payload: {payload_type}"}) + + identifier = issue_data.get("identifier") + if not identifier: + return self._send_error(400, "Missing issue identifier") + + pkg_id = f"linear/{identifier}" + + if action == "remove": + try: + conn = _rds_connect() + cur = conn.cursor() + cur.execute("DELETE FROM ene.packages WHERE pkg = %s", (pkg_id,)) + conn.commit() + conn.close() + return self._send_json({"ok": True, "action": "delete", "pkg": pkg_id}) + except Exception as e: + return self._send_error(500, f"Database delete error: {e}") + + # Extract title and description + title = issue_data.get("title", "") + description = issue_data.get("description", "") + full_text = f"{title}\n\n{description}" if description else title + url = issue_data.get("url", "") + + # Extract labels + labels_data = issue_data.get("labels", []) + labels_list = [] + if isinstance(labels_data, list): + for l in labels_data: + if isinstance(l, dict): + labels_list.append(l.get("name")) + elif isinstance(l, str): + labels_list.append(l) + tags_json = json.dumps(labels_list) + + import datetime + now_iso = datetime.datetime.now(datetime.timezone.utc).isoformat() + + try: + conn = _rds_connect() + cur = conn.cursor() + cur.execute(""" + INSERT INTO ene.packages ( + pkg, version, domain, tier, archetype, + tags, description, source, indexed_utc + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + ON CONFLICT (pkg) DO UPDATE SET + description = EXCLUDED.description, + tags = EXCLUDED.tags, + source = EXCLUDED.source, + indexed_utc = EXCLUDED.indexed_utc + """, ( + pkg_id, + "1.0.0", + "LINEAR", + "INTENT", + "issue", + tags_json, + full_text, + url, + now_iso + )) + conn.commit() + conn.close() + return self._send_json({"ok": True, "action": action, "pkg": pkg_id}) + except Exception as e: + return self._send_error(500, f"Database upsert error: {e}") + + return self._send_error(404, f"not found: {path}") + + do_HEAD = do_GET + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description="Research Stack Credential Server") + parser.add_argument("--port", type=int, default=int(os.environ.get("RS_CREDENTIAL_PORT", "8444"))) + parser.add_argument("--bind", default=os.environ.get("RS_CREDENTIAL_BIND", "0.0.0.0")) + args = parser.parse_args() + + server = ThreadingHTTPServer((args.bind, args.port), CredentialHTTPHandler) + print(f"[credential-server] listening on {args.bind}:{args.port}", flush=True) + print(f"[credential-server] OpenAPI spec at http://{args.bind}:{args.port}/openapi.json", flush=True) + try: + server.serve_forever() + except KeyboardInterrupt: + print("\n[credential-server] shutting down", flush=True) + server.server_close() + + +if __name__ == "__main__": + main() diff --git a/4-Infrastructure/infra/ene_rds_fractal_fold.py b/4-Infrastructure/infra/ene_rds_fractal_fold.py new file mode 100644 index 00000000..8a179425 --- /dev/null +++ b/4-Infrastructure/infra/ene_rds_fractal_fold.py @@ -0,0 +1,592 @@ +"""RDS-backed ENEFractalFold — drop-in replacement for SQLite ENEFractalFold. + +API-compatible with ene_fractal_fold.ENEFractalFold: same dataclasses, same +handle_request() protocol, but backed by PostgreSQL via psycopg2. + +Constructor: + ENERDSFractalFold(dsn="postgresql://user:pass@host:5432/dbname") + +The DSN defaults to the RDS_HOST / RDS_PORT / RDS_USER / RDS_PASSWORD / RDS_DB +environment variables. +""" + +from __future__ import annotations + +import base64 +import json +import math +import time +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +import psycopg2 +import psycopg2.extras + +from infra.ene_fractal_fold import ( + FractalNode, + FractalManifest, + VERSION, + GOLDEN_ANGLE, + canonical_json, + sha256_text, + sha256_bytes, + gray_code, + inverse_gray_code, + golden_spiral_point, + manifold_distance, + tree_depth, + make_leaf, + make_parent, + encode_fractal, + encode_fractal_chunks, + parse_graphml_concepts, + node_record, + archive_record, + jsonl_event, +) + + +import os + + +def _default_dsn() -> str: + host = os.environ.get("RDS_HOST", "database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com") + port = os.environ.get("RDS_PORT", "5432") + user = os.environ.get("RDS_USER", "postgres") + password = os.environ.get("RDS_PASSWORD") or os.environ.get("RDS_IAM_TOKEN", "") + dbname = os.environ.get("RDS_DB", "postgres") + return f"host={host} port={port} dbname={dbname} user={user} password={password} sslmode=require" + + +class ENERDSFractalFold: + def __init__(self, dsn: str | None = None): + self.dsn = dsn or _default_dsn() + self._init_db() + + def _get_conn(self): + return psycopg2.connect(self.dsn) + + def _init_db(self) -> None: + with self._get_conn() as conn: + with conn.cursor() as cur: + cur.execute("CREATE SCHEMA IF NOT EXISTS ene") + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.fractal_manifolds ( + root_hash TEXT PRIMARY KEY, + name TEXT NOT NULL, + byte_len INTEGER NOT NULL, + leaves_count INTEGER NOT NULL, + depth INTEGER NOT NULL, + chunk_size INTEGER NOT NULL, + branching_factor INTEGER NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + receipt TEXT NOT NULL, + archive_record JSONB NOT NULL DEFAULT '{}', + jsonl_event JSONB NOT NULL DEFAULT '{}' + ) + """) + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.fractal_nodes ( + root_hash TEXT NOT NULL, + node_hash TEXT NOT NULL, + kind TEXT NOT NULL, + level INTEGER NOT NULL, + ordinal INTEGER NOT NULL, + fold_address INTEGER NOT NULL, + start_leaf INTEGER NOT NULL, + end_leaf INTEGER NOT NULL, + size_bytes INTEGER NOT NULL, + children TEXT NOT NULL, + payload_b64 TEXT, + PRIMARY KEY (root_hash, node_hash) + ) + """) + cur.execute(""" + CREATE INDEX IF NOT EXISTS idx_rds_fractal_leaf + ON ene.fractal_nodes (root_hash, level, ordinal) + """) + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.fractal_graph_entities ( + root_hash TEXT NOT NULL, + graph_node_id TEXT NOT NULL, + leaf_index INTEGER NOT NULL, + name TEXT NOT NULL, + family TEXT, + domain TEXT, + neighbors TEXT NOT NULL, + PRIMARY KEY (root_hash, graph_node_id) + ) + """) + cur.execute(""" + CREATE INDEX IF NOT EXISTS idx_rds_fractal_graph_name + ON ene.fractal_graph_entities (root_hash, name) + """) + conn.commit() + + def _store(self, manifest: FractalManifest, nodes: list[FractalNode]) -> tuple[dict[str, Any], dict[str, Any]]: + record = archive_record(manifest) + event = jsonl_event(record, manifest) + with self._get_conn() as conn: + with conn.cursor() as cur: + cur.execute(""" + INSERT INTO ene.fractal_manifolds + (root_hash, name, byte_len, leaves_count, depth, chunk_size, branching_factor, + created_at, receipt, archive_record, jsonl_event) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + ON CONFLICT (root_hash) DO UPDATE SET + name = EXCLUDED.name, + byte_len = EXCLUDED.byte_len, + leaves_count = EXCLUDED.leaves_count, + depth = EXCLUDED.depth, + chunk_size = EXCLUDED.chunk_size, + branching_factor = EXCLUDED.branching_factor, + receipt = EXCLUDED.receipt, + archive_record = EXCLUDED.archive_record, + jsonl_event = EXCLUDED.jsonl_event + """, ( + manifest.root_hash, manifest.name, manifest.byte_len, + manifest.leaves_count, manifest.depth, manifest.chunk_size, + manifest.branching_factor, manifest.created_at, manifest.receipt, + json.dumps(record, sort_keys=True), json.dumps(event, sort_keys=True), + )) + + cur.execute("DELETE FROM ene.fractal_nodes WHERE root_hash = %s", (manifest.root_hash,)) + psycopg2.extras.execute_values( + cur, + """ + INSERT INTO ene.fractal_nodes + (root_hash, node_hash, kind, level, ordinal, fold_address, start_leaf, + end_leaf, size_bytes, children, payload_b64) + VALUES %s + """, + [ + ( + manifest.root_hash, node.node_hash, node.kind, node.level, + node.ordinal, node.fold_address, node.start_leaf, node.end_leaf, + node.size_bytes, canonical_json(node.children), node.payload_b64, + ) + for node in nodes + ], + ) + conn.commit() + return record, event + + def put(self, data: bytes, name: str = "unnamed", chunk_size: int = 4096, branching_factor: int = 4) -> dict[str, Any]: + manifest, nodes = encode_fractal(data, name, chunk_size, branching_factor) + record, event = self._store(manifest, nodes) + return { + "ok": True, "op": "fractal_put", + "manifest": asdict(manifest), + "archive_record": record, + "jsonl_event": event, + } + + def put_graphml(self, graphml: bytes, name: str = "graphml", branching_factor: int = 4) -> dict[str, Any]: + records, chunks = parse_graphml_concepts(graphml) + max_chunk = max((len(chunk) for chunk in chunks), default=0) + manifest, nodes = encode_fractal_chunks(chunks, name, max_chunk, branching_factor) + record, event = self._store(manifest, nodes) + with self._get_conn() as conn: + with conn.cursor() as cur: + cur.execute("DELETE FROM ene.fractal_graph_entities WHERE root_hash = %s", (manifest.root_hash,)) + psycopg2.extras.execute_values( + cur, + """ + INSERT INTO ene.fractal_graph_entities + (root_hash, graph_node_id, leaf_index, name, family, domain, neighbors) + VALUES %s + """, + [ + ( + manifest.root_hash, concept["graph_node_id"], + concept["leaf_index"], concept["name"], + concept["family"], concept["domain"], + canonical_json(concept["neighbors"]), + ) + for concept in records + ], + ) + conn.commit() + return { + "ok": True, "op": "fractal_graphml_put", + "manifest": asdict(manifest), + "graphml": {"concepts": len(records), "concept_leaf_mode": True}, + "archive_record": record, + "jsonl_event": event, + } + + def manifest(self, root_hash: str) -> dict[str, Any] | None: + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute("SELECT * FROM ene.fractal_manifolds WHERE root_hash = %s", (root_hash,)) + row = cur.fetchone() + if row is None: + return None + return { + "root_hash": row["root_hash"], + "name": row["name"], + "byte_len": row["byte_len"], + "leaves_count": row["leaves_count"], + "depth": row["depth"], + "chunk_size": row["chunk_size"], + "branching_factor": row["branching_factor"], + "created_at": row["created_at"].isoformat() if hasattr(row["created_at"], "isoformat") else str(row["created_at"]), + "receipt": row["receipt"], + "archive_record": row["archive_record"] if isinstance(row["archive_record"], dict) else json.loads(row["archive_record"] or "{}"), + "jsonl_event": row["jsonl_event"] if isinstance(row["jsonl_event"], dict) else json.loads(row["jsonl_event"] or "{}"), + } + + def _node(self, cur, root_hash: str, node_hash: str) -> dict[str, Any]: + cur.execute( + "SELECT * FROM ene.fractal_nodes WHERE root_hash = %s AND node_hash = %s", + (root_hash, node_hash), + ) + row = cur.fetchone() + if row is None: + raise KeyError(f"missing fractal node {node_hash}") + return dict(row) + + def proof(self, root_hash: str, leaf_index: int) -> dict[str, Any]: + meta = self.manifest(root_hash) + if meta is None: + raise KeyError(f"unknown root {root_hash}") + if leaf_index < 0 or leaf_index >= meta["leaves_count"]: + raise IndexError("leaf_index outside manifold") + + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + frontier = root_hash + path = [] + target_point = golden_spiral_point(gray_code(leaf_index), 0) + while True: + row = self._node(cur, root_hash, frontier) + children = json.loads(row["children"]) + node_point = golden_spiral_point(row["fold_address"], row["level"]) + entry = {k: v for k, v in dict(row).items() if k != "payload_b64"} + entry["children"] = children + entry["golden_spiral"] = node_point + entry["distance_to_target"] = round(manifold_distance(node_point, target_point), 9) + path.append(entry) + + if row["kind"] == "leaf": + payload = base64.b64decode(row["payload_b64"] or "") + break + + next_hash = None + pruned = [] + for child_hash in children: + child = self._node(cur, root_hash, child_hash) + child_point = golden_spiral_point(child["fold_address"], child["level"]) + pruned.append({ + "node_hash": child_hash, + "covers_target": child["start_leaf"] <= leaf_index <= child["end_leaf"], + "distance": round(manifold_distance(child_point, target_point), 9), + }) + if child["start_leaf"] <= leaf_index <= child["end_leaf"]: + next_hash = child_hash + path[-1]["manifold_distance_pruning"] = sorted(pruned, key=lambda item: item["distance"]) + if next_hash is None: + raise ValueError("corrupt tree: no child covers requested leaf") + frontier = next_hash + + path_valid = self._verify_path_rows(path, payload) + return { + "ok": True, "op": "fractal_proof", + "root_hash": root_hash, "leaf_index": leaf_index, + "fold_address": gray_code(leaf_index), + "inverse_fold_address": inverse_gray_code(gray_code(leaf_index)), + "golden_spiral": golden_spiral_point(gray_code(leaf_index), 0), + "traversal_cost": len(path), + "expected_complexity": f"O(log_{meta['branching_factor']}(n))", + "path_hash_verified": path_valid, + "path": path, + "payload_b64": base64.b64encode(payload).decode("ascii"), + } + + def _verify_path_rows(self, path: list[dict[str, Any]], payload: bytes) -> bool: + if not path: + return False + leaf = path[-1] + if leaf["kind"] != "leaf": + return False + expected_leaf = make_leaf(payload, leaf["ordinal"]).node_hash + if expected_leaf != leaf["node_hash"]: + return False + child_hash = expected_leaf + for row in reversed(path[:-1]): + if child_hash not in row["children"]: + return False + child_hash = row["node_hash"] + return child_hash == path[0]["node_hash"] + + def verify(self, root_hash: str) -> dict[str, Any]: + meta = self.manifest(root_hash) + if meta is None: + raise KeyError(f"unknown root {root_hash}") + errors = [] + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute("SELECT * FROM ene.fractal_nodes WHERE root_hash = %s", (root_hash,)) + rows = cur.fetchall() + by_hash = {r["node_hash"]: r for r in rows} + for row in rows: + children = json.loads(row["children"]) + if row["kind"] == "leaf": + payload = base64.b64decode(row["payload_b64"] or "") + expected = make_leaf(payload, row["ordinal"]).node_hash + if expected != row["node_hash"]: + errors.append({"node": row["node_hash"], "error": "leaf_hash_mismatch", "expected": expected}) + continue + child_nodes = [] + for child_hash in children: + child = by_hash.get(child_hash) + if child is None: + errors.append({"node": row["node_hash"], "error": "missing_child", "child": child_hash}) + continue + child_nodes.append(FractalNode( + node_hash=child["node_hash"], kind=child["kind"], + level=child["level"], ordinal=child["ordinal"], + fold_address=child["fold_address"], + start_leaf=child["start_leaf"], end_leaf=child["end_leaf"], + size_bytes=child["size_bytes"], + children=json.loads(child["children"]), + payload_b64=child["payload_b64"], + )) + if len(child_nodes) == len(children): + expected = make_parent(child_nodes, row["level"], row["ordinal"]).node_hash + if expected != row["node_hash"]: + errors.append({"node": row["node_hash"], "error": "parent_hash_mismatch", "expected": expected}) + return { + "ok": not errors, "op": "fractal_verify", + "root_hash": root_hash, "checked_nodes": len(rows), + "errors": errors, "damage_detected": bool(errors), + } + + def neighbors(self, root_hash: str, leaf_index: int) -> dict[str, Any]: + meta = self.manifest(root_hash) + if meta is None: + raise KeyError(f"unknown root {root_hash}") + candidates = sorted(set(idx for idx in (leaf_index - 1, leaf_index, leaf_index + 1) if 0 <= idx < meta["leaves_count"])) + return { + "ok": True, "op": "fractal_neighbors", + "root_hash": root_hash, "leaf_index": leaf_index, + "fold_address": gray_code(leaf_index), + "neighbors": [ + { + "leaf_index": idx, "fold_address": gray_code(idx), + "golden_spiral": golden_spiral_point(gray_code(idx), 0), + "fold_distance": bin(gray_code(idx) ^ gray_code(leaf_index)).count("1"), + } + for idx in candidates + ], + } + + def graph_entity(self, root_hash: str, graph_node_id: str = "", name: str = "") -> dict[str, Any]: + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + if graph_node_id: + cur.execute( + "SELECT * FROM ene.fractal_graph_entities WHERE root_hash = %s AND graph_node_id = %s", + (root_hash, graph_node_id), + ) + else: + cur.execute( + "SELECT * FROM ene.fractal_graph_entities WHERE root_hash = %s AND lower(name) = lower(%s)", + (root_hash, name), + ) + row = cur.fetchone() + if row is None: + return {"ok": False, "op": "fractal_graph_entity", "error": "graph entity not found"} + proof = self.navigate(root_hash, row["leaf_index"]) + return { + "ok": True, "op": "fractal_graph_entity", + "root_hash": root_hash, + "entity": { + "graph_node_id": row["graph_node_id"], + "leaf_index": row["leaf_index"], + "name": row["name"], + "family": row["family"], + "domain": row["domain"], + "neighbors": json.loads(row["neighbors"]), + }, + "retrieval": proof, + } + + def graph_neighbors(self, root_hash: str, graph_node_id: str) -> dict[str, Any]: + entity = self.graph_entity(root_hash, graph_node_id=graph_node_id) + if not entity.get("ok"): + return entity + neighbor_ids = entity["entity"]["neighbors"] + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + rows = [] + for nid in neighbor_ids: + cur.execute( + "SELECT * FROM ene.fractal_graph_entities WHERE root_hash = %s AND graph_node_id = %s", + (root_hash, nid), + ) + row = cur.fetchone() + if row is not None: + rows.append(row) + return { + "ok": True, "op": "fractal_graph_neighbors", + "root_hash": root_hash, "graph_node_id": graph_node_id, + "neighbors": [ + { + "graph_node_id": r["graph_node_id"], + "leaf_index": r["leaf_index"], + "name": r["name"], + "family": r["family"], + "domain": r["domain"], + "fold_address": gray_code(r["leaf_index"]), + "golden_spiral": golden_spiral_point(gray_code(r["leaf_index"]), 0), + } + for r in rows + ], + } + + def navigate(self, root_hash: str, leaf_index: int) -> dict[str, Any]: + meta = self.manifest(root_hash) + if meta is None: + raise KeyError(f"unknown root {root_hash}") + if leaf_index < 0 or leaf_index >= meta["leaves_count"]: + raise IndexError("leaf_index outside manifold") + + target_point = golden_spiral_point(gray_code(leaf_index), 0) + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + frontier = root_hash + path = [] + while True: + row = self._node(cur, root_hash, frontier) + children = json.loads(row["children"]) + entry = { + "node_hash": row["node_hash"], "kind": row["kind"], + "level": row["level"], "ordinal": row["ordinal"], + "start_leaf": row["start_leaf"], "end_leaf": row["end_leaf"], + "fold_address": row["fold_address"], + "children": children, + "golden_spiral": golden_spiral_point(row["fold_address"], row["level"]), + } + path.append(entry) + + if row["kind"] == "leaf": + payload = base64.b64decode(row["payload_b64"] or "") + break + + ranked = [] + for child_hash in children: + child = self._node(cur, root_hash, child_hash) + point = golden_spiral_point(child["fold_address"], child["level"]) + ranked.append({ + "node_hash": child_hash, + "distance": manifold_distance(point, target_point), + "covers_target": child["start_leaf"] <= leaf_index <= child["end_leaf"], + }) + ranked.sort(key=lambda item: (not item["covers_target"], item["distance"])) + path[-1]["pruned_candidates"] = [ + {"node_hash": item["node_hash"], "distance": round(item["distance"], 9), "covers_target": item["covers_target"]} + for item in ranked + ] + frontier = ranked[0]["node_hash"] + + return { + "ok": True, "op": "fractal_navigate", + "root_hash": root_hash, "leaf_index": leaf_index, + "target": {"fold_address": gray_code(leaf_index), "golden_spiral": target_point}, + "retrieval_complexity": f"O(log_{meta['branching_factor']}(n))", + "navigation": "golden_spiral_manifold_distance_pruning", + "path_hash_verified": self._verify_path_rows(path, payload), + "path": path, + "payload_b64": base64.b64encode(payload).decode("ascii"), + } + + def handle_request(self, request: dict[str, Any]) -> dict[str, Any]: + op = str(request.get("op", "manifest")) + if op in {"put", "encode"}: + if "data_b64" in request: + data = base64.b64decode(str(request["data_b64"])) + else: + data = str(request.get("text", "")).encode("utf-8") + return self.put( + data=data, + name=str(request.get("name", "unnamed")), + chunk_size=int(request.get("chunk_size", 4096)), + branching_factor=int(request.get("branching_factor", 4)), + ) + if op in {"put_graphml", "graphml"}: + if "data_b64" in request: + data = base64.b64decode(str(request["data_b64"])) + else: + data = str(request.get("text", "")).encode("utf-8") + return self.put_graphml( + graphml=data, + name=str(request.get("name", "graphml")), + branching_factor=int(request.get("branching_factor", 4)), + ) + root_hash = str(request.get("root_hash", "")) + if not root_hash: + raise ValueError("root_hash is required for this operation") + if op == "manifest": + meta = self.manifest(root_hash) + return {"ok": meta is not None, "op": "fractal_manifest", "manifest": meta} + if op == "proof": + return self.proof(root_hash, int(request.get("leaf_index", 0))) + if op in {"navigate", "get"}: + return self.navigate(root_hash, int(request.get("leaf_index", 0))) + if op == "verify": + return self.verify(root_hash) + if op == "neighbors": + return self.neighbors(root_hash, int(request.get("leaf_index", 0))) + if op in {"graph_entity", "concept"}: + return self.graph_entity( + root_hash, + graph_node_id=str(request.get("graph_node_id", "")), + name=str(request.get("name", "")), + ) + if op in {"graph_neighbors", "concept_neighbors"}: + return self.graph_neighbors(root_hash, str(request.get("graph_node_id", ""))) + raise ValueError(f"unsupported fractal op {op!r}") + + +def main() -> int: + import argparse + parser = argparse.ArgumentParser(description="ENE RDS fractal fold codec") + parser.add_argument("--dsn", help="PostgreSQL DSN") + parser.add_argument("--op", default="put") + parser.add_argument("--name", default="cli") + parser.add_argument("--text", default="") + parser.add_argument("--file", type=argparse.FileType("rb")) + parser.add_argument("--graph-node-id") + parser.add_argument("--root-hash") + parser.add_argument("--leaf-index", type=int, default=0) + parser.add_argument("--chunk-size", type=int, default=4096) + parser.add_argument("--branching-factor", type=int, default=4) + args = parser.parse_args() + + layer = ENERDSFractalFold(args.dsn) + if args.op in {"put", "encode"}: + data = args.file.read() if args.file else args.text.encode("utf-8") + result = layer.put(data, args.name, args.chunk_size, args.branching_factor) + elif args.op in {"put_graphml", "graphml"}: + data = args.file.read() if args.file else args.text.encode("utf-8") + result = layer.put_graphml(data, args.name, args.branching_factor) + elif args.op in {"graph_entity", "concept", "graph_neighbors"}: + result = layer.handle_request({ + "op": args.op, "root_hash": args.root_hash, + "leaf_index": args.leaf_index, + "graph_node_id": args.graph_node_id or "", + "name": args.name, + }) + else: + result = layer.handle_request({ + "op": args.op, "root_hash": args.root_hash, + "leaf_index": args.leaf_index, + }) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/4-Infrastructure/infra/ene_rds_wiki_layer.py b/4-Infrastructure/infra/ene_rds_wiki_layer.py new file mode 100644 index 00000000..076abebc --- /dev/null +++ b/4-Infrastructure/infra/ene_rds_wiki_layer.py @@ -0,0 +1,438 @@ +"""RDS-backed ENEWikiLayer — drop-in replacement for SQLite ENEWikiLayer. + +API-compatible with ene_wiki_layer.ENEWikiLayer: same dataclasses, same +handle_request() protocol, but backed by PostgreSQL via psycopg2. + +Constructor: + ENERDSWikiLayer(dsn="postgresql://user:pass@host:5432/dbname") + +The DSN defaults to the RDS_HOST / RDS_PORT / RDS_USER / RDS_PASSWORD / RDS_DB +environment variables (or postgres as dbname, research_stack as fallback). +""" + +from __future__ import annotations + +import hashlib +import json +import re +import time +from dataclasses import asdict, dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +import psycopg2 +import psycopg2.extras +import psycopg2.pool + +from infra.ene_wiki_layer import ( + WikiPage, + WikiRevision, + normalize_title, + title_slug, + extract_links, + extract_categories, + write_receipt, + canonical_json, + sha256_text, + iso_utc, + concept_vector_for_wiki, + genome_from_vector, + make_archive_record, + make_jsonl_event, +) + + +DEFAULT_DSN = None + + +def _default_dsn() -> str: + host = os.environ.get("RDS_HOST", "database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com") + port = os.environ.get("RDS_PORT", "5432") + user = os.environ.get("RDS_USER", "postgres") + password = os.environ.get("RDS_PASSWORD") or os.environ.get("RDS_IAM_TOKEN", "") + dbname = os.environ.get("RDS_DB", "postgres") + return f"host={host} port={port} dbname={dbname} user={user} password={password} sslmode=require" + + +import os + + +class ENERDSWikiLayer: + def __init__(self, dsn: str | None = None): + self.dsn = dsn or _default_dsn() + self._pool: psycopg2.pool.ThreadedConnectionPool | None = None + self._init_tables() + + def _get_conn(self): + return psycopg2.connect(self.dsn) + + def _init_tables(self) -> None: + with self._get_conn() as conn: + with conn.cursor() as cur: + cur.execute("CREATE SCHEMA IF NOT EXISTS ene") + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.wiki_pages ( + slug TEXT PRIMARY KEY, + title TEXT NOT NULL, + latest_revision INTEGER NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + receipt TEXT NOT NULL + ) + """) + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.wiki_revisions ( + slug TEXT NOT NULL, + revision INTEGER NOT NULL, + title TEXT NOT NULL, + text TEXT NOT NULL, + author TEXT NOT NULL DEFAULT 'ene', + summary TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + receipt TEXT NOT NULL, + archive_id TEXT, + content_hash TEXT, + archive_record JSONB DEFAULT '{}', + jsonl_event JSONB DEFAULT '{}', + PRIMARY KEY (slug, revision) + ) + """) + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.wiki_links ( + slug TEXT NOT NULL, + target_slug TEXT NOT NULL, + target_title TEXT NOT NULL, + PRIMARY KEY (slug, target_slug) + ) + """) + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.wiki_categories ( + slug TEXT NOT NULL, + category TEXT NOT NULL, + PRIMARY KEY (slug, category) + ) + """) + cur.execute(""" + CREATE TABLE IF NOT EXISTS ene.packages ( + pkg TEXT PRIMARY KEY, + version TEXT NOT NULL, + tier TEXT, + domain TEXT, + archetype TEXT, + description TEXT, + tags JSONB DEFAULT '[]', + source TEXT, + sha256 TEXT, + indexed_utc TEXT, + concept_anchor JSONB DEFAULT '{}', + concept_vector JSONB DEFAULT '[]', + idea_weights JSONB DEFAULT '{}', + analog_map JSONB DEFAULT '{}' + ) + """) + self._ensure_columns(conn, "ene.wiki_revisions", cur) + conn.commit() + + def _ensure_columns(self, conn, table: str, cur) -> None: + cur.execute(""" + SELECT column_name FROM information_schema.columns + WHERE table_schema = 'ene' AND table_name = %s + """, (table.split('.')[1],)) + existing = {r[0] for r in cur.fetchall()} + additions = { + "archive_id": "TEXT", + "content_hash": "TEXT", + "archive_record": "JSONB DEFAULT '{}'", + "jsonl_event": "JSONB DEFAULT '{}'", + } + for name, decl in additions.items(): + if name not in existing: + cur.execute(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {name} {decl}") + + def _upsert_package(self, conn, cur, event: dict[str, Any]) -> None: + data = event["data"] + cur.execute(""" + INSERT INTO ene.packages ( + pkg, version, tier, domain, archetype, description, + tags, source, sha256, indexed_utc, + concept_anchor, concept_vector, idea_weights, analog_map + ) VALUES ( + %(pkg)s, %(version)s, %(tier)s, %(domain)s, %(archetype)s, %(description)s, + %(tags)s, %(source)s, %(sha256)s, %(indexed_utc)s, + %(concept_anchor)s, %(concept_vector)s, %(idea_weights)s, %(analog_map)s + ) ON CONFLICT (pkg) DO UPDATE SET + version = EXCLUDED.version, + tier = EXCLUDED.tier, + domain = EXCLUDED.domain, + archetype = EXCLUDED.archetype, + description = EXCLUDED.description, + tags = EXCLUDED.tags, + source = EXCLUDED.source, + sha256 = EXCLUDED.sha256, + indexed_utc = EXCLUDED.indexed_utc, + concept_anchor = EXCLUDED.concept_anchor, + concept_vector = EXCLUDED.concept_vector, + idea_weights = EXCLUDED.idea_weights, + analog_map = EXCLUDED.analog_map + """, { + "pkg": data["pkg"], + "version": data["version"], + "tier": data["tier"], + "domain": data["domain"], + "archetype": data["archetype"], + "description": data["description"], + "tags": json.dumps(data["tags"], sort_keys=True), + "source": data["source"], + "sha256": data["sha256"], + "indexed_utc": data["indexed_utc"], + "concept_anchor": json.dumps(data["concept_anchor"], sort_keys=True), + "concept_vector": json.dumps(data["concept_vector"], sort_keys=True), + "idea_weights": json.dumps(data["idea_weights"], sort_keys=True), + "analog_map": json.dumps(data["analog_map"], sort_keys=True), + }) + + def admit_write(self, title: str, text: str) -> tuple[bool, str]: + try: + normalize_title(title) + except ValueError as exc: + return False, str(exc) + if len(text.encode("utf-8")) > 256_000: + return False, "wiki text too large" + lowered = text.lower() + if " WikiRevision: + admitted, reason = self.admit_write(title, text) + if not admitted: + raise ValueError(reason) + + normalized = normalize_title(title) + slug = title_slug(normalized) + now_ts = int(time.time()) + now_dt = datetime.fromtimestamp(now_ts, tz=timezone.utc) + links = extract_links(text) + categories = extract_categories(text) + + with self._get_conn() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT latest_revision FROM ene.wiki_pages WHERE slug = %s", + (slug,), + ) + row = cur.fetchone() + revision = int(row[0]) + 1 if row else 1 + receipt = write_receipt(slug, revision, text, author, now_ts) + archive_record = make_archive_record( + normalized, slug, revision, text, author, summary, now_ts, links, categories + ) + concept_vector = concept_vector_for_wiki(normalized, text, links, categories) + jsonl_event = make_jsonl_event(archive_record, concept_vector, receipt) + + cur.execute(""" + INSERT INTO ene.wiki_revisions + (slug, revision, title, text, author, summary, created_at, receipt, + archive_id, content_hash, archive_record, jsonl_event) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, ( + slug, revision, normalized, text, author, summary, now_dt, receipt, + archive_record["archive_id"], archive_record["content_hash"], + json.dumps(archive_record, sort_keys=True), + json.dumps(jsonl_event, sort_keys=True), + )) + + cur.execute(""" + INSERT INTO ene.wiki_pages (slug, title, latest_revision, updated_at, receipt) + VALUES (%s, %s, %s, %s, %s) + ON CONFLICT (slug) DO UPDATE SET + title = EXCLUDED.title, + latest_revision = EXCLUDED.latest_revision, + updated_at = EXCLUDED.updated_at, + receipt = EXCLUDED.receipt + """, (slug, normalized, revision, now_dt, receipt)) + + cur.execute("DELETE FROM ene.wiki_links WHERE slug = %s", (slug,)) + cur.execute("DELETE FROM ene.wiki_categories WHERE slug = %s", (slug,)) + + if links: + psycopg2.extras.execute_values( + cur, + "INSERT INTO ene.wiki_links (slug, target_slug, target_title) VALUES %s ON CONFLICT DO NOTHING", + [(slug, title_slug(link), link) for link in links], + ) + if categories: + psycopg2.extras.execute_values( + cur, + "INSERT INTO ene.wiki_categories (slug, category) VALUES %s ON CONFLICT DO NOTHING", + [(slug, cat) for cat in categories], + ) + + self._upsert_package(conn, cur, jsonl_event) + conn.commit() + + return WikiRevision( + normalized, slug, revision, text, author, summary, now_ts, receipt, + links, categories, archive_record, jsonl_event, + ) + + def get_page(self, title: str, revision: int | None = None) -> WikiRevision | None: + slug = title_slug(title) + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + if revision is None: + cur.execute( + "SELECT latest_revision FROM ene.wiki_pages WHERE slug = %s", + (slug,), + ) + page = cur.fetchone() + if not page: + return None + revision = int(page["latest_revision"]) + else: + revision = int(revision) + + cur.execute(""" + SELECT title, slug, revision, text, author, summary, created_at, receipt, + archive_record, jsonl_event + FROM ene.wiki_revisions + WHERE slug = %s AND revision = %s + """, (slug, revision)) + row = cur.fetchone() + if not row: + return None + + cur.execute( + "SELECT target_title FROM ene.wiki_links WHERE slug = %s ORDER BY lower(target_title)", + (slug,), + ) + links = [r["target_title"] for r in cur.fetchall()] + + cur.execute( + "SELECT category FROM ene.wiki_categories WHERE slug = %s ORDER BY lower(category)", + (slug,), + ) + categories = [r["category"] for r in cur.fetchall()] + + archive_record = row["archive_record"] if isinstance(row["archive_record"], dict) else json.loads(row["archive_record"] or "{}") + jsonl_event = row["jsonl_event"] if isinstance(row["jsonl_event"], dict) else json.loads(row["jsonl_event"] or "{}") + created_ts = int(row["created_at"].timestamp()) if hasattr(row["created_at"], "timestamp") else 0 + + return WikiRevision( + row["title"], row["slug"], int(row["revision"]), row["text"], + row["author"], row["summary"], created_ts, row["receipt"], + links, categories, archive_record, jsonl_event, + ) + + def search(self, query: str, limit: int = 20) -> list[WikiPage]: + term = f"%{query.strip()}%" + limit = max(1, min(limit, 100)) + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute(""" + SELECT p.title, p.slug, p.latest_revision, p.updated_at, p.receipt + FROM ene.wiki_pages p + JOIN ene.wiki_revisions r + ON r.slug = p.slug AND r.revision = p.latest_revision + WHERE p.title ILIKE %s OR r.text ILIKE %s + ORDER BY p.updated_at DESC + LIMIT %s + """, (term, term, limit)) + rows = cur.fetchall() + return [ + WikiPage( + r["title"], r["slug"], int(r["latest_revision"]), + int(r["updated_at"].timestamp()) if hasattr(r["updated_at"], "timestamp") else 0, + r["receipt"], + ) + for r in rows + ] + + def backlinks(self, title: str) -> list[WikiPage]: + target = title_slug(title) + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute(""" + SELECT p.title, p.slug, p.latest_revision, p.updated_at, p.receipt + FROM ene.wiki_links l + JOIN ene.wiki_pages p ON p.slug = l.slug + WHERE l.target_slug = %s + ORDER BY lower(p.title) + """, (target,)) + rows = cur.fetchall() + return [ + WikiPage( + r["title"], r["slug"], int(r["latest_revision"]), + int(r["updated_at"].timestamp()) if hasattr(r["updated_at"], "timestamp") else 0, + r["receipt"], + ) + for r in rows + ] + + def recent_changes(self, limit: int = 20) -> list[WikiPage]: + limit = max(1, min(limit, 100)) + with self._get_conn() as conn: + with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: + cur.execute(""" + SELECT title, slug, latest_revision, updated_at, receipt + FROM ene.wiki_pages + ORDER BY updated_at DESC + LIMIT %s + """, (limit,)) + rows = cur.fetchall() + return [ + WikiPage( + r["title"], r["slug"], int(r["latest_revision"]), + int(r["updated_at"].timestamp()) if hasattr(r["updated_at"], "timestamp") else 0, + r["receipt"], + ) + for r in rows + ] + + def handle_request(self, request: dict[str, Any]) -> dict[str, Any]: + op = str(request.get("op", "recent")) + if op in {"put", "edit"}: + revision = self.put_page( + title=str(request.get("title", "")), + text=str(request.get("text", "")), + author=str(request.get("author", "ene")), + summary=str(request.get("summary", "")), + ) + return {"ok": True, "op": op, "revision": asdict(revision)} + if op == "get": + page = self.get_page(str(request.get("title", "")), request.get("revision")) + return {"ok": page is not None, "op": op, "page": asdict(page) if page else None} + if op == "search": + pages = self.search(str(request.get("query", "")), int(request.get("limit", 20))) + return {"ok": True, "op": op, "pages": [asdict(page) for page in pages]} + if op == "backlinks": + pages = self.backlinks(str(request.get("title", ""))) + return {"ok": True, "op": op, "pages": [asdict(page) for page in pages]} + if op == "recent": + pages = self.recent_changes(int(request.get("limit", 20))) + return {"ok": True, "op": op, "pages": [asdict(page) for page in pages]} + raise ValueError(f"unsupported wiki op {op!r}") + + +def main() -> int: + import argparse + parser = argparse.ArgumentParser(description="ENE RDS wiki layer") + parser.add_argument("--dsn", help="PostgreSQL DSN") + parser.add_argument("--op", choices=["put", "get", "search", "backlinks", "recent"], default="recent") + parser.add_argument("--title", default="") + parser.add_argument("--text", default="") + parser.add_argument("--query", default="") + parser.add_argument("--author", default="ene") + args = parser.parse_args() + + wiki = ENERDSWikiLayer(args.dsn) + request: dict[str, Any] = { + "op": args.op, "title": args.title, "text": args.text, + "query": args.query, "author": args.author, + } + print(json.dumps(wiki.handle_request(request), sort_keys=True, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/4-Infrastructure/infra/recover_credential_server.sh b/4-Infrastructure/infra/recover_credential_server.sh new file mode 100644 index 00000000..650e4c94 --- /dev/null +++ b/4-Infrastructure/infra/recover_credential_server.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Deploy credential server to microVM (RackNerd) +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +VM_IP="172.245.19.182" +VM_USER="root" +VM_PASS="${1:-}" +SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR" + +if [ -z "$VM_PASS" ]; then + VM_PASS="$(cat "$REPO_ROOT/API KEYS/racknerd_510bd9c_root.txt" 2>/dev/null | grep root_password | cut -d: -f2 | tr -d ' ')" +fi + +if [ -z "$VM_PASS" ]; then + echo "ERROR: root password required. Pass as argument or ensure API KEYS/racknerd_510bd9c_root.txt exists." + exit 1 +fi + +SSH="sshpass -p "$VM_PASS" ssh $SSH_OPTS ${VM_USER}@${VM_IP}" +SCP="sshpass -p "$VM_PASS" scp $SSH_OPTS" + +echo "=== Deploying credential server to ${VM_IP} ===" + +# Create directories +$SSH "mkdir -p /opt/rs-surface /etc/rs-surface" + +# Copy Python modules +echo "Uploading credential_provider.py..." +$SCP "$REPO_ROOT/4-Infrastructure/infra/credential_provider.py" ${VM_USER}@${VM_IP}:/opt/rs-surface/credential_provider.py + +echo "Uploading credential_server.py..." +$SCP "$REPO_ROOT/4-Infrastructure/infra/credential_server.py" ${VM_USER}@${VM_IP}:/opt/rs-surface/credential_server.py + +# Copy credentials config +CRED_JSON="/tmp/rs-credentials.json" +python3 -c " +import json, os +creds = {} +for var, key in [('DEEPSEEK_API_KEY', 'deepseek'), ('QUANDELA_API_KEY', 'quandela'), + ('WOLFRAM_ALPHA_APPID', 'wolfram_alpha'), ('LINEAR_API_KEY', 'linear'), + ('AWS_BEARER_TOKEN_BEDROCK', 'bedrock')]: + val = os.environ.get(var, '') + if val: + creds[key] = val +if not creds: + print('WARNING: no credential env vars set; writing empty config') +with open('$CRED_JSON', 'w') as f: + json.dump(creds, f, indent=2) +print(f'Wrote {len(creds)} provider keys from environment') +" +echo "Uploading credentials.json..." +$SCP "$CRED_JSON" ${VM_USER}@${VM_IP}:/etc/rs-surface/credentials.json +rm -f "$CRED_JSON" + +# Create systemd service +echo "Setting up systemd service..." +$SSH 'cat > /etc/systemd/system/rs-credential-server.service << '"'"'SERVICEEOF'"'"' +[Unit] +Description=Research Stack Credential Server +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/rs-surface +ExecStart=/usr/bin/python3 /opt/rs-surface/credential_server.py --port 8444 --bind 0.0.0.0 +Restart=always +RestartSec=5 +Environment=RS_CREDENTIAL_CONFIG=/etc/rs-surface/credentials.json + +[Install] +WantedBy=multi-user.target +SERVICEEOF' + +# Stop old service if exists, enable new one +$SSH "systemctl daemon-reload && systemctl enable rs-credential-server && systemctl restart rs-credential-server" + +# Verify +echo "=== Verifying ===" +sleep 2 +$SSH "systemctl status rs-credential-server --no-pager --lines=5" +echo "" +echo "=== Testing HTTP ===" +curl -sf --connect-timeout 5 http://${VM_IP}:8444/ && echo "" || echo "(curl root)" +curl -sf --connect-timeout 5 http://${VM_IP}:8444/health && echo "" || echo "(curl health)" +curl -sf --connect-timeout 5 http://${VM_IP}:8444/status && echo "" || echo "(curl status)" +curl -sf --connect-timeout 5 http://${VM_IP}:8444/openapi.json | python3 -m json.tool > /dev/null 2>&1 && echo "openapi.json: valid" || echo "openapi.json: FAIL" + +echo "=== Deploy complete ===" +echo "API: http://${VM_IP}:8444/" +echo "Docs: http://${VM_IP}:8444/openapi.json" +echo "Credentials: http://${VM_IP}:8444/credentials" diff --git a/4-Infrastructure/rds_probe/Cargo.lock b/4-Infrastructure/rds_probe/Cargo.lock new file mode 100644 index 00000000..23c8d639 --- /dev/null +++ b/4-Infrastructure/rds_probe/Cargo.lock @@ -0,0 +1,2452 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.5", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rds_probe" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "dotenv", + "hex", + "serde", + "serde_json", + "sha2", + "sqlx", + "thiserror", + "tokio", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/4-Infrastructure/rds_probe/Cargo.toml b/4-Infrastructure/rds_probe/Cargo.toml new file mode 100644 index 00000000..41d6e637 --- /dev/null +++ b/4-Infrastructure/rds_probe/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rds_probe" +version = "0.1.0" +edition = "2021" +description = "RDS ENE database inspection tool" +license = "MIT" +authors = ["Research Stack"] + +[dependencies] +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "json", "uuid"] } +tokio = { version = "1", features = ["full"] } +dotenv = "0.15" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +clap = { version = "4", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } +thiserror = "1" +sha2 = "0.10" +hex = "0.4" + +[profile.release] +opt-level = 3 +lto = true diff --git a/4-Infrastructure/rds_probe/src/commands.rs b/4-Infrastructure/rds_probe/src/commands.rs new file mode 100644 index 00000000..90470ceb --- /dev/null +++ b/4-Infrastructure/rds_probe/src/commands.rs @@ -0,0 +1,183 @@ +use crate::db::get_pool; +use crate::models::*; +use clap::Subcommand; +use serde_json::json; + +#[derive(Subcommand, Debug)] +pub enum Command { + Tables, + Schema { table: String }, + Count, + Sample { domain: String }, + Export, +} + +pub async fn run_command(command: Command) -> Result> { + let pool = get_pool().await?; + + match command { + Command::Tables => tables_cmd(&pool).await, + Command::Schema { table } => schema_cmd(&pool, &table).await, + Command::Count => count_cmd(&pool).await, + Command::Sample { domain } => sample_cmd(&pool, &domain).await, + Command::Export => export_cmd(&pool).await, + } +} + +async fn tables_cmd(pool: &sqlx::PgPool) -> Result> { + let rows = sqlx::query_as::<_, (String, String)>( + "SELECT table_name, table_schema + FROM information_schema.tables + WHERE table_schema NOT IN ('pg_catalog', 'information_schema') + ORDER BY table_schema, table_name" + ) + .fetch_all(pool) + .await?; + + let tables: Vec = rows.into_iter() + .map(|(table_name, table_schema)| TableInfo { table_name, table_schema }) + .collect(); + + let output = json!({ + "success": true, + "command": "tables", + "count": tables.len(), + "tables": tables + }); + + Ok(serde_json::to_string_pretty(&output)?) +} + +async fn schema_cmd(pool: &sqlx::PgPool, table: &str) -> Result> { + let rows = if let Some(pos) = table.find('.') { + let schema = &table[..pos]; + let name = &table[pos + 1..]; + sqlx::query_as::<_, (String, String, String)>( + "SELECT column_name, data_type, is_nullable + FROM information_schema.columns + WHERE table_schema = $1 AND table_name = $2 + ORDER BY ordinal_position" + ) + .bind(schema) + .bind(name) + .fetch_all(pool) + .await? + } else { + sqlx::query_as::<_, (String, String, String)>( + "SELECT column_name, data_type, is_nullable + FROM information_schema.columns + WHERE table_name = $1 AND table_schema NOT IN ('pg_catalog', 'information_schema') + ORDER BY ordinal_position" + ) + .bind(table) + .fetch_all(pool) + .await? + }; + + let columns: Vec = rows.into_iter() + .map(|(column_name, data_type, is_nullable)| ColumnInfo { + column_name, + data_type, + is_nullable, + }) + .collect(); + + let output = json!({ + "success": true, + "command": "schema", + "table": table, + "columns": columns + }); + + Ok(serde_json::to_string_pretty(&output)?) +} + +async fn count_cmd(pool: &sqlx::PgPool) -> Result> { + let rows = sqlx::query_as::<_, (String, String, i64)>( + "SELECT domain, archetype, COUNT(*) as count + FROM ene.packages + GROUP BY domain, archetype + ORDER BY domain, archetype" + ) + .fetch_all(pool) + .await?; + + let mut by_domain = serde_json::Map::new(); + for (domain, archetype, count) in rows { + let entry = by_domain.entry(domain).or_insert_with(|| json!({})); + entry.as_object_mut().unwrap().insert(archetype, json!(count)); + } + + let total: i64 = by_domain.values() + .flat_map(|v| v.as_object().unwrap().values()) + .map(|v| v.as_i64().unwrap_or(0)) + .sum(); + + let output = json!({ + "success": true, + "command": "count", + "total_records": total, + "by_domain": by_domain + }); + + Ok(serde_json::to_string_pretty(&output)?) +} + +async fn sample_cmd(pool: &sqlx::PgPool, domain: &str) -> Result> { + let rows = sqlx::query_as::<_, ( + String, Option, Option, Option, Option, + Option, Option, Option, Option + )>( + "SELECT pkg, version, domain, tier, archetype, description, tags, source, indexed_utc + FROM ene.packages + WHERE domain = $1 + LIMIT 5" + ) + .bind(domain.to_uppercase()) + .fetch_all(pool) + .await?; + + let records: Vec = rows.into_iter() + .map(|(pkg, version, domain, tier, archetype, description, tags, source, indexed_utc)| { + PackageRecord { pkg, version, domain, tier, archetype, description, tags, source, indexed_utc } + }) + .collect(); + + let output = json!({ + "success": true, + "command": "sample", + "domain": domain.to_uppercase(), + "count": records.len(), + "records": records + }); + + Ok(serde_json::to_string_pretty(&output)?) +} + +async fn export_cmd(pool: &sqlx::PgPool) -> Result> { + let rows = sqlx::query_as::<_, ( + String, Option, Option, Option, Option, + Option, Option, Option, Option + )>( + "SELECT pkg, version, domain, tier, archetype, description, tags, source, indexed_utc + FROM ene.packages + ORDER BY domain, indexed_utc DESC" + ) + .fetch_all(pool) + .await?; + + let records: Vec = rows.into_iter() + .map(|(pkg, version, domain, tier, archetype, description, tags, source, indexed_utc)| { + PackageRecord { pkg, version, domain, tier, archetype, description, tags, source, indexed_utc } + }) + .collect(); + + let output = json!({ + "success": true, + "command": "export", + "total_records": records.len(), + "records": records + }); + + Ok(serde_json::to_string_pretty(&output)?) +} diff --git a/4-Infrastructure/rds_probe/src/db.rs b/4-Infrastructure/rds_probe/src/db.rs new file mode 100644 index 00000000..55eeef42 --- /dev/null +++ b/4-Infrastructure/rds_probe/src/db.rs @@ -0,0 +1,102 @@ +use sqlx::{postgres::PgPoolOptions, PgPool}; +use std::env; +use std::process::Command; + +pub struct DbConfig { + pub host: String, + pub port: u16, + pub user: String, + pub password: String, + pub dbname: String, +} + +fn get_iam_token(host: &str, user: &str, region: &str) -> Option { + let output = Command::new("aws") + .args(&[ + "rds", "generate-db-auth-token", + "--hostname", host, + "--port", "5432", + "--username", user, + "--region", region, + ]) + .output() + .ok()?; + + if output.status.success() { + let token = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !token.is_empty() { + return Some(token); + } + } + None +} + +impl DbConfig { + pub fn from_env() -> Self { + let host = env::var("RDS_HOST") + .unwrap_or("database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com".to_string()); + let user = env::var("RDS_USER").unwrap_or("postgres".to_string()); + let region = env::var("AWS_DEFAULT_REGION").unwrap_or("us-east-1".to_string()); + + let password = if let Ok(pwd) = env::var("RDS_PASSWORD") { + if !pwd.is_empty() { + pwd + } else { + get_iam_token(&host, &user, ®ion).unwrap_or_default() + } + } else { + get_iam_token(&host, &user, ®ion).unwrap_or_default() + }; + + Self { + host, + port: env::var("RDS_PORT") + .unwrap_or("5432".to_string()) + .parse() + .unwrap_or(5432), + user, + password, + dbname: env::var("RDS_DB").unwrap_or("postgres".to_string()), + } + } + + pub fn dsn(&self) -> String { + format!( + "postgres://{}:{}@{}:{}/{}?sslmode=require", + self.user, + urlencoding(&self.password), + self.host, + self.port, + self.dbname + ) + } +} + +fn urlencoding(s: &str) -> String { + let mut encoded = String::new(); + for c in s.chars() { + match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => { + encoded.push(c); + } + _ => { + encoded.push('%'); + encoded.push_str(&format!("{:02X}", c as u8)); + } + } + } + encoded +} + +pub async fn create_pool(config: &DbConfig) -> Result { + PgPoolOptions::new() + .max_connections(1) + .acquire_timeout(std::time::Duration::from_secs(30)) + .connect(&config.dsn()).await +} + +pub async fn get_pool() -> Result> { + let config = DbConfig::from_env(); + let pool = create_pool(&config).await?; + Ok(pool) +} diff --git a/4-Infrastructure/rds_probe/src/main.rs b/4-Infrastructure/rds_probe/src/main.rs new file mode 100644 index 00000000..df596795 --- /dev/null +++ b/4-Infrastructure/rds_probe/src/main.rs @@ -0,0 +1,29 @@ +mod commands; +mod db; +mod models; + +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(name = "rds_probe")] +#[command(about = "RDS ENE database inspection tool", long_about = None)] +struct Cli { + #[command(subcommand)] + command: commands::Command, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + dotenv::dotenv().ok(); + + match commands::run_command(Cli::parse().command).await { + Ok(output) => { + println!("{}", output); + Ok(()) + } + Err(e) => { + eprintln!("Error: {}", e); + Err(e.into()) + } + } +} diff --git a/4-Infrastructure/rds_probe/src/models.rs b/4-Infrastructure/rds_probe/src/models.rs new file mode 100644 index 00000000..8856ba37 --- /dev/null +++ b/4-Infrastructure/rds_probe/src/models.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct TableInfo { + pub table_name: String, + pub table_schema: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ColumnInfo { + pub column_name: String, + pub data_type: String, + pub is_nullable: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CountResult { + pub domain: String, + pub archetype: String, + pub count: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PackageRecord { + pub pkg: String, + pub version: Option, + pub domain: Option, + pub tier: Option, + pub archetype: Option, + pub description: Option, + pub tags: Option, + pub source: Option, + pub indexed_utc: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CommandOutput { + pub success: bool, + pub data: serde_json::Value, +} diff --git a/5-Applications/scripts/canonical_math_functions.py b/5-Applications/scripts/canonical_math_functions.py index 98383153..8cef6792 100644 --- a/5-Applications/scripts/canonical_math_functions.py +++ b/5-Applications/scripts/canonical_math_functions.py @@ -9,12 +9,13 @@ Output: JSON + Markdown reference table """ import json +import os import time import requests from pathlib import Path from typing import Dict, List, Tuple -WOLFRAM_APP_ID = "HYJE3R3R63" +WOLFRAM_APP_ID = os.environ.get("WOLFRAM_ALPHA_APPID", "") WOLFRAM_API_URL = "https://api.wolframalpha.com/v2/query" # Canonical math function categories and test queries diff --git a/5-Applications/scripts/export_linear_from_rds.py b/5-Applications/scripts/export_linear_from_rds.py new file mode 100755 index 00000000..2606f282 --- /dev/null +++ b/5-Applications/scripts/export_linear_from_rds.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""export_linear_from_rds.py - Exports LINEAR domain records from RDS to JSON. +""" + +import json +import os +import sys +from pathlib import Path + +# Add infra and scripts folders to import path +sys.path.append(str(Path(__file__).resolve().parent.parent.parent / "4-Infrastructure" / "infra")) +sys.path.append(str(Path(__file__).resolve().parent)) + +from import_dumps_to_rds import get_rds_password + +def main(): + print("[+] Fetching RDS credentials...") + password = get_rds_password() + if not password: + print("[-] Failed to retrieve RDS password or IAM token. Exiting.") + sys.exit(1) + + host = os.environ.get("RDS_HOST", "database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com") + port = int(os.environ.get("RDS_PORT", "5432")) + user = os.environ.get("RDS_USER", "postgres") + dbname = os.environ.get("RDS_DB", "postgres") + + try: + import psycopg2 + conn = psycopg2.connect( + host=host, + port=port, + user=user, + password=password, + dbname=dbname, + sslmode="require" + ) + cur = conn.cursor() + except Exception as e: + print(f"[-] Database connection failed: {e}") + sys.exit(1) + + print("[+] Querying all LINEAR domain packages from RDS...") + try: + cur.execute(""" + SELECT pkg, version, domain, tier, archetype, description, tags, source, indexed_utc + FROM ene.packages + WHERE domain = 'LINEAR' + ORDER BY indexed_utc DESC + """) + rows = cur.fetchall() + except Exception as e: + print(f"[-] Failed to execute query: {e}") + sys.exit(1) + + records = [] + for r in rows: + records.append({ + "pkg": r[0], + "version": r[1], + "domain": r[2], + "tier": r[3], + "archetype": r[4], + "description": r[5], + "tags": r[6], + "source": r[7], + "indexed_utc": r[8] + }) + + out_file = Path(__file__).resolve().parent / "linear_export.json" + try: + with open(out_file, "w", encoding="utf-8") as f: + json.dump(records, f, indent=2, default=str) + print(f"[+] Successfully exported {len(records)} Linear records to {out_file}") + except Exception as e: + print(f"[-] Failed to write JSON output: {e}") + +if __name__ == "__main__": + main() diff --git a/5-Applications/scripts/fixedpoint_wolfram_verify.py b/5-Applications/scripts/fixedpoint_wolfram_verify.py index bf1e85d5..eaa3991a 100644 --- a/5-Applications/scripts/fixedpoint_wolfram_verify.py +++ b/5-Applications/scripts/fixedpoint_wolfram_verify.py @@ -9,6 +9,7 @@ Verifies the 9 FixedPoint.lean theorems using Wolfram Alpha API: import sys import json +import os import urllib.parse import urllib.request from pathlib import Path @@ -92,7 +93,7 @@ def main(): print("Verifying 9 FixedPoint.lean theorems") print("=" * 70) - app_id = "HYJE3R3R63" + app_id = os.environ.get("WOLFRAM_ALPHA_APPID", "") verifier = WolframAlphaVerifier(app_id) # FixedPoint.lean theorems to verify diff --git a/5-Applications/scripts/fundamental_math_verifier.py b/5-Applications/scripts/fundamental_math_verifier.py index ac7a446c..df25f53e 100644 --- a/5-Applications/scripts/fundamental_math_verifier.py +++ b/5-Applications/scripts/fundamental_math_verifier.py @@ -22,6 +22,7 @@ FixedPoint.lean. """ import json +import os import time import urllib.parse import urllib.request @@ -106,7 +107,7 @@ def main(): print("FUNDAMENTAL MATH VERIFICATION — DRIFT-PREVENTION ANCHOR NET") print("=" * 70) - app_id = "HYJE3R3R63" + app_id = os.environ.get("WOLFRAM_ALPHA_APPID", "") verifier = WolframAlphaVerifier(app_id) # (equation, description, expected_substring) diff --git a/5-Applications/scripts/import_dumps_to_rds.py b/5-Applications/scripts/import_dumps_to_rds.py new file mode 100755 index 00000000..9e7318c9 --- /dev/null +++ b/5-Applications/scripts/import_dumps_to_rds.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""import_dumps_to_rds.py - Ingestion pipeline from crawled JSON dumps to RDS. + +Imports Notion database pages into ene.wiki_pages/revisions/links/categories +and Linear issues into ene.packages. +""" + +import json +import os +import sys +from datetime import datetime, timezone +from pathlib import Path + +# Add infra folder to import path +sys.path.append(str(Path(__file__).resolve().parent.parent.parent / "4-Infrastructure" / "infra")) +sys.path.append(str(Path(__file__).resolve().parent.parent.parent / "4-Infrastructure")) + +def get_rds_password(): + password = os.environ.get("RDS_IAM_TOKEN") or os.environ.get("RDS_PASSWORD") + if password: + return password + + # Fallback to subprocess call to aws cli + try: + import subprocess + host = os.environ.get("RDS_HOST", "database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com") + user = os.environ.get("RDS_USER", "postgres") + region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1") + cmd = [ + "aws", "rds", "generate-db-auth-token", + "--hostname", host, + "--port", "5432", + "--username", user, + "--region", region + ] + res = subprocess.run(cmd, capture_output=True, text=True, check=True) + token = res.stdout.strip() + if token: + return token + except Exception as e: + print(f"AWS CLI token generation failed: {e}") + + return "" + +def import_notion(password: str): + notion_file = Path(__file__).resolve().parent / "notion_full_dump.json" + if not notion_file.exists(): + print("[-] notion_full_dump.json not found, skipping Notion import.") + return + + print("[+] Loading notion_full_dump.json...") + with open(notion_file, "r") as f: + dump_data = json.load(f) + + pages = dump_data.get("pages", []) + print(f"[+] Found {len(pages)} pages to import.") + + # Override password environment variable for ENEWikiLayer + os.environ["RDS_PASSWORD"] = password + + try: + from ene_rds_wiki_layer import ENERDSWikiLayer + wiki = ENERDSWikiLayer() + except Exception as e: + print(f"[-] Failed to initialize ENERDSWikiLayer: {e}") + return + + imported_count = 0 + for page in pages: + properties = page.get("properties", {}) + title = "Untitled" + for prop_name in ["Name", "title", "Title"]: + prop = properties.get(prop_name, {}) + if prop and prop.get("title"): + title_list = prop["title"] + if title_list and isinstance(title_list, list): + title = title_list[0].get("plain_text", "Untitled") + break + + content = page.get("_content", "") or "" + author = "notion_importer" + + try: + print(f" -> Importing page: {title}") + wiki.put_page(title=title, text=content, author=author, summary="Notion Full Crawl Ingestion") + imported_count += 1 + except Exception as e: + print(f" [!] Error importing page '{title}': {e}") + + print(f"[+] Notion import complete. Successfully imported {imported_count}/{len(pages)} pages.") + +def import_linear(password: str): + linear_file = Path(__file__).resolve().parent / "linear_full_dump.json" + if not linear_file.exists(): + print("[-] linear_full_dump.json not found, skipping Linear import.") + return + + print("[+] Loading linear_full_dump.json...") + with open(linear_file, "r") as f: + dump_data = json.load(f) + + issues = dump_data.get("issues", []) + print(f"[+] Found {len(issues)} issues to import.") + + host = os.environ.get("RDS_HOST", "database-1-instance-1.cghu8yqogqwo.us-east-1.rds.amazonaws.com") + port = int(os.environ.get("RDS_PORT", "5432")) + user = os.environ.get("RDS_USER", "postgres") + dbname = os.environ.get("RDS_DB", "postgres") + + try: + import psycopg2 + conn = psycopg2.connect( + host=host, + port=port, + user=user, + password=password, + dbname=dbname, + sslmode="require" + ) + cur = conn.cursor() + except Exception as e: + print(f"[-] Database connection failed: {e}") + return + + imported_count = 0 + for issue in issues: + identifier = issue.get("identifier") + if not identifier: + continue + + pkg_id = f"linear/{identifier}" + title = issue.get("title", "") + description = issue.get("description", "") + full_text = f"{title}\n\n{description}" if description else title + url = issue.get("url", "") + + # Extract labels + labels_nodes = issue.get("labels", {}).get("nodes", []) + labels_list = [l.get("name") for l in labels_nodes if l.get("name")] + tags_json = json.dumps(labels_list) + + now_iso = datetime.now(timezone.utc).isoformat() + + try: + cur.execute(""" + INSERT INTO ene.packages ( + pkg, version, domain, tier, archetype, + tags, description, source, indexed_utc + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + ON CONFLICT (pkg) DO UPDATE SET + description = EXCLUDED.description, + tags = EXCLUDED.tags, + source = EXCLUDED.source, + indexed_utc = EXCLUDED.indexed_utc + """, ( + pkg_id, + "1.0.0", + "LINEAR", + "INTENT", + "issue", + tags_json, + full_text, + url, + now_iso + )) + imported_count += 1 + except Exception as e: + print(f" [!] Error importing issue '{pkg_id}': {e}") + + try: + conn.commit() + conn.close() + print(f"[+] Linear import complete. Successfully imported {imported_count}/{len(issues)} issues.") + except Exception as e: + print(f"[-] Database commit failed: {e}") + +def main(): + print("=== ENE RDS Ingestion Pipeline ===") + password = get_rds_password() + if not password: + print("[-] Failed to retrieve RDS password or IAM token. Exiting.") + sys.exit(1) + + import_notion(password) + import_linear(password) + print("=== Pipeline Execution Finished ===") + +if __name__ == "__main__": + main() diff --git a/5-Applications/scripts/store_wolfram_credential.py b/5-Applications/scripts/store_wolfram_credential.py index d4c318cf..5f6babb1 100644 --- a/5-Applications/scripts/store_wolfram_credential.py +++ b/5-Applications/scripts/store_wolfram_credential.py @@ -3,6 +3,7 @@ Store Wolfram Alpha credential in ENE """ +import os import sys from pathlib import Path @@ -33,7 +34,7 @@ def main(): sys.exit(1) # Store Wolfram Alpha credential - wolfram_app_id = "HYJE3R3R63" + wolfram_app_id = os.environ.get("WOLFRAM_ALPHA_APPID", "") print(f"\nStoring Wolfram Alpha App ID: {wolfram_app_id}") diff --git a/5-Applications/scripts/test_knowledge_ingestion.py b/5-Applications/scripts/test_knowledge_ingestion.py index 404a7fe6..aa018b0b 100644 --- a/5-Applications/scripts/test_knowledge_ingestion.py +++ b/5-Applications/scripts/test_knowledge_ingestion.py @@ -11,7 +11,7 @@ from infra.knowledge_ingestion import KnowledgeIngestion def test_wolfram_alpha(): """Test Wolfram Alpha API integration""" - api_key = "HYJE3R3R63" + api_key = os.environ.get("WOLFRAM_ALPHA_APPID", "") print("Testing Wolfram Alpha API integration...") ingestion = KnowledgeIngestion(api_key)