// ─── Strata Analysis Container Server ──────────────────────────────── // HTTP server running inside the Cloudflare Container. // Endpoints: // POST /analyze — run strata analysis on an island // POST /deriveIsland — derive island (connected component) from a lexmin vertex // POST /sync — sync vault + carry data // GET /health — health check import { analyze } from "./analysis.js"; import { syncAll, syncVault, syncCarry } from "./sync.js"; import { createServer } from "http"; import { execSync, } from "child_process"; const PORT = 8080; const CARRY_PATH = process.env.CARRY_PATH || "/data/carry"; createServer(async (req, res) => { const url = new URL(req.url || "/", `http://localhost:${PORT}`); // ── Health ── if (url.pathname === "/health" && req.method === "GET") { res.writeHead(200); res.end("ok"); return; } // ── Sync ── if (url.pathname === "/sync" && req.method === "POST") { try { await syncAll(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ ok: true })); } catch (e: any) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } return; } // ── Derive Islands ── // Input: { connections: Array<{source, target, connectionType, note, did, rkey}>, seed?: string } // - With seed: derive single island containing that vertex // - Without seed: detect all islands (all connected components) // Output: { islands: Array<{id, lexmin, vertices, edges}> } if (url.pathname === "/deriveIsland" && req.method === "POST") { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(chunk); const body = Buffer.concat(chunks).toString(); try { const input = JSON.parse(body); const { connections, seed } = input; if (!Array.isArray(connections)) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "connections[] required" })); return; } // Build adjacency list const adj = new Map>(); const allVertices = new Set(); for (const conn of connections) { const s = conn.source as string; const t = conn.target as string; if (!adj.has(s)) adj.set(s, []); if (!adj.has(t)) adj.set(t, []); adj.get(s)!.push(conn); adj.get(t)!.push(conn); allVertices.add(s); allVertices.add(t); } // BFS from a seed vertex to find one connected component function bfsComponent(start: string): { vertices: string[]; edges: any[]; } { const visited = new Set(); const queue = [start]; const componentEdges: any[] = []; while (queue.length > 0) { const current = queue.shift()!; if (visited.has(current)) continue; visited.add(current); for (const conn of adj.get(current) || []) { const s = conn.source as string; const t = conn.target as string; componentEdges.push({ uri: `at://${conn.did}/network.cosmik.connection/${conn.rkey}`, did: conn.did, rkey: conn.rkey, source: s, target: t, connectionType: conn.connectionType || "relates", note: conn.note || "", }); if (!visited.has(s)) queue.push(s); if (!visited.has(t)) queue.push(t); } } // Dedupe edges const vertexSet = new Set(visited); const edges = componentEdges.filter(e => vertexSet.has(e.source) && vertexSet.has(e.target)); const seenEdges = new Set(); const dedupedEdges = edges.filter(e => { const key = `${e.source}|${e.target}|${e.rkey}`; if (seenEdges.has(key)) return false; seenEdges.add(key); return true; }); const sortedVertices = [...visited].sort(); return { vertices: sortedVertices, edges: dedupedEdges }; } const { createHash } = await import("crypto"); if (seed) { // Single island from seed vertex const { vertices, edges } = bfsComponent(seed); const lexmin = vertices[0]; const id = createHash("sha256").update(lexmin).digest("hex").slice(0, 16); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ islands: [{ id, lexmin, vertices, edges }] })); } else { // All islands — find all connected components const globalVisited = new Set(); const islands: any[] = []; for (const vertex of allVertices) { if (globalVisited.has(vertex)) continue; const { vertices, edges } = bfsComponent(vertex); for (const v of vertices) globalVisited.add(v); const lexmin = vertices[0]; const id = createHash("sha256").update(lexmin).digest("hex").slice(0, 16); islands.push({ id, lexmin, vertices, edges }); } // Sort by vertex count descending islands.sort((a, b) => b.vertices.length - a.vertices.length); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ islands })); } } catch (e: any) { console.error("deriveIsland error:", e); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } return; } // ── Analyze ── if (url.pathname === "/analyze" && req.method === "POST") { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(chunk); const body = Buffer.concat(chunks).toString(); try { const input = JSON.parse(body); const result = await analyze(input); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result)); } catch (e: any) { console.error("Analysis error:", e); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } return; } res.writeHead(404); res.end("Not found"); }).listen(PORT, () => { console.log(`Strata analysis container listening on port ${PORT}`); // Sync on startup syncAll().catch(e => console.error("Initial sync failed:", e)); });