import { MongoClient, type Db, type Collection } from 'mongodb'; import type { EngineerContext, Collision, Intervention, InterventionOutcome, Pod, } from '@podman/shared'; import { env } from '../env.js'; let clientPromise: Promise | null = null; function getClient(): Promise { if (!clientPromise) { const client = new MongoClient(env.MONGODB_URI, { serverSelectionTimeoutMS: 3000 }); clientPromise = client.connect(); } return clientPromise; } /** The PodMan database (name comes from the MONGODB_URI path, e.g. `podman`). */ export async function getDb(): Promise { const client = await getClient(); return client.db(); } export async function closeMemory(): Promise { if (!clientPromise) return; const client = await clientPromise; clientPromise = null; await client.close(); } export interface PodCollections { pods: Collection; observations: Collection; collisions: Collection; interventions: Collection; outcomes: Collection; } export async function collections(): Promise { const db = await getDb(); return { pods: db.collection('pods'), observations: db.collection('observations'), collisions: db.collection('collisions'), interventions: db.collection('interventions'), outcomes: db.collection('outcomes'), }; } export interface GitState { changedFiles: string[]; branch: string | null; recentCommit: string | null; gitUpdatedAt: Date | null; } /** Fetch latest git state per engineer for a pod from the engineer_states collection. * Returns a map keyed by engineer name (matches --name arg used in podman-agent.mjs). */ export async function getGitStates(podId: string): Promise> { const db = await getDb(); const col = db.collection<{ _id: string; name: string; changedFiles?: string[]; branch?: string | null; recentCommit?: string | null; gitUpdatedAt?: Date; }>('engineer_states'); const docs = await col.find({ podId }).toArray(); const map = new Map(); for (const doc of docs) { map.set(doc.name, { changedFiles: doc.changedFiles ?? [], branch: doc.branch ?? null, recentCommit: doc.recentCommit ?? null, gitUpdatedAt: doc.gitUpdatedAt ?? null, }); } return map; } /** Connect, verify reachability, and create helpful indexes. Call once on startup. */ export async function initMemory(): Promise { const db = await getDb(); await db.command({ ping: 1 }); const c = await collections(); // Create each index independently so one failure (e.g. the unique pods index // failing on pre-existing duplicate ids) doesn't abort the others or block // seeding. Failures are logged loudly rather than silently swallowed. const indexes: Array<[string, () => Promise]> = [ ['pods.id (unique)', () => c.pods.createIndex({ id: 1 }, { unique: true })], ['observations.podId', () => c.observations.createIndex({ podId: 1, observedAt: -1 })], ['observations.engineerId', () => c.observations.createIndex({ engineerId: 1 })], ['collisions.podId', () => c.collisions.createIndex({ podId: 1, detectedAt: -1 })], [ 'collisions.memorySignature', () => c.collisions.createIndex({ podId: 1, memorySignature: 1 }), ], ['collisions.file', () => c.collisions.createIndex({ podId: 1, file: 1, detectedAt: -1 })], ['interventions.collisionId', () => c.interventions.createIndex({ collisionId: 1 })], ['outcomes.interventionId', () => c.outcomes.createIndex({ interventionId: 1 })], ]; for (const [name, make] of indexes) { try { await make(); } catch (err) { console.error(`[memory] index "${name}" failed: ${(err as Error).message}`); } } console.log(`[memory] mongo connected -> ${db.databaseName}`); }