checkpoint: matrix-agent-validated (2026-04-25)

Architectural snapshot of the lakehouse codebase at the point where the
full matrix-driven agent loop with Mem0 versioning + deletion was
validated end-to-end.

WHAT THIS REPO IS
A clean single-commit snapshot of the lakehouse code. Heavy test data
(.parquet datasets, vector indexes) excluded — see REPLICATION.md for
regen path. Full lakehouse history at git.agentview.dev/profit/lakehouse.

WHAT WAS PROVEN
- Vector retrieval across multi-corpora matrix (chicago_permits + entity
  briefs + sec_tickers + distilled procedural + llm_team runs)
- Observer hand-review (cloud + heuristic fallback) gating each candidate
- Local-model agent loop (qwen3.5:latest) with tool use + scratchpad
- Playbook seal on success → next-iter retrieval surfaces it as preamble
- Mem0 versioning + deletion in pathway_memory:
    * UPSERT: ADD on new workflow, UPDATE bumps replay_count on identical
    * REVISE: chains versions, parent.superseded_at + superseded_by stamped
    * RETIRE: marks specific trace retired with reason, excluded from retrieval
    * HISTORY: walks chain root→tip, cycle-safe

KEY DIRECTORIES
- crates/vectord/src/pathway_memory.rs — Mem0 ops live here
- crates/vectord/src/playbook_memory.rs — original Mem0 reference
- tests/agent_test/ — local-model agent harness + PRD + session archives
- scripts/dump_raw_corpus.sh — MinIO bucket dump (raw test corpus)
- scripts/vectorize_raw_corpus.ts — corpus → vector indexes
- scripts/analyze_chicago_contracts.ts — real inference pipeline
- scripts/seal_agent_playbook.ts — Mem0 upsert from agent traces

Replication: see REPLICATION.md for Debian 13 clean install + cloud-only
adaptation (no local Ollama).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
profit
2026-04-25 19:43:27 -05:00
co-authored by Claude Opus 4.7
commit ac01fffd9a
325 changed files with 82720 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Lakehouse · Visual Control Plane</title>
<link rel="stylesheet" href="/ui.css" />
</head>
<body>
<!-- TOP BAR — service heartbeats + view switcher + run selector -->
<header id="topbar">
<div class="brand">
<span class="sig">◆</span>
<span>LAKEHOUSE · VCP</span>
<span class="build" id="build-ts"></span>
</div>
<nav id="views">
<button data-view="map" class="on">MAP</button>
<button data-view="trace">TRACE</button>
<button data-view="trajectory">TRAJECTORY</button>
<button data-view="metrics">METRICS</button>
<button data-view="kb">KB</button>
<button data-view="console">CONSOLE</button>
</nav>
<div id="hb">
<span class="hbchip" data-svc="gateway">GW</span>
<span class="hbchip" data-svc="sidecar">SC</span>
<span class="hbchip" data-svc="observer">OBS</span>
<span class="hbchip" data-svc="mcp">MCP</span>
<span class="hbchip" data-svc="context7">CTX7</span>
</div>
</header>
<main>
<!-- MAIN STAGE — view-switched but context panel persists -->
<section id="stage">
<!-- System Map view -->
<div id="view-map" class="view on">
<div id="overlay-controls">
<span class="lbl">OVERLAY:</span>
<button data-ov="status" class="on">status</button>
<button data-ov="confidence">confidence</button>
<button data-ov="gradient">gradient</button>
<button data-ov="verdict">verdict</button>
<button data-ov="activity">activity</button>
</div>
<svg id="map"></svg>
<div id="legend">
<span class="lg healthy">● healthy</span>
<span class="lg degraded">● degraded</span>
<span class="lg down">● down</span>
<span class="lg active">◆ active flow</span>
</div>
</div>
<!-- Trace view -->
<div id="view-trace" class="view">
<div class="subhead">
<span>TASK TRACE — file:</span>
<span id="trace-file">—</span>
<span class="spacer"></span>
<span id="trace-runs"></span>
</div>
<div id="trace-timeline"></div>
<div id="trace-detail"></div>
</div>
<!-- Trajectory view — scrum trajectory, refactor signals, reverse index -->
<div id="view-trajectory" class="view">
<div class="traj-header">
<input id="traj-search" type="text" placeholder="Reverse index: search every scrum review for a phrase ('auth', 'middleware', 'dead code', 'ADR-012', …)" />
<div id="traj-stats"></div>
</div>
<div id="traj-body"></div>
</div>
<!-- Metrics view -->
<div id="view-metrics" class="view">
<div class="metric-grid" id="metric-grid"></div>
</div>
<!-- KB view -->
<div id="view-kb" class="view">
<div class="kb-grid" id="kb-grid"></div>
</div>
<!-- Console view — per-service live logs -->
<div id="view-console" class="view">
<div class="console-toolbar">
<span class="con-eyebrow">SERVICE LOGS</span>
<span id="con-tabs">
<button data-svc="gateway" class="on">gateway</button>
<button data-svc="sidecar">sidecar</button>
<button data-svc="observer">observer</button>
<button data-svc="mcp">mcp</button>
<button data-svc="context7">ctx7</button>
<button data-svc="auditor">auditor</button>
<button data-svc="langfuse">langfuse</button>
<button data-svc="summary">▣ all</button>
</span>
<span class="spacer"></span>
<span id="con-unit"></span>
</div>
<div id="console-log"></div>
</div>
</section>
<!-- CONTINUOUS CONTEXT PANEL — right rail, always visible -->
<aside id="context">
<div class="ctx-header">
<span class="ctx-eyebrow">CONTEXT</span>
<span id="ctx-target">no selection</span>
</div>
<div id="ctx-body">
<div class="ctx-hint">Click a node or a file in KB to inspect. Context persists across view switches.</div>
</div>
</aside>
</main>
<!-- BOTTOM STRIP — btop-style real-time stream -->
<section id="stream">
<div class="stream-head">
<span class="stream-eyebrow">STREAM</span>
<span class="spacer"></span>
<span id="stream-file">—</span>
<span class="dot" id="stream-tick"></span>
</div>
<div id="stream-body"></div>
</section>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="/ui.js"></script>
</body>
</html>
+455
View File
@@ -0,0 +1,455 @@
// Visual Control Plane server — v1
// Single Bun.serve process on :3950. Serves static index.html and
// /data/* endpoints that fan out to the live services + tail jsonl KB
// files. No build step, no node_modules. Restart via systemd or
// `bun run ui/server.ts`.
const PORT = Number(process.env.LH_UI_PORT ?? 3950);
const KB = "/home/profit/lakehouse/data/_kb";
const REPO = "/home/profit/lakehouse";
const GATEWAY = "http://localhost:3100";
const SIDECAR = "http://localhost:3200";
const OBSERVER = "http://localhost:3800";
const MCP = "http://localhost:3700";
const CONTEXT7 = "http://localhost:3900";
// Tail helper — read last N lines of a jsonl file without loading
// the whole thing. For files up to a few MB this is fine to read fully.
async function tailJsonl(path: string, n = 50): Promise<any[]> {
try {
const text = await Bun.file(path).text();
const lines = text.trim().split("\n").filter(Boolean);
const tail = lines.slice(-n);
return tail.map(l => {
try { return JSON.parse(l); } catch { return { _raw: l, _error: "parse" }; }
});
} catch (e) {
return [];
}
}
async function tryFetch(url: string, timeout = 1500): Promise<any | null> {
try {
const r = await fetch(url, { signal: AbortSignal.timeout(timeout) });
if (!r.ok) return null;
// Fix 2026-04-24: some upstream services (observer Bun.serve) return
// JSON without an application/json content-type. Don't rely on header
// — try parsing the body as JSON; fall back to raw text on failure.
const body = await r.text();
try { return JSON.parse(body); } catch { return body; }
} catch {
return null;
}
}
// Compact the massive /vectors/indexes response into just the shape the
// UI needs: [{name, source, model, dims, chunks, bucket, backend}]
async function indexesSummary(): Promise<any> {
const j = await tryFetch(`${GATEWAY}/vectors/indexes`);
if (!Array.isArray(j)) return { count: 0, items: [] };
const items = j.slice(0, 12).map((i: any) => ({
name: i.index_name,
source: i.source,
dims: i.dimensions,
chunks: i.chunk_count,
backend: i.vector_backend,
bucket: i.bucket,
}));
return { count: j.length, items };
}
async function servicesSnapshot() {
const [gw, sc, obs, mcp, c7, jstats, ustats] = await Promise.all([
tryFetch(`${GATEWAY}/health`),
tryFetch(`${SIDECAR}/health`),
tryFetch(`${OBSERVER}/health`),
tryFetch(`${MCP}/health`),
tryFetch(`${CONTEXT7}/health`),
tryFetch(`${GATEWAY}/journal/stats`),
tryFetch(`${GATEWAY}/v1/usage`),
]);
return {
ts: new Date().toISOString(),
nodes: [
{ id: "gateway", label: "Gateway :3100", status: gw ? "healthy" : "down", health: gw },
{ id: "sidecar", label: "Sidecar :3200", status: sc ? "healthy" : "down", health: sc },
{ id: "observer", label: "Observer :3800", status: obs ? "healthy" : "down", health: obs,
stats: await tryFetch(`${OBSERVER}/stats`) },
{ id: "mcp", label: "MCP :3700", status: mcp ? "healthy" : "down", health: mcp },
{ id: "context7", label: "Context7 :3900", status: c7 ? "healthy" : "down", health: c7 },
],
// Virtual nodes — backed by gateway subsystems rather than own ports
subsystems: [
{ id: "journal", label: "Journal", stats: jstats },
{ id: "usage", label: "Usage /v1", stats: ustats },
{ id: "vectord", label: "Vectord", stats: await indexesSummary() },
{ id: "playbook", label: "Playbook", stats: await tryFetch(`${GATEWAY}/vectors/playbook_memory/status`) },
{ id: "agent", label: "Autotune", stats: await tryFetch(`${GATEWAY}/vectors/agent/status`) },
],
};
}
// Extract phrase-level markers that indicate "this should be removed,
// simplified, or refactored" across scrum suggestions. These are the
// signals that accumulate into a refactor recommendation.
const REFACTOR_PHRASES = [
"should be removed", "remove this", "dead code", "unused", "unnecessary",
"duplicate of", "duplicates", "redundant",
"consolidate", "merge with", "extract into",
"refactor", "rewrite", "replace with",
"orphaned", "stale", "deprecated",
"pseudocode", "placeholder", "stub",
"split this file", "too large",
];
// Signal-class classifier — per file, given 2+ consecutive iterations'
// reviews, tag the file's behavior:
// CONVERGING — resolved > novel, score ↑
// LOOPING — 3+ same findings repeat, novel = 0, score flat
// ORBITING — novel findings each iter, no resolved (healthy depth)
// PLATEAU — score flat + findings flat (diminishing returns)
// MIXED — partial/unclear
// This is the foundation for iter-6+ auto-routing: each class gets a
// different sub-pipeline (specialist model, reviewer rotation, etc).
const SIGNAL_PHRASES = [
"pseudocode", "placeholder", "stub", "unwired", "missing", "dead code", "orphaned",
"duplicate", "redundant", "refactor", "rewrite", "remove", "unused", "unnecessary",
];
async function signalClasses(): Promise<any> {
const runsDir = `${REPO}/tests/real-world/runs`;
// Load every review, group by file, sort by timestamp
const perFile: Record<string, Array<{run: string, phrases: Set<string>, score: number | null, conf_avg: number | null, findings: number, ts: number}>> = {};
try {
const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false }));
for (const d of dirs) {
const files = await Array.fromAsync(new Bun.Glob("review_*.json").scan({ cwd: `${runsDir}/${d}` }));
for (const f of files) {
try {
const p = `${runsDir}/${d}/${f}`;
const j = JSON.parse(await Bun.file(p).text());
const key = j.file?.replace("/home/profit/lakehouse/", "") ?? "?";
const sug = (j.suggestions ?? "").toLowerCase();
const phrases = new Set<string>();
for (const ph of SIGNAL_PHRASES) if (sug.includes(ph)) phrases.add(ph);
const scoreMatch = sug.match(/(\d(?:\.\d)?)\s*\/\s*10\b/);
const score = scoreMatch ? parseFloat(scoreMatch[1]) : null;
const mconf = [...sug.matchAll(/(?:confidence[*:\s]*\s*|\|\s*)(\d{1,3})\s*%/gi)].map(m=>parseInt(m[1],10));
const jconf = [...sug.matchAll(/"confidence"\s*:\s*(\d{1,3})(?!\d)/gi)].map(m=>parseInt(m[1],10));
const all = [...mconf, ...jconf].filter(x => 0 <= x && x <= 100);
const conf_avg = all.length ? Math.round(all.reduce((a,b)=>a+b,0)/all.length) : null;
const ts = (await Bun.file(p).stat()).mtime.getTime();
(perFile[key] ??= []).push({ run: d, phrases, score, conf_avg, findings: all.length, ts });
} catch {}
}
}
} catch (e) {
return { error: String(e), classes: {} };
}
const classes: Record<string, any> = {};
for (const [file, runs] of Object.entries(perFile)) {
runs.sort((a, b) => a.ts - b.ts);
if (runs.length < 2) { classes[file] = { cls: "NEW", runs: runs.length }; continue; }
const last = runs[runs.length - 1];
const prev = runs[runs.length - 2];
const novel = [...last.phrases].filter(p => !prev.phrases.has(p));
const resolved = [...prev.phrases].filter(p => !last.phrases.has(p));
const looping = [...prev.phrases].filter(p => last.phrases.has(p));
const dScore = (last.score != null && prev.score != null) ? last.score - prev.score : null;
const dConf = (last.conf_avg != null && prev.conf_avg != null) ? last.conf_avg - prev.conf_avg : null;
const dFindings = last.findings - prev.findings;
let cls: string;
if (dScore != null && dScore > 0 && resolved.length > novel.length) cls = "CONVERGING";
else if (looping.length >= 3 && novel.length === 0 && (dScore == null || Math.abs(dScore) < 0.5)) cls = "LOOPING";
else if (novel.length >= 2 && resolved.length === 0) cls = "ORBITING";
else if (Math.abs(dFindings) <= 1 && (dScore == null || Math.abs(dScore) < 0.5)) cls = "PLATEAU";
else cls = "MIXED";
classes[file] = {
cls,
runs: runs.length,
iter_span: `${runs[0].run}…${last.run}`,
prev_score: prev.score,
last_score: last.score,
delta_score: dScore,
delta_conf: dConf,
delta_findings: dFindings,
novel,
resolved,
looping,
};
}
// Summary counts
const counts: Record<string, number> = {};
for (const v of Object.values(classes)) counts[v.cls] = (counts[v.cls] ?? 0) + 1;
return { generated_at: new Date().toISOString(), counts, classes };
}
async function refactorSignals(): Promise<any> {
// Walk every accepted review across all scrum runs. For each file,
// count how many times its suggestions mention a refactor phrase.
// Return a sorted list — files most often flagged for refactor first.
const runsDir = `${REPO}/tests/real-world/runs`;
const perFile: Record<string, { file: string; hits: number; phrases: Record<string, number>; examples: string[]; iterations: number }> = {};
try {
const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false }));
for (const d of dirs) {
const files = await Array.fromAsync(new Bun.Glob("review_*.json").scan({ cwd: `${runsDir}/${d}` }));
for (const f of files) {
const p = `${runsDir}/${d}/${f}`;
try {
const j = JSON.parse(await Bun.file(p).text());
const file = j.file?.replace("/home/profit/lakehouse/", "") ?? "?";
const sug = (j.suggestions ?? "").toLowerCase();
if (!perFile[file]) perFile[file] = { file, hits: 0, phrases: {}, examples: [], iterations: 0 };
perFile[file].iterations++;
for (const phrase of REFACTOR_PHRASES) {
const count = (sug.match(new RegExp(phrase, "gi")) ?? []).length;
if (count > 0) {
perFile[file].hits += count;
perFile[file].phrases[phrase] = (perFile[file].phrases[phrase] ?? 0) + count;
// Pull one example sentence around the phrase
if (perFile[file].examples.length < 3) {
const idx = sug.indexOf(phrase);
if (idx >= 0) {
const s = Math.max(0, idx - 60);
const e = Math.min(sug.length, idx + phrase.length + 80);
perFile[file].examples.push("…" + sug.slice(s, e).replace(/\s+/g, " ") + "…");
}
}
}
}
} catch {}
}
}
} catch (e) {
return { error: String(e), signals: [] };
}
const signals = Object.values(perFile)
.filter(x => x.hits > 0)
.sort((a, b) => b.hits - a.hits)
.slice(0, 30);
return { signals, scanned: Object.keys(perFile).length };
}
async function reverseIndex(query: string, limit = 20): Promise<any> {
// Grep-like substring search across every review's suggestions.
// Returns file + snippet + which iter it was in + score + verdict.
const runsDir = `${REPO}/tests/real-world/runs`;
if (!query || query.length < 2) return { query, hits: [] };
const q = query.toLowerCase();
const hits: any[] = [];
try {
const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false }));
for (const d of dirs) {
const files = await Array.fromAsync(new Bun.Glob("review_*.json").scan({ cwd: `${runsDir}/${d}` }));
for (const f of files) {
const p = `${runsDir}/${d}/${f}`;
try {
const j = JSON.parse(await Bun.file(p).text());
const sug = j.suggestions ?? "";
const lower = sug.toLowerCase();
const idx = lower.indexOf(q);
if (idx < 0) continue;
const s = Math.max(0, idx - 80);
const e = Math.min(sug.length, idx + q.length + 200);
hits.push({
file: j.file?.replace("/home/profit/lakehouse/", ""),
run_id: d,
model: j.escalated_to_model,
snippet: sug.slice(s, e).replace(/\s+/g, " "),
});
if (hits.length >= limit) break;
} catch {}
}
if (hits.length >= limit) break;
}
} catch (e) {
return { query, error: String(e), hits: [] };
}
return { query, hits };
}
async function fileHistory(relpath: string): Promise<any> {
// Walk all scrum_<id>/review_*.json files and gather every review
// for this file path. Returns timeline rows keyed by run_id.
const runsDir = `${REPO}/tests/real-world/runs`;
const out: any[] = [];
try {
const dirs = await Array.fromAsync(new Bun.Glob("scrum_*").scan({ cwd: runsDir, onlyFiles: false }));
for (const d of dirs) {
const safe = relpath.replaceAll("/", "_");
const p = `${runsDir}/${d}/review_${safe}.json`;
if (await Bun.file(p).exists()) {
const j = JSON.parse(await Bun.file(p).text());
const sug = j.suggestions ?? "";
const scoreMatch = sug.match(/(?:score[\s*:]*)?(\d(?:\.\d)?)\s*\/\s*10\b/i);
const score = scoreMatch ? parseFloat(scoreMatch[1]) : null;
const confs = [...sug.matchAll(/(?:Confidence[*:\s]*\s*|\|\s*)(\d{1,3})\s*%/gi)]
.map(m => parseInt(m[1], 10)).filter(x => x >= 0 && x <= 100);
const jsonConfs = [...sug.matchAll(/"confidence"\s*:\s*(\d{1,3})(?!\d)/gi)]
.map(m => parseInt(m[1], 10)).filter(x => x >= 0 && x <= 100);
const all = [...confs, ...jsonConfs];
const mt = await Bun.file(p).stat();
out.push({
run_id: d,
reviewed_at: j.reviewed_at ?? mt.mtime,
model: j.escalated_to_model,
score,
chars: sug.length,
conf_avg: all.length ? Math.round(all.reduce((a,b)=>a+b,0)/all.length) : null,
conf_min: all.length ? Math.min(...all) : null,
findings: all.length,
output_format: sug.includes('"verdict"') ? "forensic_json" : "markdown",
// first 1200 chars preview
preview: sug.slice(0, 1200),
});
}
}
} catch (e) {
return { error: String(e), history: [] };
}
out.sort((a, b) => String(a.reviewed_at).localeCompare(String(b.reviewed_at)));
return { file: relpath, history: out };
}
Bun.serve({
port: PORT,
hostname: "0.0.0.0",
async fetch(req) {
const url = new URL(req.url);
const path = url.pathname;
// Static shell
if (path === "/" || path === "/index.html") {
return new Response(Bun.file(`${REPO}/ui/index.html`));
}
if (path === "/ui.css") {
return new Response(Bun.file(`${REPO}/ui/ui.css`), { headers: { "content-type": "text/css" } });
}
if (path === "/ui.js") {
return new Response(Bun.file(`${REPO}/ui/ui.js`), { headers: { "content-type": "application/javascript" } });
}
// Data API
if (path === "/data/services") return Response.json(await servicesSnapshot());
if (path === "/data/reviews") {
const n = Number(url.searchParams.get("tail") ?? 50);
return Response.json(await tailJsonl(`${KB}/scrum_reviews.jsonl`, n));
}
if (path === "/data/findings") return Response.json(await tailJsonl(`${KB}/phase_sweep_findings.jsonl`));
if (path === "/data/metrics") return Response.json(await tailJsonl(`${KB}/scrum_loop_metrics.jsonl`));
if (path === "/data/trust") return Response.json(await tailJsonl(`${KB}/model_trust.jsonl`, 200));
if (path === "/data/overrides") return Response.json(await tailJsonl(`${KB}/human_overrides.jsonl`));
if (path === "/data/outcomes") return Response.json(await tailJsonl(`${KB}/outcomes.jsonl`, 30));
if (path === "/data/audit_facts") return Response.json(await tailJsonl(`${KB}/audit_facts.jsonl`, 30));
// Pathway memory — consensus-designed sidecar (2026-04-24). Two
// exposed metrics: reuse_rate (activity — is it firing?) and
// avg_rungs_saved_per_commit (value — is it earning its keep?).
// Round-3 consensus (qwen3.5:397b) pointed out that activity
// without value tells us nothing; the UI needs both to judge the
// health of the hot-swap learning loop.
if (path === "/data/pathway_stats") {
try {
const r = await fetch("http://localhost:3100/vectors/pathway/stats", { signal: AbortSignal.timeout(3000) });
if (!r.ok) return Response.json({ error: `vectord ${r.status}`, stats: null });
const stats = await r.json();
// Tail recent scrum events to compute avg_rungs_saved_per_commit
// (a committed review = any row in scrum_reviews.jsonl; rungs_saved
// only populates when pathway memory fired AND the recommended
// model actually produced the accept).
const reviews = await tailJsonl(`${KB}/scrum_reviews.jsonl`, 200);
let totalCommits = 0;
let totalRungsSaved = 0;
let hotSwapHits = 0;
for (const r of reviews) {
totalCommits++;
if (r.pathway_hot_swap_hit) hotSwapHits++;
if (typeof r.rungs_saved === "number") totalRungsSaved += r.rungs_saved;
}
return Response.json({
stats,
scrum_window: {
reviews: totalCommits,
hot_swap_hits: hotSwapHits,
pathway_reuse_rate: totalCommits ? hotSwapHits / totalCommits : 0,
avg_rungs_saved_per_commit: totalCommits ? totalRungsSaved / totalCommits : 0,
},
});
} catch (e) {
return Response.json({ error: (e as Error).message, stats: null });
}
}
if (path.startsWith("/data/file/")) {
const relpath = decodeURIComponent(path.slice("/data/file/".length));
return Response.json(await fileHistory(relpath));
}
if (path === "/data/refactor_signals") {
return Response.json(await refactorSignals());
}
if (path === "/data/signal_classes") {
return Response.json(await signalClasses());
}
if (path === "/data/search") {
const q = url.searchParams.get("q") ?? "";
return Response.json(await reverseIndex(q, 30));
}
// Per-service systemd log tail. Allowed service list is fixed so the
// :service path param can never be used to invoke arbitrary units.
if (path.startsWith("/data/logs/")) {
const svc = path.slice("/data/logs/".length).split("?")[0];
const UNITS: Record<string, string> = {
gateway: "lakehouse.service",
sidecar: "lakehouse-sidecar.service",
observer: "lakehouse-observer.service",
mcp: "lakehouse-agent.service",
context7: "lakehouse-context7-bridge.service",
auditor: "lakehouse-auditor.service",
langfuse: "lakehouse-langfuse-bridge.service",
};
const unit = UNITS[svc];
if (!unit) return Response.json({ error: "unknown service", allowed: Object.keys(UNITS) }, { status: 400 });
const n = Number(url.searchParams.get("n") ?? 60);
try {
// Use execFile-style API: pass args as array, never shell-interpolate
const proc = Bun.spawn(["journalctl", "-u", unit, "-n", String(n), "--no-pager", "--output=short-iso"], {
stdout: "pipe",
stderr: "pipe",
});
const text = await new Response(proc.stdout).text();
await proc.exited;
const lines = text.split("\n").filter(Boolean);
return Response.json({ service: svc, unit, lines });
} catch (e) {
return Response.json({ service: svc, unit, error: String(e), lines: [] });
}
}
// Live scrum log tail — best-effort
if (path === "/data/scrum_log") {
try {
const bg = await Array.fromAsync(new Bun.Glob("scrum_iter*.log").scan({ cwd: "/tmp" }));
if (bg.length === 0) return Response.json({ lines: [] });
bg.sort();
const latest = `/tmp/${bg[bg.length - 1]}`;
const text = await Bun.file(latest).text();
const lines = text.split("\n").slice(-80);
return Response.json({ file: latest, lines });
} catch (e) {
return Response.json({ error: String(e) });
}
}
return new Response("not found", { status: 404 });
},
});
console.log(`[ui] visual control plane listening on http://0.0.0.0:${PORT}`);
+440
View File
@@ -0,0 +1,440 @@
/* Lakehouse Visual Control Plane — neo-brutalist dark */
:root {
--bg: #0a0c10;
--bg-1: #10141a;
--bg-2: #171c24;
--border: #2a303b;
--border-hi: #3a4252;
--fg: #e8ecf3;
--fg-dim: #8a94a7;
--fg-muted: #525c6f;
--green: #3eed86;
--yellow: #ffbf3c;
--red: #ff4d6e;
--blue: #55c5ff;
--purple: #b57cff;
--orange: #ff9f43;
--shadow: 0 2px 0 #000;
--mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
--sans: -apple-system, Inter, system-ui, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body {
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
font-size: 13px;
display: flex;
flex-direction: column;
}
/* ────── TOP BAR ────── */
#topbar {
height: 44px;
display: flex;
align-items: center;
gap: 16px;
padding: 0 16px;
border-bottom: 1px solid var(--border);
background: var(--bg-1);
flex-shrink: 0;
}
.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; letter-spacing: 0.08em; }
.brand .sig { color: var(--green); font-size: 16px; }
.brand .build { color: var(--fg-muted); font-size: 10px; font-family: var(--mono); margin-left: 6px; }
#views { display: flex; gap: 2px; margin-left: 20px; }
#views button {
background: transparent; border: 1px solid var(--border); color: var(--fg-dim);
font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em;
padding: 5px 10px; cursor: pointer; text-transform: uppercase;
}
#views button:hover { border-color: var(--border-hi); color: var(--fg); }
#views button.on { background: var(--fg); color: var(--bg); border-color: var(--fg); font-weight: 700; }
#hb { margin-left: auto; display: flex; gap: 6px; }
.hbchip {
font-family: var(--mono); font-size: 10px; letter-spacing: 0.05em;
padding: 4px 8px; border: 1px solid var(--border); border-radius: 2px;
color: var(--fg-muted);
}
.hbchip[data-status="healthy"] { border-color: var(--green); color: var(--green); }
.hbchip[data-status="down"] { border-color: var(--red); color: var(--red); }
.hbchip[data-status="degraded"]{ border-color: var(--yellow); color: var(--yellow); }
/* ────── MAIN ────── */
main {
flex: 1;
display: grid;
grid-template-columns: 1fr 380px;
min-height: 0;
}
#stage { position: relative; border-right: 1px solid var(--border); min-height: 0; overflow: hidden; }
.view { display: none; width: 100%; height: 100%; }
.view.on { display: block; }
.subhead {
height: 32px; display: flex; align-items: center; gap: 10px;
padding: 0 14px; border-bottom: 1px solid var(--border);
background: var(--bg-1);
font-family: var(--mono); font-size: 11px; color: var(--fg-dim);
text-transform: uppercase; letter-spacing: 0.08em;
}
.subhead .spacer { flex: 1; }
/* ────── MAP ────── */
#view-map { position: relative; }
#overlay-controls {
position: absolute; top: 10px; left: 10px; z-index: 2;
display: flex; align-items: center; gap: 4px;
background: rgba(16,20,26,0.95);
border: 1px solid var(--border); padding: 4px;
}
#overlay-controls .lbl { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); padding: 0 8px; }
#overlay-controls button {
background: transparent; color: var(--fg-dim); border: 1px solid var(--border);
font-family: var(--mono); font-size: 10px; padding: 3px 8px; cursor: pointer;
text-transform: lowercase; letter-spacing: 0.05em;
}
#overlay-controls button:hover { border-color: var(--border-hi); color: var(--fg); }
#overlay-controls button.on { background: var(--fg); color: var(--bg); border-color: var(--fg); }
#map { width: 100%; height: 100%; }
#map .node-circle { stroke: var(--fg); stroke-width: 2; cursor: pointer; transition: r 200ms; }
#map .node-circle:hover { stroke-width: 3; }
#map .node-label {
font-family: var(--mono); font-size: 11px; fill: var(--fg);
pointer-events: none; text-anchor: middle; font-weight: 600;
}
#map .node-sub {
font-family: var(--mono); font-size: 9px; fill: var(--fg-muted);
pointer-events: none; text-anchor: middle;
}
#map .edge { stroke: var(--border-hi); stroke-width: 1.5; fill: none; }
#map .edge.active { stroke: var(--blue); stroke-width: 2; stroke-dasharray: 4 3; animation: dash 1.5s linear infinite; }
@keyframes dash { to { stroke-dashoffset: -14; } }
#map .node-selected { stroke: var(--yellow); stroke-width: 3; }
#legend {
position: absolute; bottom: 10px; left: 10px;
display: flex; gap: 16px; font-family: var(--mono); font-size: 10px;
background: rgba(16,20,26,0.95); border: 1px solid var(--border); padding: 6px 10px;
}
.lg { color: var(--fg-muted); }
.lg.healthy::before { content: ''; }
.lg.healthy { color: var(--green); }
.lg.degraded { color: var(--yellow); }
.lg.down { color: var(--red); }
.lg.active { color: var(--blue); }
/* ────── CONTEXT PANEL ────── */
#context {
background: var(--bg-1); overflow-y: auto; overflow-x: hidden;
display: flex; flex-direction: column;
}
.ctx-header {
height: 44px; display: flex; flex-direction: column; justify-content: center;
padding: 4px 14px; border-bottom: 1px solid var(--border);
background: var(--bg-2);
}
.ctx-eyebrow { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); letter-spacing: 0.1em; }
#ctx-target { font-family: var(--mono); font-size: 12px; color: var(--fg); font-weight: 600; margin-top: 2px; }
#ctx-body { padding: 12px 14px; flex: 1; overflow-y: auto; }
.ctx-hint { color: var(--fg-muted); font-style: italic; font-size: 11px; }
.ctx-row { padding: 6px 0; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; gap: 10px; font-family: var(--mono); font-size: 11px; }
.ctx-row .k { color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.ctx-row .v { color: var(--fg); text-align: right; word-break: break-all; }
.ctx-row .v.good { color: var(--green); }
.ctx-row .v.warn { color: var(--yellow); }
.ctx-row .v.bad { color: var(--red); }
.ctx-section-hd { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); letter-spacing: 0.1em; margin: 14px 0 4px; text-transform: uppercase; }
.pill {
display: inline-block; font-family: var(--mono); font-size: 10px;
padding: 2px 7px; border: 1px solid var(--border); margin-right: 4px;
}
.pill.tier-auto { border-color: var(--green); color: var(--green); }
.pill.tier-dry_run { border-color: var(--blue); color: var(--blue); }
.pill.tier-simulation { border-color: var(--yellow); color: var(--yellow); }
.pill.tier-block { border-color: var(--red); color: var(--red); }
.pill.ver-needs_patch { border-color: var(--orange); color: var(--orange); }
.pill.ver-pass { border-color: var(--green); color: var(--green); }
.pill.ver-fail { border-color: var(--red); color: var(--red); }
.pill.fmt-forensic_json { border-color: var(--purple); color: var(--purple); }
.pill.fmt-markdown { border-color: var(--fg-dim); color: var(--fg-dim); }
/* ────── TRACE ────── */
#trace-timeline {
display: flex; gap: 0; padding: 20px;
overflow-x: auto; border-bottom: 1px solid var(--border);
min-height: 140px;
}
.trace-node {
position: relative; flex: 0 0 140px; padding: 10px 12px;
border: 2px solid var(--border); background: var(--bg-1); cursor: pointer;
font-family: var(--mono); font-size: 10px;
}
.trace-node:hover { border-color: var(--border-hi); }
.trace-node.active { border-color: var(--yellow); background: var(--bg-2); }
.trace-node::after {
content: '→'; position: absolute; right: -14px; top: 50%; transform: translateY(-50%);
color: var(--fg-muted); font-size: 16px;
}
.trace-node:last-child::after { display: none; }
.trace-node .tn-run { color: var(--fg-muted); letter-spacing: 0.05em; margin-bottom: 4px; }
.trace-node .tn-score { font-size: 22px; font-weight: 700; color: var(--fg); }
.trace-node .tn-conf { color: var(--fg-dim); margin-top: 4px; }
.trace-node .tn-model { color: var(--purple); margin-top: 4px; font-size: 9px; }
#trace-detail { padding: 16px; overflow-y: auto; height: calc(100% - 172px); }
#trace-detail pre { font-family: var(--mono); font-size: 11px; color: var(--fg-dim); white-space: pre-wrap; word-break: break-word; }
/* ────── METRICS ────── */
.metric-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px; padding: 16px; overflow-y: auto; max-height: 100%;
}
.metric {
border: 1px solid var(--border); background: var(--bg-1);
padding: 14px; display: flex; flex-direction: column; gap: 6px;
}
.metric .m-label { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); letter-spacing: 0.08em; text-transform: uppercase; }
.metric .m-big { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; }
.metric .m-sub { font-family: var(--mono); font-size: 11px; color: var(--fg-dim); }
.metric .m-explain {
color: var(--fg); font-family: var(--sans); font-size: 12px;
line-height: 1.45; margin-top: 2px; font-weight: 400;
}
.metric .m-source {
color: var(--fg-muted); font-size: 10px; letter-spacing: 0.05em;
border-top: 1px dashed var(--border); padding-top: 6px; margin-top: 6px;
}
.metric .m-good {
color: var(--green); font-size: 10px; letter-spacing: 0.03em;
line-height: 1.5; opacity: 0.85;
}
.metric.warn .m-good { color: var(--yellow); }
.metric.bad .m-good { color: var(--red); }
.metric.good .m-big { color: var(--green); }
.metric.warn .m-big { color: var(--yellow); }
.metric.bad .m-big { color: var(--red); }
.bar { display: flex; height: 8px; border: 1px solid var(--border); background: var(--bg); margin-top: 4px; }
.bar > span { display: block; height: 100%; }
.bar .seg-auto { background: var(--green); }
.bar .seg-dry_run { background: var(--blue); }
.bar .seg-simulation { background: var(--yellow); }
.bar .seg-block { background: var(--red); }
/* ────── KB ────── */
.kb-grid {
padding: 16px; display: grid; gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
overflow-y: auto; max-height: 100%;
}
.kb-banner {
grid-column: 1 / -1;
border: 1px solid var(--border); background: var(--bg-1);
padding: 14px 16px; border-left: 3px solid var(--blue);
}
.kb-banner-title {
font-family: var(--mono); font-size: 11px; color: var(--blue);
letter-spacing: 0.1em; font-weight: 700; margin-bottom: 6px;
}
.kb-banner-body {
color: var(--fg); font-size: 12px; line-height: 1.55;
}
.kb-statline {
grid-column: 1 / -1;
display: flex; gap: 18px;
font-family: var(--mono); font-size: 11px; color: var(--fg-dim);
padding: 8px 14px; border: 1px solid var(--border);
background: var(--bg-2); margin-bottom: 2px;
}
.kb-statline .stat-warn { color: var(--yellow); font-weight: 700; }
.kb-file { border: 1px solid var(--border); background: var(--bg-1); padding: 10px 12px; cursor: pointer; }
.kb-file:hover { border-color: var(--border-hi); }
.kb-file .kf-path { font-family: var(--mono); font-size: 11px; color: var(--fg); word-break: break-all; }
.kb-file .kf-meta { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); margin-top: 4px; display: flex; gap: 10px; flex-wrap: wrap; }
.kb-file .kf-score { font-weight: 700; color: var(--fg); }
.kb-file .kf-delta.up { color: var(--green); }
.kb-file .kf-delta.down { color: var(--red); }
/* ────── CONSOLE ────── */
#view-console { background: #000; display: none; flex-direction: column; }
#view-console.on { display: flex; }
.console-toolbar {
height: 36px; display: flex; align-items: center; gap: 8px;
padding: 0 12px; background: var(--bg-1); border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.console-toolbar .con-eyebrow {
font-family: var(--mono); font-size: 10px; color: var(--fg-muted); letter-spacing: 0.1em;
text-transform: uppercase; margin-right: 6px;
}
.console-toolbar .spacer { flex: 1; }
#con-tabs { display: flex; gap: 2px; }
#con-tabs button {
background: transparent; border: 1px solid var(--border); color: var(--fg-dim);
font-family: var(--mono); font-size: 10px; letter-spacing: 0.05em;
padding: 4px 10px; cursor: pointer; text-transform: lowercase;
}
#con-tabs button:hover { border-color: var(--border-hi); color: var(--fg); }
#con-tabs button.on { background: var(--fg); color: var(--bg); border-color: var(--fg); font-weight: 700; }
#con-unit { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); }
#console-log {
flex: 1; padding: 12px 16px; overflow-y: auto;
font-family: var(--mono); font-size: 11px; color: var(--fg);
line-height: 1.5;
}
#console-log .cl-line { white-space: pre-wrap; word-break: break-all; }
#console-log .cl-info { color: var(--fg-dim); }
#console-log .cl-ok { color: var(--green); }
#console-log .cl-warn { color: var(--yellow); }
#console-log .cl-err { color: var(--red); }
/* ────── STREAM (bottom) ────── */
#stream {
height: 180px; background: var(--bg-1);
border-top: 1px solid var(--border);
display: flex; flex-direction: column; flex-shrink: 0;
}
.stream-head {
height: 26px; display: flex; align-items: center; gap: 10px;
padding: 0 14px; border-bottom: 1px solid var(--border);
font-family: var(--mono); font-size: 10px; color: var(--fg-muted);
letter-spacing: 0.1em; text-transform: uppercase;
}
.stream-head .spacer { flex: 1; }
#stream-file { color: var(--fg-dim); font-size: 10px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 0.35; transform: scale(0.8); } }
#stream-body {
flex: 1; padding: 8px 14px; overflow-y: auto; font-family: var(--mono); font-size: 11px; color: var(--fg-dim);
display: flex; flex-direction: column;
}
.sline { padding: 1px 0; white-space: pre; }
.sline.ok { color: var(--green); }
.sline.thin { color: var(--yellow); }
.sline.err { color: var(--red); }
.sline.info { color: var(--fg-dim); }
.sline.head { color: var(--fg); font-weight: 600; }
/* ────── TRAJECTORY view ────── */
#view-trajectory { display: none; flex-direction: column; }
#view-trajectory.on { display: flex; }
.traj-header {
border-bottom: 1px solid var(--border); background: var(--bg-1);
padding: 12px 16px; display: flex; flex-direction: column; gap: 6px;
}
#traj-search {
width: 100%; background: var(--bg-2); color: var(--fg);
border: 1px solid var(--border); padding: 8px 10px;
font-family: var(--mono); font-size: 12px;
}
#traj-search:focus { outline: none; border-color: var(--blue); }
#traj-stats {
font-family: var(--mono); font-size: 10px; color: var(--fg-muted); letter-spacing: 0.06em;
}
#traj-body {
flex: 1; overflow-y: auto; padding: 16px;
display: flex; flex-direction: column; gap: 10px;
}
.traj-section-head {
font-family: var(--mono); font-size: 11px; color: var(--blue);
letter-spacing: 0.1em; font-weight: 700; margin-top: 14px; margin-bottom: 4px;
border-bottom: 1px solid var(--border); padding-bottom: 6px;
}
.traj-section-head:first-child { margin-top: 0; }
.traj-section-explain {
color: var(--fg); font-size: 12px; line-height: 1.55; margin-bottom: 6px;
padding: 8px 10px; background: var(--bg-1); border-left: 2px solid var(--blue);
}
.traj-table { display: flex; flex-direction: column; border: 1px solid var(--border); }
.traj-row {
display: grid; grid-template-columns: 40px 1.5fr 80px 2fr 80px; gap: 12px;
padding: 8px 12px; border-bottom: 1px solid var(--border);
font-family: var(--mono); font-size: 11px; cursor: pointer;
}
.traj-row:hover { background: var(--bg-2); }
.traj-row:last-child { border-bottom: none; }
.traj-col-rank { color: var(--fg-muted); font-weight: 700; }
.traj-col-file { color: var(--fg); }
.traj-col-hits { color: var(--red); font-weight: 700; }
.traj-col-phrases { color: var(--fg-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.traj-col-iters { color: var(--fg-muted); text-align: right; }
.traj-spark-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); gap: 10px;
}
.traj-spark {
border: 1px solid var(--border); background: var(--bg-1);
padding: 12px; cursor: pointer;
}
.traj-spark:hover { border-color: var(--border-hi); }
.traj-spark-file { font-family: var(--mono); font-size: 11px; color: var(--fg); margin-bottom: 8px; }
.traj-spark-line { display: flex; align-items: center; gap: 6px; }
.traj-spark-pt {
flex: 0 0 80px; padding: 6px 8px; border: 1px solid var(--border);
background: var(--bg-2); text-align: center;
}
.traj-pt-score { font-family: var(--mono); font-size: 14px; font-weight: 800; color: var(--fg); }
.traj-pt-conf { font-family: var(--mono); font-size: 10px; color: var(--fg-dim); }
.traj-pt-label { font-family: var(--mono); font-size: 9px; color: var(--fg-muted); letter-spacing: 0.06em; }
.traj-spark-arrow { color: var(--fg-muted); font-size: 14px; }
.traj-spark-delta { font-family: var(--mono); font-size: 11px; color: var(--fg-dim); margin-top: 8px; }
.traj-spark-delta .delta-up { color: var(--green); }
.traj-spark-delta .delta-down { color: var(--red); }
.traj-spark-empty { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); font-style: italic; }
.traj-hit {
border: 1px solid var(--border); padding: 10px 12px; background: var(--bg-1); cursor: pointer;
}
.traj-hit:hover { border-color: var(--border-hi); }
.traj-hit-top { display: flex; gap: 14px; margin-bottom: 6px; }
.traj-hit-file { font-family: var(--mono); font-size: 11px; color: var(--fg); font-weight: 600; }
.traj-hit-meta { font-family: var(--mono); font-size: 10px; color: var(--fg-muted); }
.traj-hit-snip { font-family: var(--mono); font-size: 11px; color: var(--fg-dim); line-height: 1.5; }
/* signal classes */
.signal-class-row { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.signal-chip {
font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em;
padding: 4px 10px; border: 1px solid var(--border); font-weight: 700;
}
.signal-converging { color: var(--green); border-color: var(--green); }
.signal-looping { color: var(--red); border-color: var(--red); }
.signal-orbiting { color: var(--purple); border-color: var(--purple); }
.signal-plateau { color: var(--yellow); border-color: var(--yellow); }
.signal-mixed { color: var(--blue); border-color: var(--blue); }
.signal-new { color: var(--fg-muted); border-color: var(--fg-muted); }
.signal-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); gap: 8px;
}
.signal-card {
border: 1px solid var(--border); background: var(--bg-1);
padding: 10px 12px; cursor: pointer; border-left: 3px solid var(--fg-muted);
}
.signal-card.signal-converging { border-left-color: var(--green); }
.signal-card.signal-looping { border-left-color: var(--red); }
.signal-card.signal-orbiting { border-left-color: var(--purple); }
.signal-card.signal-plateau { border-left-color: var(--yellow); }
.signal-card.signal-mixed { border-left-color: var(--blue); }
.signal-card:hover { border-color: var(--border-hi); background: var(--bg-2); }
.signal-card-top { display: flex; gap: 10px; align-items: center; margin-bottom: 6px; }
.signal-card-file { font-family: var(--mono); font-size: 11px; color: var(--fg); }
.signal-card-body { font-family: var(--mono); font-size: 10px; color: var(--fg-dim); line-height: 1.55; }
.signal-card-body > div { margin-top: 2px; }
.signal-novel { color: var(--purple); }
.signal-resolved { color: var(--green); }
.signal-loop { color: var(--red); }
+883
View File
@@ -0,0 +1,883 @@
// Visual Control Plane — client (vanilla JS, D3 from CDN)
// Design note: KB data flows from local jsonl files we control, but we
// still use DOM methods (createElement/textContent) for every
// data-derived node to satisfy static analysis and keep a clean XSS
// boundary if the UI ever gets exposed.
const POLL_MS = 3000;
const state = {
view: "map",
overlay: "status",
selected: null,
services: null,
reviews: [],
metrics: [],
overrides: [],
trust: [],
findings: [],
};
// ───── view switcher ─────
document.querySelectorAll("#views button").forEach(b => {
b.addEventListener("click", () => {
document.querySelectorAll("#views button").forEach(x => x.classList.remove("on"));
b.classList.add("on");
state.view = b.dataset.view;
document.querySelectorAll(".view").forEach(v => v.classList.remove("on"));
document.getElementById(`view-${state.view}`).classList.add("on");
renderView();
});
});
document.querySelectorAll("#overlay-controls button").forEach(b => {
b.addEventListener("click", () => {
document.querySelectorAll("#overlay-controls button").forEach(x => x.classList.remove("on"));
b.classList.add("on");
state.overlay = b.dataset.ov;
if (state.services) drawMap(state.services);
});
});
// ───── helpers ─────
function el(tag, opts = {}, ...kids) {
const n = document.createElement(tag);
if (opts.className) n.className = opts.className;
if (opts.text != null) n.textContent = String(opts.text);
if (opts.data) for (const k in opts.data) n.dataset[k] = opts.data[k];
if (opts.attrs) for (const k in opts.attrs) n.setAttribute(k, opts.attrs[k]);
if (opts.style) for (const k in opts.style) n.style[k] = opts.style[k];
for (const k of kids) if (k != null) n.append(k);
return n;
}
function clear(node) { while (node.firstChild) node.removeChild(node.firstChild); }
function row(k, v, valClass) {
const r = el("div", { className: "ctx-row" });
r.append(el("span", { className: "k", text: k }));
const vv = el("span", { className: "v" + (valClass ? " " + valClass : ""), text: String(v ?? "-") });
r.append(vv);
return r;
}
function short(v) {
if (v == null) return "-";
if (typeof v === "object") return JSON.stringify(v).slice(0, 80);
return String(v).slice(0, 80);
}
// ───── polling ─────
async function poll() {
try {
const [svc, rev, met, ov, tr, fin] = await Promise.all([
fetch("/data/services").then(r => r.json()),
fetch("/data/reviews?tail=80").then(r => r.json()),
fetch("/data/metrics").then(r => r.json()),
fetch("/data/overrides").then(r => r.json()),
fetch("/data/trust").then(r => r.json()),
fetch("/data/findings").then(r => r.json()),
]);
state.services = svc;
state.reviews = Array.isArray(rev) ? rev : [];
state.metrics = Array.isArray(met) ? met : [];
state.overrides = Array.isArray(ov) ? ov : [];
state.trust = Array.isArray(tr) ? tr : [];
state.findings = Array.isArray(fin) ? fin : [];
document.getElementById("build-ts").textContent = new Date(svc.ts).toLocaleTimeString();
svc.nodes.forEach(n => {
const chip = document.querySelector(`.hbchip[data-svc="${n.id}"]`);
if (chip) chip.setAttribute("data-status", n.status);
});
renderView();
renderContext();
pollStream();
} catch (e) { console.error("poll error", e); }
}
async function pollStream() {
try {
const j = await fetch("/data/scrum_log").then(r => r.json());
if (!j.lines) return;
document.getElementById("stream-file").textContent = j.file ? j.file.split("/").pop() : "—";
const body = document.getElementById("stream-body");
clear(body);
j.lines.slice(-30).forEach(line => {
const cls = /✓ ACCEPTED/.test(line) ? "ok"
: /✗ thin/.test(line) ? "thin"
: /error|failed|FAIL/i.test(line) ? "err"
: /^\[scrum\] file:/.test(line) ? "head"
: "info";
body.append(el("div", { className: "sline " + cls, text: line }));
});
body.scrollTop = body.scrollHeight;
} catch {}
}
function renderView() {
if (!state.services) return;
if (state.view === "map") drawMap(state.services);
else if (state.view === "trace") drawTrace();
else if (state.view === "trajectory") drawTrajectory();
else if (state.view === "metrics") drawMetrics();
else if (state.view === "kb") drawKB();
else if (state.view === "console") drawConsole();
}
// ───── MAP ─────
const NODES_STATIC = [
{ id: "gateway", x: 0.5, y: 0.15 },
{ id: "sidecar", x: 0.2, y: 0.3 },
{ id: "observer", x: 0.8, y: 0.3 },
{ id: "mcp", x: 0.85, y: 0.1 },
{ id: "context7", x: 0.15, y: 0.1 },
{ id: "journal", x: 0.35, y: 0.55 },
{ id: "vectord", x: 0.5, y: 0.5 },
{ id: "playbook", x: 0.65, y: 0.55 },
{ id: "agent", x: 0.5, y: 0.75 },
{ id: "usage", x: 0.2, y: 0.75 },
];
const EDGES = [
["gateway","sidecar"],["gateway","observer"],["gateway","mcp"],["gateway","context7"],
["gateway","journal"],["gateway","vectord"],["gateway","playbook"],["gateway","agent"],["gateway","usage"],
["vectord","playbook"],["agent","vectord"],["observer","playbook"],["sidecar","vectord"],
];
function drawMap(svc) {
const svg = d3.select("#map");
const box = svg.node().getBoundingClientRect();
const W = box.width, H = box.height;
svg.selectAll("*").remove();
const statusMap = {};
[...svc.nodes, ...svc.subsystems].forEach(n => statusMap[n.id] = n);
svg.selectAll(".edge").data(EDGES).enter().append("line")
.attr("class", d => "edge" + (overlayEdgeActive(d) ? " active" : ""))
.attr("x1", d => nodePos(d[0]).x * W).attr("y1", d => nodePos(d[0]).y * H)
.attr("x2", d => nodePos(d[1]).x * W).attr("y2", d => nodePos(d[1]).y * H);
const g = svg.selectAll(".node").data(NODES_STATIC).enter().append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x * W}, ${d.y * H})`)
.on("click", (_ev, d) => { state.selected = { type:"node", id:d.id }; renderContext(); drawMap(svc); });
g.append("circle")
.attr("class", d => "node-circle" + (state.selected?.type==="node" && state.selected.id===d.id ? " node-selected" : ""))
.attr("r", d => nodeRadius(d, statusMap))
.attr("fill", d => nodeColor(d, statusMap));
// SVG tooltip — hover a node, browser shows a native tooltip with
// what this node DOES, not just its name.
g.append("title").text(d => nodeTooltip(d.id));
g.append("text").attr("class","node-label").attr("y", -30).text(d => nodeLabel(d.id));
g.append("text").attr("class","node-sub").attr("y", 40).text(d => nodeSub(d, statusMap));
}
function nodePos(id) { return NODES_STATIC.find(x => x.id === id) ?? { x:0, y:0 }; }
function nodeLabel(id) {
return ({gateway:"GATEWAY",sidecar:"SIDECAR",observer:"OBSERVER",mcp:"MCP",context7:"CTX7",
journal:"JOURNAL",vectord:"VECTORD",playbook:"PLAYBOOK",agent:"AUTOTUNE",usage:"USAGE"})[id] ?? id;
}
function nodeRadius(d, m) {
const n = m[d.id];
if (state.overlay === "activity") {
if (d.id === "journal" && n?.stats?.total_events_created != null) return 14 + Math.min(20, Math.log2(n.stats.total_events_created + 1) * 2);
if (d.id === "vectord" && n?.stats?.count != null) return 14 + Math.min(20, Math.log2(n.stats.count + 1) * 2);
if (d.id === "playbook" && n?.stats?.total != null) return 14 + Math.min(20, Math.log2(n.stats.total + 1));
if (d.id === "observer" && n?.stats?.total != null) return 14 + Math.min(20, Math.log2(n.stats.total + 1));
if (d.id === "usage" && n?.stats?.requests != null) return 14 + Math.min(20, Math.log2(n.stats.requests + 1) * 2);
}
return 18;
}
function nodeColor(d, m) {
const n = m[d.id];
const ov = state.overlay;
if (ov === "status" || ov === "activity") {
const st = n?.status ?? (n?.stats ? "healthy" : "unknown");
return { healthy:"#3eed86", degraded:"#ffbf3c", down:"#ff4d6e", unknown:"#525c6f" }[st] ?? "#525c6f";
}
if (ov === "confidence") {
const c = recentAvgConfidence(d.id);
if (c == null) return "#525c6f";
if (c >= 88) return "#3eed86";
if (c >= 70) return "#55c5ff";
if (c >= 50) return "#ffbf3c";
return "#ff4d6e";
}
if (ov === "gradient") {
const t = recentGradientTier(d.id);
return t ? ({auto:"#3eed86",dry_run:"#55c5ff",simulation:"#ffbf3c",block:"#ff4d6e"}[t] ?? "#525c6f") : "#525c6f";
}
if (ov === "verdict") {
const v = recentVerdict(d.id);
return {pass:"#3eed86",needs_patch:"#ff9f43",fail:"#ff4d6e"}[v] ?? "#525c6f";
}
return "#55c5ff";
}
function nodeSub(d, m) {
const n = m[d.id];
if (!n) return "…";
if (d.id === "journal" && n.stats) return `${n.stats.total_events_created ?? 0} events · ${n.stats.persisted_files ?? 0} parquet`;
if (d.id === "usage" && n.stats) return `${n.stats.requests ?? 0} requests · ${Math.round((n.stats.total_tokens ?? 0)/1000)}k tokens`;
if (d.id === "vectord" && typeof n.stats === "object" && n.stats) return `${n.stats.count ?? 0} indexes`;
if (d.id === "playbook" && n.stats) return `${n.stats.active ?? 0} active · ${n.stats.retired ?? 0} retired`;
if (d.id === "agent" && n.stats) return `${n.stats.trials_run ?? 0} trials · ${n.stats.promotions ?? 0} promotions`;
if (d.id === "observer" && n.stats) return `${n.stats.total ?? 0} observed ops`;
return String(n.status ?? "");
}
// Describes what each node DOES — shown as SVG <title> tooltip.
function nodeTooltip(id) {
return ({
gateway: "GATEWAY — Rust/Axum HTTP on :3100. Every external call enters here: /v1/chat, /ingest, /query, /tools, /journal, /vectors. Also hosts gRPC on :3101.",
sidecar: "SIDECAR — Python FastAPI on :3200. Adapter from Rust to local Ollama (:11434). Handles /embed /generate /rerank. Stateless.",
observer: "OBSERVER — Bun on :3800. Ring buffer of recent ops across the system. Feeds analyzeErrors + PLAYBOOK_BUILDER loops. Scrum events now land here (P45 fix).",
mcp: "MCP — Bun on :3700. Model Context Protocol tool gateway. Agent-facing tool endpoints.",
context7: "CONTEXT7 — Bun on :3900. Doc-drift resolver — checks playbook doc_refs against current docs for version drift (Phase 45 target).",
journal: "JOURNAL — ADR-012 append-only mutation log inside the gateway. Every ingest/delta-write/tombstone should record here. Currently ~1 real event (P9-001 still mostly unwired).",
vectord: "VECTORD — Embeddings store + HNSW index + autotune harness. The 'indexes' count = named vector indexes live right now (one per source × model_version).",
playbook: "PLAYBOOK — Meta-index. Each entry = a successful past pattern + geo/role + 768d embedding. Active entries boost future vector-search results (Phase 19).",
agent: "AUTOTUNE — Background agent that continuously proposes HNSW config trials, picks Pareto winners above min_recall, promotes, and rolls back. Self-tuning vector index.",
usage: "USAGE — /v1/chat token counters. Tracks requests, prompt/completion tokens, per-provider breakdown. Grows with scrum + audit traffic.",
})[id] ?? id;
}
function overlayEdgeActive(edge) {
if (!state.reviews.length) return false;
const latest = state.reviews[state.reviews.length - 1];
if (!latest?.reviewed_at) return false;
const age = Date.now() - new Date(latest.reviewed_at).getTime();
if (age > 60000) return false;
return edge.includes("gateway") && (edge.includes("observer") || edge.includes("vectord"));
}
function matchesNode(r, id) {
if (!r?.file) return false;
const f = r.file.toLowerCase();
if (id === "gateway") return f.includes("/gateway/");
if (id === "vectord") return f.includes("/vectord");
if (id === "journal") return f.includes("/journald");
if (id === "playbook")return f.includes("playbook_memory");
if (id === "sidecar") return f.includes("sidecar");
if (id === "agent") return f.includes("agent.rs") || f.includes("autotune");
return false;
}
function recentAvgConfidence(id) {
const rs = state.reviews.filter(r => matchesNode(r, id));
const vs = rs.map(r => r.confidence_avg).filter(v => v != null);
return vs.length ? vs.reduce((a,b)=>a+b,0)/vs.length : null;
}
function recentGradientTier(id) {
const rs = state.reviews.filter(r => matchesNode(r, id));
const ts = rs.map(r => r.gradient_tier).filter(Boolean);
return ts[ts.length - 1] ?? null;
}
function recentVerdict(id) {
const rs = state.reviews.filter(r => matchesNode(r, id));
const vs = rs.map(r => r.verdict).filter(Boolean);
return vs[vs.length - 1] ?? null;
}
// ───── CONTEXT ─────
function renderContext() {
const target = document.getElementById("ctx-target");
const body = document.getElementById("ctx-body");
clear(body);
if (!state.selected) {
target.textContent = "no selection";
body.append(el("div", { className: "ctx-hint", text: "Click a node or a file in KB to inspect. Context persists across view switches." }));
body.append(el("div", { className: "ctx-section-hd", text: "System totals" }));
appendSummaryKV(body);
return;
}
if (state.selected.type === "node") renderNodeContext(state.selected.id, target, body);
else if (state.selected.type === "file") renderFileContext(state.selected.id, target, body);
}
function appendSummaryKV(body) {
const s = state.services;
if (!s) { body.append(el("div", { className: "ctx-hint", text: "loading…" })); return; }
const get = id => s.nodes.concat(s.subsystems).find(n => n.id === id);
const journal = get("journal")?.stats ?? {};
const usage = get("usage")?.stats ?? {};
const playbook = get("playbook")?.stats ?? {};
const agent = get("agent")?.stats ?? {};
const observer = get("observer")?.stats ?? {};
body.append(row("scrum reviews", state.reviews.length));
body.append(row("journal events", journal.total_events_created ?? 0));
body.append(row("usage tokens", (usage.total_tokens ?? 0).toLocaleString()));
body.append(row("playbook active", playbook.active ?? 0));
body.append(row("autotune trials", agent.trials_run ?? 0));
body.append(row("observer ops", observer.total ?? 0));
body.append(row("findings (h/m/l)", `${countFindingsSev("high")}/${countFindingsSev("medium")}/${countFindingsSev("low")}`));
}
function countFindingsSev(sev) {
let n = 0;
for (const row of state.findings) for (const f of row.findings ?? []) if (f.severity === sev) n++;
return n;
}
function renderNodeContext(id, target, body) {
target.textContent = `NODE · ${id.toUpperCase()}`;
const n = [...state.services.nodes, ...state.services.subsystems].find(x => x.id === id);
if (n?.health) {
body.append(el("div", { className: "ctx-section-hd", text: "Health" }));
// Fix 2026-04-24: some /health endpoints return a plain string like
// "lakehouse ok". Don't Object.entries() on strings — that iterates
// characters. Detect primitive vs object explicitly.
if (typeof n.health === "string" || typeof n.health === "number" || typeof n.health === "boolean") {
body.append(row("response", String(n.health).slice(0, 80)));
} else if (typeof n.health === "object" && n.health !== null) {
Object.entries(n.health).slice(0, 8).forEach(([k,v]) => body.append(row(k, short(v))));
}
}
if (n?.stats) {
body.append(el("div", { className: "ctx-section-hd", text: "Stats" }));
if (typeof n.stats === "string") {
body.append(row("raw", String(n.stats).slice(0, 80)));
} else if (typeof n.stats === "object" && n.stats !== null) {
Object.entries(n.stats).slice(0, 10).forEach(([k,v]) => body.append(row(k, short(v))));
}
}
const related = state.reviews.filter(r => matchesNode(r, id)).slice(-5).reverse();
if (related.length) {
body.append(el("div", { className: "ctx-section-hd", text: "Recent reviews" }));
related.forEach(r => {
const rr = row(r.file.split("/").pop(), `${r.confidence_avg ?? "-"}% · ${r.alignment_score ?? "?"}/10`);
rr.style.cursor = "pointer";
rr.addEventListener("click", () => { state.selected = { type:"file", id:r.file }; renderContext(); });
body.append(rr);
});
}
if (!body.firstChild) body.append(el("div", { className: "ctx-hint", text: "no data yet" }));
}
function renderFileContext(fpath, target, body) {
target.textContent = fpath.split("/").slice(-3).join("/");
const fileReviews = state.reviews.filter(r => r.file === fpath).slice(-6);
if (!fileReviews.length) {
body.append(el("div", { className: "ctx-hint", text: `no reviews for ${fpath}` }));
return;
}
const latest = fileReviews[fileReviews.length - 1];
const pillRow = el("div", { style: { paddingBottom: "6px" } });
if (latest.gradient_tier) pillRow.append(el("span", { className: `pill tier-${latest.gradient_tier}`, text: latest.gradient_tier }));
if (latest.verdict) pillRow.append(el("span", { className: `pill ver-${latest.verdict}`, text: latest.verdict }));
if (latest.output_format) pillRow.append(el("span", { className: `pill fmt-${latest.output_format}`, text: latest.output_format }));
body.append(pillRow);
const rows = [
["file", fpath],
["score", latest.alignment_score != null ? `${latest.alignment_score}/10` : "-"],
["conf avg", latest.confidence_avg != null ? `${latest.confidence_avg}%` : "-"],
["conf min", latest.confidence_min != null ? `${latest.confidence_min}%` : "-"],
["findings", latest.findings_count ?? 0],
["critical", latest.critical_failures_count ?? 0],
["verified", latest.verified_components_count ?? 0],
["missing", latest.missing_components_count ?? 0],
["model", latest.accepted_model ?? "-"],
["attempts", latest.attempts_made ?? 1],
["tree split", latest.tree_split_fired ? "yes" : "no"],
];
rows.forEach(([k,v]) => body.append(row(k, short(v))));
body.append(el("div", { className: "ctx-section-hd", text: "Score history" }));
fileReviews.forEach(r => body.append(row(new Date(r.reviewed_at).toLocaleTimeString(), `${r.alignment_score ?? "?"}/10 · ${r.confidence_avg ?? "-"}%`)));
body.append(el("div", { className: "ctx-section-hd", text: "Preview" }));
const pre = el("pre", { text: latest.suggestions_preview ?? "", style: { whiteSpace: "pre-wrap", fontFamily: "var(--mono)", fontSize: "10px", color: "var(--fg-dim)", maxHeight: "200px", overflowY: "auto" } });
body.append(pre);
document.getElementById("stream-file").textContent = fpath.split("/").pop();
}
// ───── TRACE ─────
async function drawTrace() {
const fpath = state.selected?.type === "file" ? state.selected.id : state.reviews[state.reviews.length-1]?.file;
const tl = document.getElementById("trace-timeline");
const detail = document.getElementById("trace-detail");
clear(tl); clear(detail);
document.getElementById("trace-file").textContent = fpath ?? "—";
if (!fpath) { tl.append(el("div", { className: "ctx-hint", text: "no file selected — pick one in KB view" })); return; }
const r = await fetch(`/data/file/${encodeURIComponent(fpath)}`).then(r => r.json());
const history = r.history ?? [];
document.getElementById("trace-runs").textContent = `${history.length} runs`;
history.forEach((h, i) => {
const node = el("div", { className: "trace-node" + (i === history.length - 1 ? " active" : "") });
node.append(el("div", { className: "tn-run", text: h.run_id }));
node.append(el("div", { className: "tn-score", text: h.score != null ? String(h.score) : "?" }));
node.append(el("div", { className: "tn-conf", text: `conf ${h.conf_avg ?? "-"}% · ${h.findings}f` }));
node.append(el("div", { className: "tn-model", text: (h.model ?? "").split("/").pop() }));
node.addEventListener("click", () => {
tl.querySelectorAll(".trace-node").forEach(x => x.classList.remove("active"));
node.classList.add("active");
clear(detail);
detail.append(el("pre", { text: h.preview ?? "" }));
});
tl.append(node);
});
if (history.length) { clear(detail); detail.append(el("pre", { text: history[history.length-1].preview ?? "" })); }
}
// ───── TRAJECTORY — refactor signals + reverse index + per-file delta ─────
let trajectorySearchTimer = null;
document.getElementById("traj-search")?.addEventListener("input", (e) => {
const q = e.target.value.trim();
clearTimeout(trajectorySearchTimer);
trajectorySearchTimer = setTimeout(() => runReverseIndex(q), 300);
});
async function runReverseIndex(query) {
const body = document.getElementById("traj-body");
if (!query) { drawTrajectory(); return; }
clear(body);
const res = await fetch(`/data/search?q=${encodeURIComponent(query)}`).then(r => r.json());
const hdr = el("div", { className: "traj-section-head", text: `REVERSE INDEX · "${query}" · ${res.hits?.length ?? 0} hits` });
body.append(hdr);
(res.hits ?? []).forEach(h => {
const card = el("div", { className: "traj-hit" });
card.append(el("div", { className: "traj-hit-top" },
el("span", { className: "traj-hit-file", text: h.file }),
el("span", { className: "traj-hit-meta", text: `${h.run_id} · ${(h.model ?? "").split("/").pop()}` })
));
card.append(el("div", { className: "traj-hit-snip", text: h.snippet }));
card.addEventListener("click", () => {
state.selected = { type: "file", id: `/home/profit/lakehouse/${h.file}` };
renderContext();
document.querySelector('#views button[data-view="trace"]').click();
});
body.append(card);
});
}
async function drawTrajectory() {
const body = document.getElementById("traj-body");
clear(body);
const statsEl = document.getElementById("traj-stats");
clear(statsEl);
// SECTION 0 — signal classes (CONVERGING/LOOPING/ORBITING/PLATEAU/MIXED)
try {
const sc = await fetch("/data/signal_classes").then(r => r.json());
body.append(el("div", { className: "traj-section-head", text: "SIGNAL CLASSES · iter-to-iter behavior per file" }));
body.append(el("div", { className: "traj-section-explain", text:
"Each file compared iter-to-iter: CONVERGING = fix landed (resolved > novel + score↑), " +
"LOOPING = same findings repeating (deadlock candidate for hyper-focus), " +
"ORBITING = novel findings every iter (healthy depth-first), " +
"PLATEAU = score+findings flat (diminishing returns, needs different angle), " +
"MIXED = partial movement, NEW = only 1 iter so far."
}));
const classRow = el("div", { className: "signal-class-row" });
for (const [cls, n] of Object.entries(sc.counts ?? {})) {
const chip = el("span", { className: `signal-chip signal-${cls.toLowerCase()}`, text: `${cls} ${n}` });
classRow.append(chip);
}
body.append(classRow);
const grid = el("div", { className: "signal-grid" });
const sorted = Object.entries(sc.classes ?? {}).sort((a, b) => {
const order = { CONVERGING: 0, LOOPING: 1, ORBITING: 2, MIXED: 3, PLATEAU: 4, NEW: 5 };
return (order[a[1].cls] ?? 9) - (order[b[1].cls] ?? 9);
});
for (const [file, info] of sorted) {
const card = el("div", { className: `signal-card signal-${info.cls.toLowerCase()}` });
card.append(el("div", { className: "signal-card-top" },
el("span", { className: `signal-chip signal-${info.cls.toLowerCase()}`, text: info.cls }),
el("span", { className: "signal-card-file", text: file })
));
const body2 = el("div", { className: "signal-card-body" });
if (info.prev_score != null || info.last_score != null) {
body2.append(el("div", { text: `score ${info.prev_score ?? "?"} → ${info.last_score ?? "?"} (Δ ${info.delta_score != null ? (info.delta_score > 0 ? "+" : "") + info.delta_score.toFixed(1) : "?"})` }));
}
if (info.novel?.length) body2.append(el("div", { className: "signal-novel", text: `NEW: ${info.novel.join(", ")}` }));
if (info.resolved?.length) body2.append(el("div", { className: "signal-resolved", text: `RESOLVED: ${info.resolved.join(", ")}` }));
if (info.looping?.length) body2.append(el("div", { className: "signal-loop", text: `LOOPING: ${info.looping.join(", ")}` }));
card.append(body2);
card.addEventListener("click", () => {
state.selected = { type: "file", id: `/home/profit/lakehouse/${file}` };
renderContext();
document.querySelector('#views button[data-view="trace"]').click();
});
grid.append(card);
}
body.append(grid);
} catch (e) {
body.append(el("div", { className: "ctx-hint", text: `signal classes error: ${e}` }));
}
// SECTION 1 — refactor signals
const sig = await fetch("/data/refactor_signals").then(r => r.json());
const sigs = sig.signals ?? [];
const totalHits = sigs.reduce((a,s) => a + s.hits, 0);
statsEl.textContent = `${sig.scanned ?? 0} files scanned · ${sigs.length} with refactor hints · ${totalHits} phrase hits total`;
const sigHead = el("div", { className: "traj-section-head", text: "REFACTOR SIGNALS · files the scrum repeatedly flagged as dead / redundant / stub / needs-rewrite" });
body.append(sigHead);
const explain = el("div", { className: "traj-section-explain", text:
"Aggregates across all scrum iterations. A phrase hit = one time the reviewer used language like 'remove', 'duplicate', 'refactor', 'pseudocode', 'orphaned'. " +
"Files near the top are the strongest refactor candidates — the scrum keeps calling them out. Click a row to jump to its per-iteration trace."
});
body.append(explain);
const table = el("div", { className: "traj-table" });
sigs.slice(0, 30).forEach(s => {
const r = el("div", { className: "traj-row" });
r.append(el("div", { className: "traj-col-rank", text: String(sigs.indexOf(s) + 1) }));
r.append(el("div", { className: "traj-col-file", text: s.file }));
r.append(el("div", { className: "traj-col-hits", text: `${s.hits}×` }));
const topPhrases = Object.entries(s.phrases).sort((a,b)=>b[1]-a[1]).slice(0,3)
.map(([p,n]) => `${p} (${n})`).join(", ");
r.append(el("div", { className: "traj-col-phrases", text: topPhrases }));
r.append(el("div", { className: "traj-col-iters", text: `${s.iterations} iter` }));
r.addEventListener("click", () => {
state.selected = { type: "file", id: `/home/profit/lakehouse/${s.file}` };
renderContext();
document.querySelector('#views button[data-view="trace"]').click();
});
table.append(r);
});
body.append(table);
// SECTION 2 — per-file trajectory: pick the top-5 refactor candidates and
// show their score/conf delta across iterations inline.
if (sigs.length) {
body.append(el("div", { className: "traj-section-head", text: "SCORE TRAJECTORY — top refactor candidates" }));
const grid = el("div", { className: "traj-spark-grid" });
for (const s of sigs.slice(0, 6)) {
const card = el("div", { className: "traj-spark" });
card.append(el("div", { className: "traj-spark-file", text: s.file }));
// pull history
const hist = await fetch(`/data/file/${encodeURIComponent("/home/profit/lakehouse/" + s.file)}`)
.then(r => r.json()).catch(() => ({ history: [] }));
const runs = hist.history ?? [];
if (runs.length === 0) { card.append(el("div", { className: "traj-spark-empty", text: "no history" })); }
else {
const line = el("div", { className: "traj-spark-line" });
runs.forEach((h,i) => {
const pt = el("div", { className: "traj-spark-pt" });
pt.append(el("div", { className: "traj-pt-score", text: h.score != null ? `${h.score}/10` : "?" }));
pt.append(el("div", { className: "traj-pt-conf", text: `${h.conf_avg ?? "-"}%` }));
pt.append(el("div", { className: "traj-pt-label", text: `iter${i+1}` }));
line.append(pt);
if (i < runs.length - 1) line.append(el("div", { className: "traj-spark-arrow", text: "→" }));
});
card.append(line);
// delta summary
if (runs.length >= 2) {
const first = runs[0], last = runs[runs.length - 1];
const dScore = (last.score != null && first.score != null) ? (last.score - first.score) : null;
const dConf = (last.conf_avg != null && first.conf_avg != null) ? (last.conf_avg - first.conf_avg) : null;
const delta = el("div", { className: "traj-spark-delta" });
if (dScore != null) delta.append(el("span", { text: `Δscore ${dScore > 0 ? "+" : ""}${dScore.toFixed(1)}`, className: dScore < 0 ? "delta-down" : dScore > 0 ? "delta-up" : "" }));
if (dConf != null) delta.append(el("span", { text: ` · Δconf ${dConf > 0 ? "+" : ""}${dConf}%`, className: dConf > 0 ? "delta-up" : dConf < 0 ? "delta-down" : "" }));
card.append(delta);
}
}
card.addEventListener("click", () => {
state.selected = { type: "file", id: `/home/profit/lakehouse/${s.file}` };
renderContext();
document.querySelector('#views button[data-view="trace"]').click();
});
grid.append(card);
}
body.append(grid);
}
}
// ───── METRICS ─────
function metricBox(label, big, kind, opts = {}) {
// opts: { source, good, explain }
// source = where the number comes from (data path)
// good = the "what's a healthy value" sentence
// explain = one-line definition of what this counts
const box = el("div", { className: "metric" + (kind ? " " + kind : "") });
box.append(el("div", { className: "m-label", text: label }));
box.append(el("div", { className: "m-big", text: big }));
if (opts.explain) box.append(el("div", { className: "m-sub m-explain", text: opts.explain }));
if (opts.source) box.append(el("div", { className: "m-sub m-source", text: "SOURCE · " + opts.source }));
if (opts.good) box.append(el("div", { className: "m-sub m-good", text: "GOOD · " + opts.good }));
return box;
}
function drawMetrics() {
const grid = document.getElementById("metric-grid");
clear(grid);
// Kick off pathway fetch in parallel; render when it resolves so the
// rest of the metrics grid appears immediately. The cards append to
// the grid after the synchronous block below — they'll show up at
// the bottom of the grid within a tick of first render.
fetch("/data/pathway_stats").then(r => r.ok ? r.json() : null).then(j => {
if (!j || !j.stats) return;
const s = j.stats;
const w = j.scrum_window ?? {};
// Activity metric — is the hot-swap firing at all?
grid.append(metricBox("pathway reuse rate", `${Math.round((w.pathway_reuse_rate ?? 0) * 100)}%`,
(w.pathway_reuse_rate ?? 0) > 0.1 ? "good" : (w.pathway_reuse_rate ?? 0) > 0 ? "warn" : "bad", {
explain: "% of recent reviews where a pathway hot-swap fired (narrow fingerprint match + 0.80 success rate + ≥3 replays + audit_consensus pass + 0.90 similarity).",
source: `scrum_reviews.jsonl .pathway_hot_swap_hit over last ${w.reviews ?? 0} reviews (${w.hot_swap_hits ?? 0} hits)`,
good: "≥10% sustained = index earning its keep. <10% over many iters = fingerprint too narrow or probation too strict. 0% on fresh install is expected (no replays yet).",
}));
// Value metric — how much compute did hot-swap actually save?
const saved = w.avg_rungs_saved_per_commit ?? 0;
grid.append(metricBox("avg rungs saved", saved.toFixed(2),
saved >= 1 ? "good" : saved > 0 ? "warn" : "bad", {
explain: "Average ladder rungs skipped per committed review by hot-swap. Rungs_saved = recommended_rung - 1 when the recommended model succeeded (otherwise 0).",
source: "scrum_reviews.jsonl .rungs_saved averaged",
good: "Every 1.0 here ≈ one less model call per review. At 21 files/iter, 1.0 saved = 21 cloud calls avoided. Value only counts when the replay actually succeeded.",
}));
// Stability metric — retired pathways indicate the learning loop is correcting itself.
grid.append(metricBox("pathways tracked", String(s.total_pathways),
s.total_pathways > 0 ? "good" : "warn", {
explain: `Total pathway traces stored. ${s.retired} retired (below 0.80 success after ≥3 replays). ${s.with_audit_pass} audit-passed, eligible for hot-swap probation.`,
source: "/vectors/pathway/stats",
good: `Grows monotonically with scrum runs. Retired=${s.retired} is HEALTHY — it means the learning loop is pruning pathways that stopped working. replay_success_rate=${(s.replay_success_rate*100).toFixed(0)}% aggregates all historical replays.`,
}));
}).catch(() => {});
const byTier = { auto:0, dry_run:0, simulation:0, block:0, unknown:0 };
state.reviews.forEach(r => { const t = r.gradient_tier ?? "unknown"; if (byTier[t] != null) byTier[t]++; });
const total = state.reviews.length || 1;
const confRows = state.reviews.filter(r => r.confidence_avg != null);
const avg = confRows.length ? Math.round(confRows.reduce((a,r)=>a+r.confidence_avg,0)/confRows.length) : 0;
const verdictCount = { pass:0, needs_patch:0, fail:0, unknown:0 };
state.reviews.forEach(r => { const v=r.verdict??"unknown"; if(verdictCount[v]!=null) verdictCount[v]++; });
const findingsTotal = state.reviews.reduce((a,r)=>a+(r.findings_count??0),0);
const critTotal = state.reviews.reduce((a,r)=>a+(r.critical_failures_count??0),0);
const verTotal = state.reviews.reduce((a,r)=>a+(r.verified_components_count??0),0);
const usage = state.services?.subsystems?.find(n=>n.id==="usage")?.stats ?? {};
const journal = state.services?.subsystems?.find(n=>n.id==="journal")?.stats ?? {};
grid.append(metricBox("avg confidence", `${avg}%`, avg>=85?"good":avg>=70?"warn":"bad", {
explain: "Self-assessed probability per suggestion, averaged across every review.",
source: "scrum_reviews.jsonl .confidence_avg",
good: "≥85% — model is confident. 70-84% routine. <70% means the scrum is uncertain and findings need human review.",
}));
grid.append(metricBox("scrum reviews", String(state.reviews.length), "good", {
explain: "Every source file reviewed by the scrum master, across all iterations.",
source: `${state.metrics.length} scrum runs tracked in scrum_loop_metrics.jsonl`,
good: "Grows every run — 21 files × N iterations. Stall = pipeline broken.",
}));
grid.append(metricBox("critical failures", String(critTotal), critTotal>50?"bad":critTotal>10?"warn":"good", {
explain: "Hard FAILs flagged by the forensic reviewer — pseudocode, fake implementations, unwired invariants. Each one is a concrete code-level gap.",
source: "scrum_reviews.jsonl .critical_failures_count (forensic JSON format only)",
good: "Trending DOWN each iteration = fixes are landing. Rising = new gaps surfacing faster than we close them.",
}));
grid.append(metricBox("verified components", String(verTotal), verTotal>0?"good":"warn", {
explain: "What the scrum CONFIRMED is working — with file/line evidence. The inverse of critical_failures.",
source: "scrum_reviews.jsonl .verified_components_count",
good: "Trending UP = the system has more provably-real parts over time. Should grow as fixes land.",
}));
grid.append(metricBox("findings captured", String(findingsTotal), "good", {
explain: "Total individual suggestions the scrum produced across all reviews (tables + JSON).",
source: "scrum_reviews.jsonl .findings_count summed",
good: "Higher = more scrutiny per file. Per-file average ≥10 means the review is substantive.",
}));
grid.append(metricBox("journal events", String(journal.total_events_created ?? 0), "good", {
explain: "Mutation events recorded via ADR-012 append-only journal. Every ingest/delta-write should emit one.",
source: "/journal/stats → total_events_created",
good: "Should grow with ingest traffic. 1 = only a test probe fired; internal callers still unwired on most paths (P9-001).",
}));
grid.append(metricBox("v1 requests", String(usage.requests ?? 0), "good", {
explain: "Calls through the Universal API /v1/chat endpoint (Phase 38). Captures all scrum + audit traffic.",
source: `/v1/usage → requests. ${(usage.total_tokens ?? 0).toLocaleString()} tokens total`,
good: "Every iteration adds ~21 requests. Stall = scrum paused OR callers bypassing the gateway (P44-style bypass).",
}));
// gradient bar
const gb = el("div", { className: "metric" });
gb.append(el("div", { className: "m-label", text: "permission gradient" }));
gb.append(el("div", { className: "m-big", text: String(state.reviews.length) }));
gb.append(el("div", { className: "m-sub m-explain", text: "Tiers the scrum's suggestions by confidence: how much auto-apply we can trust per file." }));
const bar = el("div", { className: "bar" });
bar.append(el("span", { className: "seg-auto", style: { width: `${100*byTier.auto/total}%` } }));
bar.append(el("span", { className: "seg-dry_run", style: { width: `${100*byTier.dry_run/total}%` } }));
bar.append(el("span", { className: "seg-simulation", style: { width: `${100*byTier.simulation/total}%` } }));
bar.append(el("span", { className: "seg-block", style: { width: `${100*byTier.block/total}%` } }));
gb.append(bar);
gb.append(el("div", { className: "m-sub", text: `auto ${byTier.auto} · dry_run ${byTier.dry_run} · sim ${byTier.simulation} · block ${byTier.block}` }));
gb.append(el("div", { className: "m-sub m-good", text:
"AUTO (≥90%): ship the suggestion. DRY_RUN (70-89): apply then diff. SIMULATION (50-69): test first. BLOCK (<50): human review — the model doesn't trust itself."
}));
grid.append(gb);
const vb = el("div", { className: "metric" });
vb.append(el("div", { className: "m-label", text: "verdict distribution" }));
vb.append(el("div", { className: "m-big", text: String(verdictCount.pass + verdictCount.needs_patch + verdictCount.fail) }));
vb.append(el("div", { className: "m-sub m-explain", text: "Forensic audit verdict per file: pass = works, needs_patch = fixable gaps, fail = not trustable." }));
vb.append(el("div", { className: "m-sub", text: `pass ${verdictCount.pass} · needs_patch ${verdictCount.needs_patch} · fail ${verdictCount.fail}` }));
vb.append(el("div", { className: "m-sub m-source", text: "SOURCE · scrum_reviews.jsonl .verdict (forensic JSON only — markdown rows count as unknown)" }));
grid.append(vb);
}
// ───── KB ─────
function drawKB() {
const grid = document.getElementById("kb-grid");
clear(grid);
// Explanatory banner — each iteration the scrum re-reviews every
// target file and writes a row here. A card = one file's latest
// review. Click to drill into its trace across all iterations.
const banner = el("div", { className: "kb-banner" });
banner.append(el("div", { className: "kb-banner-title", text: "KNOWLEDGE BASE — every source file reviewed by the scrum master" }));
banner.append(el("div", { className: "kb-banner-body", text:
"Each card below is the LATEST scrum review of one source file. The review itself lives in data/_kb/scrum_reviews.jsonl. " +
"Fields: score (scrum's alignment rating, 1-10 vs PRD intent), conf (model's self-assessed confidence per suggestion, avg'd), " +
"findings (# of suggestions), crit (critical_failures — hard FAILs found), verified (verified_components — what's confirmed working). " +
"Pills show: permission gradient (can we trust auto-apply), verdict (pass/needs_patch/fail), output format (JSON = forensic, markdown = legacy). " +
"Click a card to see its trace across all iterations (iter 1 → iter N) and watch scores trend."
}));
grid.append(banner);
const byFile = new Map();
state.reviews.forEach(r => { if (r.file) byFile.set(r.file, r); });
const rows = [...byFile.values()].sort((a,b) => (b.confidence_avg??0) - (a.confidence_avg??0));
// Quick stats above the cards
const statLine = el("div", { className: "kb-statline" });
const avgConf = rows.length ? Math.round(rows.reduce((a,r)=>a+(r.confidence_avg??0),0) / rows.length) : 0;
const scoreMean = rows.filter(r=>r.alignment_score!=null);
const avgScore = scoreMean.length ? (scoreMean.reduce((a,r)=>a+r.alignment_score,0) / scoreMean.length).toFixed(1) : "?";
const blockCount = rows.filter(r => r.gradient_tier === "block").length;
statLine.append(el("span", { text: `${rows.length} files tracked` }));
statLine.append(el("span", { text: `mean score ${avgScore}/10` }));
statLine.append(el("span", { text: `mean confidence ${avgConf}%` }));
statLine.append(el("span", { text: `${blockCount} blocked (need human review)`, className: blockCount > 0 ? "stat-warn" : "" }));
grid.append(statLine);
rows.forEach(r => {
const card = el("div", { className: "kb-file", data: { file: r.file } });
card.append(el("div", { className: "kf-path", text: r.file }));
const meta = el("div", { className: "kf-meta" });
const scoreSpan = el("span", { className: "kf-score", text: `${r.alignment_score ?? "?"}/10` });
scoreSpan.title = "Scrum's alignment score (1-10) — how well this file matches PRD intent. Lower = more gaps.";
meta.append(scoreSpan);
const confSpan = el("span", { text: `conf ${r.confidence_avg ?? "-"}%` });
confSpan.title = "Average self-confidence across suggestions. <70% = model uncertain, treat carefully.";
meta.append(confSpan);
const findingsSpan = el("span", { text: `${r.findings_count ?? 0} findings` });
findingsSpan.title = "Total suggestions in this review (table rows or JSON array entries).";
meta.append(findingsSpan);
const critSpan = el("span", { text: `${r.critical_failures_count ?? 0} crit` });
critSpan.title = "Critical failures: pseudocode, fake implementations, unwired invariants. Hard FAILs.";
if ((r.critical_failures_count ?? 0) > 0) critSpan.style.color = "var(--red)";
meta.append(critSpan);
const verSpan = el("span", { text: `${r.verified_components_count ?? 0} verified` });
verSpan.title = "Verified components: things the scrum CONFIRMED work, with file/line evidence.";
if ((r.verified_components_count ?? 0) > 0) verSpan.style.color = "var(--green)";
meta.append(verSpan);
meta.append(el("span", { text: (r.accepted_model ?? "").split("/").pop(), attrs: { title: "Which model produced this review" } }));
card.append(meta);
const pills = el("div", { className: "kf-meta" });
if (r.gradient_tier) {
const p = el("span", { className: `pill tier-${r.gradient_tier}`, text: r.gradient_tier });
p.title = ({
auto: "AUTO — confidence ≥90%, suggestions safe to apply automatically",
dry_run: "DRY_RUN — confidence 70-89%, apply then review the diff",
simulation: "SIMULATION — confidence 50-69%, test in sandbox first",
block: "BLOCK — confidence <50%, requires human review, do not auto-apply",
})[r.gradient_tier] ?? r.gradient_tier;
pills.append(p);
}
if (r.verdict) {
const p = el("span", { className: `pill ver-${r.verdict}`, text: r.verdict });
p.title = ({
pass: "PASS — scrum confirms this file meets its PRD intent",
needs_patch: "NEEDS_PATCH — gaps exist but are fixable; scrum has concrete suggestions",
fail: "FAIL — file cannot be trusted for its claimed purpose without structural changes",
})[r.verdict] ?? r.verdict;
pills.append(p);
}
if (r.output_format) {
const p = el("span", { className: `pill fmt-${r.output_format}`, text: r.output_format });
p.title = r.output_format === "forensic_json"
? "FORENSIC_JSON — structured output with verdict/critical/verified/missing fields. Richer signal."
: "MARKDOWN — legacy tabular output. Lower structure; we only extract confidence scalars from these.";
pills.append(p);
}
card.append(pills);
card.addEventListener("click", () => {
state.selected = { type: "file", id: r.file };
renderContext();
document.querySelector('#views button[data-view="trace"]').click();
});
grid.append(card);
});
}
// ───── CONSOLE ─────
// Persistent selection across polls so tab switches survive.
state.consoleSvc = "gateway";
// Hook tab buttons once
document.querySelectorAll("#con-tabs button").forEach(b => {
b.addEventListener("click", () => {
document.querySelectorAll("#con-tabs button").forEach(x => x.classList.remove("on"));
b.classList.add("on");
state.consoleSvc = b.dataset.svc;
drawConsole();
});
});
async function drawConsole() {
const log = document.getElementById("console-log");
clear(log);
const unit = document.getElementById("con-unit");
if (unit) unit.textContent = "";
if (state.consoleSvc === "summary") {
drawConsoleSummary(log);
return;
}
// Per-service log tail
const svc = state.consoleSvc;
try {
const res = await fetch(`/data/logs/${svc}?n=120`).then(r => r.json());
if (unit && res.unit) unit.textContent = `unit · ${res.unit}`;
if (res.error) {
log.append(lineInfo(`[error] ${res.error}`, "cl-err"));
return;
}
const lines = res.lines ?? [];
if (!lines.length) { log.append(lineInfo("(no log lines — unit may have just started)", "cl-info")); return; }
lines.forEach(l => {
const cls = /\berror\b|\bERROR\b|panic|\[ERROR|failed/.test(l) ? "cl-err"
: /\bwarn\b|\bWARN\b|\bwarning\b|\[WARN/.test(l) ? "cl-warn"
: /\baccepted\b|\bok\b|\bOK\b|success|complete|ready/.test(l) ? "cl-ok"
: "cl-info";
log.append(lineInfo(l, cls));
});
// autoscroll to bottom
log.scrollTop = log.scrollHeight;
} catch (e) {
log.append(lineInfo(`[fetch-error] ${e}`, "cl-err"));
}
}
function lineInfo(text, cls) {
return el("div", { className: "cl-line " + cls, text });
}
function drawConsoleSummary(log) {
const info = t => lineInfo(t, "cl-info");
const ok = t => lineInfo(t, "cl-ok");
const warn = t => lineInfo(t, "cl-warn");
const err = t => lineInfo(t, "cl-err");
log.append(info(`# Lakehouse VCP · ${new Date().toLocaleTimeString()}`));
log.append(info(`# Services`));
for (const n of state.services?.nodes ?? []) {
const line = `[${String(n.status).padEnd(8)}] ${n.label}`;
log.append(n.status === "healthy" ? ok(line) : n.status === "down" ? err(line) : warn(line));
}
log.append(info(`# Subsystems`));
for (const s of state.services?.subsystems ?? []) {
log.append(info(` ${String(s.id).padEnd(10)} ${JSON.stringify(s.stats ?? {}).slice(0, 120)}`));
}
log.append(info(`# Recent overrides (layer 10)`));
for (const o of state.overrides.slice(-6)) {
log.append(warn(` [${o.ts}] ${o.task_signature}: ${o.human_fix}`));
}
log.append(info(`# Model trust accumulated`));
const agg = {};
for (const t of state.trust) {
const k = t.accepted_model ?? "?";
agg[k] = agg[k] ?? { accepts:0, thin:0, attempts:0 };
agg[k].accepts++;
agg[k].thin += t.thin_rejections ?? 0;
agg[k].attempts += t.attempts_made ?? 0;
}
for (const [m, s] of Object.entries(agg)) {
log.append(info(` ${String(m).padEnd(48)} accepts=${s.accepts} thin=${s.thin} attempts=${s.attempts}`));
}
}
// ───── boot ─────
poll();
setInterval(poll, POLL_MS);
window.addEventListener("resize", () => { if (state.services && state.view === "map") drawMap(state.services); });