// Utility functions for daemon operations using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.Json; using Npgsql; namespace AIBrain.Daemon { public class DbConfig { public string Dsn { get; set; } public string Host { get; set; } = Environment.GetEnvironmentVariable("PGHOST") ?? "localhost"; public int Port { get; set; } = int.Parse(Environment.GetEnvironmentVariable("PGPORT") ?? "5432"); public string DbName { get; set; } = Environment.GetEnvironmentVariable("PGDATABASE") ?? Environment.GetEnvironmentVariable("POSTGRES_DB") ?? "postgres"; public string User { get; set; } = Environment.GetEnvironmentVariable("PGUSER") ?? Environment.GetEnvironmentVariable("POSTGRES_USER") ?? "postgres"; public string Password { get; set; } = Environment.GetEnvironmentVariable("PGPASSWORD") ?? Environment.GetEnvironmentVariable("POSTGRES_PASSWORD") ?? ""; public string SslMode { get; set; } = Environment.GetEnvironmentVariable("PGSSLMODE") ?? "disable"; public int Timeout { get; set; } = int.Parse(Environment.GetEnvironmentVariable("PGCONNECT_TIMEOUT") ?? "10"); public static DbConfig FromEnv() { return new DbConfig { Dsn = Environment.GetEnvironmentVariable("DATABASE_URL") }; } public string ToConnInfo() { if (!string.IsNullOrEmpty(Dsn)) return Dsn; var parts = new List(); if (!string.IsNullOrEmpty(Host)) parts.Add($"Host={Host}"); if (Port > 0) parts.Add($"Port={Port}"); if (!string.IsNullOrEmpty(DbName)) parts.Add($"Database={DbName}"); if (!string.IsNullOrEmpty(User)) parts.Add($"Username={User}"); if (!string.IsNullOrEmpty(Password)) parts.Add($"Password={Password}"); if (!string.IsNullOrEmpty(SslMode)) parts.Add($"SslMode={SslMode}"); if (Timeout > 0) parts.Add($"Timeout={Timeout}"); return string.Join(";", parts); } } public static class Utils { public static void JsonLog(string eventName, Dictionary fields = null) { var logEntry = new Dictionary { ["event"] = eventName, ["timestamp"] = DateTime.UtcNow.ToString("o") }; if (fields != null) { foreach (var kvp in fields) { logEntry[kvp.Key] = kvp.Value; } } Console.WriteLine(JsonSerializer.Serialize(logEntry)); } public static string Sha256Hex(string text) { using (var sha256 = SHA256.Create()) { var bytes = Encoding.UTF8.GetBytes(text); var hashBytes = sha256.ComputeHash(bytes); return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); } } public static NpgsqlConnection Connect() { var cfg = DbConfig.FromEnv(); var connInfo = cfg.ToConnInfo(); JsonLog("db_connect", new Dictionary { ["host"] = cfg.Host, ["db"] = cfg.DbName, ["sslmode"] = cfg.SslMode }); return new NpgsqlConnection(connInfo); } public static void EnsureSchema(NpgsqlConnection conn) { using (var cmd = conn.CreateCommand()) { cmd.CommandText = @" CREATE TABLE IF NOT EXISTS atomic_facts ( hash CHAR(64) PRIMARY KEY, category TEXT NOT NULL, sentence TEXT NOT NULL, inserted_at TIMESTAMPTZ NOT NULL DEFAULT now() );"; cmd.ExecuteNonQuery(); cmd.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS atomic_facts_hash_idx ON atomic_facts(hash);"; cmd.ExecuteNonQuery(); } JsonLog("db_schema_ready", new Dictionary { ["table"] = "atomic_facts" }); } public static (int inserted, int skipped) InsertFacts( NpgsqlConnection conn, List<(string hash, string category, string sentence)> items) { if (items.Count == 0) return (0, 0); using (var cmd = conn.CreateCommand()) { cmd.CommandText = @" INSERT INTO atomic_facts (hash, category, sentence) VALUES (@hash, @category, @sentence) ON CONFLICT (hash) DO NOTHING"; foreach (var item in items) { cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@hash", item.hash); cmd.Parameters.AddWithValue("@category", item.category); cmd.Parameters.AddWithValue("@sentence", item.sentence); cmd.ExecuteNonQuery(); } } // Check how many exist now var hashes = items.Select(x => x.hash).ToArray(); int present; using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT count(*) FROM atomic_facts WHERE hash = ANY(@hashes)"; cmd.Parameters.AddWithValue("@hashes", hashes); present = Convert.ToInt32(cmd.ExecuteScalar()); } var inserted = present; var skipped = Math.Max(0, items.Count - inserted); return (inserted, skipped); } public static List<(string hash, string category, string sentence)> MakeItems( string category, IEnumerable sentences) { var output = new List<(string, string, string)>(); foreach (var s in sentences) { var hash = Sha256Hex(s); output.Add((hash, category, s)); } return output; } public static List Default500(string category, string prefix) { var output = new List(); for (int i = 1; i <= 500; i++) { output.Add($"{prefix} #{i:D3} — {category} atomic seed"); } return output; } } }