File size: 7,531 Bytes
b58e0b6
 
 
 
 
320c18f
b58e0b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320c18f
 
 
b58e0b6
 
 
320c18f
 
 
 
 
 
b58e0b6
 
 
320c18f
 
 
 
 
b58e0b6
 
 
320c18f
 
 
 
b58e0b6
 
320c18f
b58e0b6
320c18f
b58e0b6
 
320c18f
b58e0b6
 
 
320c18f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b58e0b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 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();
}