Spaces:
Running
Running
feat(mesh): live 45-kernel observability — per-kernel status colours (green/amber/red from /api/<organ>/v3/kernels), heartbeat pulse on each dot, last-5-heartbeats in kernel side panel, 45-kernel signed-receipt ticker. ADDITIVE. Doctrine v11 749/14/163. Sign: Yachay
320c18f verified | // Codex panel — click any chakra -> its codex (recipes/theses/formulas) opens in | |
| // the side panel. Click a kernel dot -> that kernel's docs + recent live activity. | |
| // Top-right "All Codices" -> unified search across all 5 chakras' codices. | |
| // Doctrine v11 LOCKED. ZERO BANDAID — links are real; live activity shows honest state. | |
| import { CHAKRAS, UNIVERSAL_KERNELS } from './config.js'; | |
| import { kernelEntry, lastHeartbeats } from './heartbeats.js'; | |
| let panel, body, closeBtn; | |
| export function initCodex(){ | |
| panel = document.getElementById('panel'); | |
| body = document.getElementById('panelBody'); | |
| closeBtn = document.getElementById('panelClose'); | |
| closeBtn.onclick = closePanel; | |
| document.getElementById('allCodices').onclick = openAllCodices; | |
| addEventListener('keydown', e => { if (e.key==='Escape') closePanel(); }); | |
| } | |
| function openPanel(){ panel.classList.add('open'); } | |
| function closePanel(){ panel.classList.remove('open'); } | |
| function chakraById(id){ return CHAKRAS.find(c => c.id===id); } | |
| // ---------- chakra codex ---------- | |
| export function openChakraCodex(id){ | |
| const c = chakraById(id); if (!c) return; | |
| const col = '#'+c.color.toString(16).padStart(6,'0'); | |
| const kernels = UNIVERSAL_KERNELS.map(k => `<span class="ktag">${k.name}</span>`).join('') | |
| + c.vertical.map(k => `<span class="ktag vert">${k.name}</span>`).join(''); | |
| body.innerHTML = ` | |
| <h2 style="color:${col}">${c.label}</h2> | |
| <div class="sub">${c.glyph} · 9 kernels (7 universal + 2 vertical)</div> | |
| <h3>Codex</h3> | |
| ${c.codex.map(x => `<a class="link" href="${x.href}" target="_blank" rel="noopener">${x.label}</a>`).join('')} | |
| <h3>Kernels (wheel)</h3> | |
| <div id="kernelList">${kernels}</div> | |
| <h3>Live</h3> | |
| <div class="kv" id="liveBox">polling ${c.label}…</div> | |
| `; | |
| openPanel(); | |
| pollChakraLive(c); | |
| } | |
| async function pollChakraLive(c){ | |
| const boxEl = document.getElementById('liveBox'); | |
| try { | |
| const r = await fetch(c.health.url, { cache:'no-store' }); | |
| if (!r.ok){ boxEl.textContent = `health → HTTP ${r.status}`; return; } | |
| let j; try { j = await r.json(); } catch(e){ boxEl.textContent='health → 200 (non-JSON)'; return; } | |
| const bits = []; | |
| if (j.status) bits.push(`status: <b>${j.status}</b>`); | |
| if (j.version) bits.push(`version: ${j.version}`); | |
| if (j.doctrine) bits.push(`doctrine: ${j.doctrine}`); | |
| if (j.numbers) bits.push(`numbers: ${j.numbers.declarations}/${j.numbers.axioms}/${j.numbers.sorries}`); | |
| else if (j.declarations!==undefined) bits.push(`decl: ${j.declarations} · sorries: ${j.sorries}`); | |
| if (j.gates) bits.push(`gates: ${j.gates}`); | |
| if (j.traceparent_propagating) bits.push('traceparent: LIVE'); | |
| boxEl.innerHTML = bits.length ? bits.join('<br>') : '200 OK (no detail fields)'; | |
| } catch(e){ boxEl.textContent = 'flagship unreachable (honest offline state)'; } | |
| } | |
| // ---------- kernel panel ---------- | |
| const STATUS_LABEL = { green:'● live', amber:'● degraded', red:'● offline' }; | |
| const STATUS_HEX = { green:'#34d399', amber:'#fbbf24', red:'#f87171' }; | |
| export function openKernelPanel(c, kernel, vertical){ | |
| const col = '#'+c.color.toString(16).padStart(6,'0'); | |
| const sub = vertical ? 'vertical kernel' : `universal kernel · substrate: ${kernel.substrate}`; | |
| const e = kernelEntry(c.id, kernel.name); | |
| const st = e ? e.status : 'red'; | |
| const stHex = STATUS_HEX[st] || '#6b7280'; | |
| const ago = (e && e.agoSec != null) ? `${e.agoSec}s ago` : '—'; | |
| const ticks = (e && e.ticks != null) ? e.ticks.toLocaleString() : '—'; | |
| const endpoint = `${c.base}/api/${c.id}/v3/kernels/${kernel.name.toLowerCase()}`; | |
| body.innerHTML = ` | |
| <h2 style="color:${col}">${kernel.name}</h2> | |
| <div class="sub">${c.label} · ${sub}</div> | |
| <div class="kv" style="display:flex;gap:14px;flex-wrap:wrap;margin:8px 0 2px"> | |
| <span style="color:${stHex};font-weight:700">${STATUS_LABEL[st]||'●'}</span> | |
| <span>last beat: <b>${ago}</b></span> | |
| <span>signed receipts: <b>${ticks}</b></span> | |
| </div> | |
| <h3>What it does</h3> | |
| <div class="kv">${kernel.does}</div> | |
| ${kernel.substrate ? `<h3>Substrate</h3><div class="kv">package: <b>${kernel.substrate}</b></div>` : ''} | |
| <h3>Last 5 heartbeats</h3> | |
| <div class="kv" id="hbBox">polling ${kernel.name}…</div> | |
| <h3>Kernel endpoint</h3> | |
| <a class="link" href="${endpoint}" target="_blank" rel="noopener">GET /api/${c.id}/v3/kernels/${kernel.name.toLowerCase()}</a> | |
| <h3>Chakra codex</h3> | |
| ${c.codex.map(x => `<a class="link" href="${x.href}" target="_blank" rel="noopener">${x.label}</a>`).join('')} | |
| <h3>Flagship health</h3> | |
| <div class="kv" id="liveBox">polling ${c.label}…</div> | |
| <p style="font-size:11px;color:#6b7c90;margin-top:18px">Each kernel is a perpetual OODA loop (observe→decide→act→sign) rooted in a hash-linked, DSSE-signed codex. The Ouroboros loop threads SIGN→GATE→CHAIN→MEMORY→REPLAY through every chakra. Doctrine v11 LOCKED (749/14/163).</p> | |
| `; | |
| openPanel(); | |
| pollKernelHeartbeats(c, kernel); | |
| pollChakraLive(c); | |
| } | |
| async function pollKernelHeartbeats(c, kernel){ | |
| const box = document.getElementById('hbBox'); | |
| if (!box) return; | |
| const beats = await lastHeartbeats(c.id, kernel.name, 5); | |
| if (!document.getElementById('hbBox')) return; // panel changed while awaiting | |
| if (!beats.length){ box.textContent = 'no heartbeats yet (or flagship unreachable — honest offline state)'; return; } | |
| box.innerHTML = beats.map(b => { | |
| // codex entries may wrap the receipt under .payload / .data / be the receipt itself | |
| const rcpt = b.payload || b.data || b.receipt || b; | |
| const tick = rcpt.tick != null ? `#${rcpt.tick}` : ''; | |
| const ts = rcpt.ts || b.ts || b.created_at || ''; | |
| const sum = rcpt.summary || rcpt.did_work === false ? (rcpt.summary || 'no-op') : (rcpt.summary || 'tick'); | |
| const signed = (rcpt.signed_payload || rcpt.signatures || (rcpt.signed_payload && rcpt.signed_payload.signatures)) ? '🔏' : ''; | |
| return `<div style="padding:6px 0;border-bottom:1px solid #ffffff10;font-size:12px"> | |
| <b>${tick}</b> <span style="color:#9fb0c4">${String(ts).replace('T',' ').replace('Z','')}</span> ${signed}<br> | |
| <span style="color:#cfe0f2">${sum}</span></div>`; | |
| }).join(''); | |
| } | |
| // ---------- unified "All Codices" search ---------- | |
| function openAllCodices(){ | |
| const all = CHAKRAS.flatMap(c => c.codex.map(x => ({...x, chakra:c.label, color:c.color, id:c.id}))); | |
| body.innerHTML = ` | |
| <h2>All Codices</h2> | |
| <div class="sub">Unified search across all 5 chakras (${all.length} codex links · 45 kernels)</div> | |
| <input id="codexSearch" placeholder="filter recipes / formulas / theses…" | |
| style="width:100%;padding:10px;border-radius:8px;border:1px solid #ffffff22;background:#0a0e14;color:#e8eef6;font-size:14px;margin-bottom:12px" /> | |
| <div id="codexResults"></div> | |
| `; | |
| openPanel(); | |
| const input = document.getElementById('codexSearch'); | |
| const out = document.getElementById('codexResults'); | |
| const render = (q='') => { | |
| const ql = q.toLowerCase(); | |
| const rows = all.filter(x => (x.label+x.chakra).toLowerCase().includes(ql)); | |
| out.innerHTML = rows.map(x => { | |
| const col = '#'+x.color.toString(16).padStart(6,'0'); | |
| return `<a class="link" href="${x.href}" target="_blank" rel="noopener"> | |
| <span style="color:${col};font-weight:700">${x.chakra}</span> · ${x.label}</a>`; | |
| }).join('') || '<div class="kv">no match</div>'; | |
| }; | |
| render(); | |
| input.oninput = () => render(input.value); | |
| input.focus(); | |
| } | |