betterwithage Claude Opus 4.7 commited on
Commit
424636b
·
verified ·
1 Parent(s): cca3499

deploy(hf): sync szl-holdings/a11oy@main derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 1138 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Files changed (2) hide show
  1. pages/hatun-mcp.html +67 -57
  2. web/immune.html +134 -43
pages/hatun-mcp.html CHANGED
@@ -9,7 +9,7 @@ a{color:var(--gold);text-decoration:none}a:hover{text-decoration:underline}
9
  header{border-bottom:1px solid var(--line);padding:14px 22px;display:flex;align-items:center;gap:14px;background:linear-gradient(180deg,#10121b,#0a0b10)}
10
  header .brand{font-weight:700;letter-spacing:.5px;color:var(--gold);font-size:18px}
11
  header .tag{color:var(--mut);font-size:12px}
12
- nav{display:flex;flex-wrap:wrap;gap:8px;padding:10px 22px;border-bottom:1px solid var(--line);background:var(--panel)}
13
  nav a{font-size:12.5px;padding:4px 10px;border:1px solid var(--line);border-radius:999px;color:var(--mut)}
14
  nav a:hover{border-color:var(--gold);color:var(--gold);text-decoration:none}
15
  nav a.active{background:var(--gold);color:#0a0b10;border-color:var(--gold);font-weight:600}
@@ -22,6 +22,8 @@ h1{font-size:26px;margin:0 0 4px;color:#fff}h2{font-size:18px;margin:28px 0 10px
22
  .grid a.tile:hover{border-color:var(--gold);text-decoration:none}
23
  .grid a.tile .t{color:var(--gold);font-weight:600;margin-bottom:4px}.grid a.tile .d{color:var(--mut);font-size:12.5px}
24
  table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13.5px}
 
 
25
  th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top}
26
  th{color:var(--mut);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.4px}
27
  code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px}
@@ -35,12 +37,22 @@ footer{border-top:1px solid var(--line);color:var(--mut);font-size:11.5px;paddin
35
  .statline b{color:var(--ink)}
36
  .tag2{display:inline-block;background:#1c2030;color:var(--mut);border:1px solid var(--line);border-radius:6px;padding:1px 7px;font-size:11px;margin-left:6px}
37
  .st{color:var(--amb);font-weight:600}
 
 
 
 
 
 
 
 
 
 
38
  </style></head><body>
39
  <header><span class="brand">a11oy</span><span class="tag">Brand Orchestration Layer · the one place to see everything</span></header>
40
  <nav><a href="/hub">Hub</a><a href="/a11oy.code">a11oy.code</a><a href="/docs">Docs</a><a href="/pricing">Pricing</a><a href="/api-keys">API Keys</a><a href="/sdk">SDK</a><a href="/status">Status</a><a href="/hatun-mcp" class="active">Hatun-MCP</a><a href="/observability">Observability</a><a href="/security">Security</a><a href="/compliance">Compliance</a><a href="/cued-engagement">Cued Engagement</a><a href="/uds">UDS</a><a href="/counter-uas">Counter-UAS</a><a href="/evidence">Evidence</a><a href="/upgrades">Upgrades</a><a href="/audit">Audit</a><a href="/gap-report">Gap Report</a></nav>
41
  <main>
42
  <h1>Hatun-MCP — agentic MCP server</h1>
43
- <p class="sub"><b>Hatun</b> (Quechua: <i>great / sovereign</i>) is the Model Context Protocol gateway that exposes SZL's governed flagship capabilities to any MCP client (Claude Desktop, Cursor, custom agents). Every tool invocation passes the Yuyay-13 governance gate and emits a signed Khipu receipt. Anonymous calls are governed-but-declined (OWASP MCP07) provide an SZL API key to execute. This tab probes the live server directly from your browser.</p>
44
 
45
  <div class="card">
46
  <div class="statline">
@@ -50,20 +62,20 @@ footer{border-top:1px solid var(--line);color:var(--mut);font-size:11.5px;paddin
50
  <span>Protocol: <b id="proto">—</b></span>
51
  <span>Probe latency: <b id="lat">—</b></span>
52
  </div>
53
- <p class="sub" style="margin:12px 0 0">Endpoint: <code>https://szlholdings-a11oy.hf.space/mcp/</code> (canonical, live, same-origin) · Transport: JSON-RPC over Streamable HTTP (<code>POST /mcp/</code>) · MCP revision <code>2024-11-05</code><br><b>Note:</b> the standalone hatun-mcp Space is retired; a11oy now serves the canonical MCP directly at <code>/mcp/</code>.</p>
54
  </div>
55
 
56
- <h2>Tools exposed (<span id="toolcount">16</span>)</h2>
57
- <p class="sub">Loaded live from the canonical <code>GET /mcp/</code> discovery card on this Space. These are the real governed tools exposed by the live MCP (call them with <code>POST /mcp/</code> JSON-RPC <code>tools/call</code>).</p>
58
- <table><thead><tr><th>Tool</th><th>Backend flagship</th><th>Class</th></tr></thead><tbody id="tools"></tbody></table>
59
 
60
  <h2>Recent invocations</h2>
61
- <p class="sub">Each MCP call appends a Khipu receipt (continuum hash + DSSE signature) to the governance chain. Recent receipts are summarized here from the server's invocation feed; if the feed endpoint is unreachable cross-origin it shows the honest interim message below.</p>
62
- <table><thead><tr><th>Time (UTC)</th><th>Tool</th><th>Outcome</th><th>Continuum hash</th></tr></thead><tbody id="inv"><tr><td colspan="4" class="sub">loading…</td></tr></tbody></table>
63
 
64
  <h2>Connect a client</h2>
65
  <div class="card">
66
- <p style="margin:0 0 8px">Claude Desktop — add to <code>claude_desktop_config.json</code> (uses the <code>mcp-remote</code> bridge, pointing at a11oy's live canonical <code>/mcp/</code>):</p>
67
  <pre>{
68
  "mcpServers": {
69
  "szl-a11oy": {
@@ -72,63 +84,61 @@ footer{border-top:1px solid var(--line);color:var(--mut);font-size:11.5px;paddin
72
  }
73
  }
74
  }</pre>
75
- <p class="sub" style="margin:8px 0 0">Quick test: <code>curl -s https://szlholdings-a11oy.hf.space/mcp/</code> (discovery card) or <code>curl -s -X POST https://szlholdings-a11oy.hf.space/mcp/ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'</code> · public key: <a href="/cosign.pub" target="_blank" rel="noopener">/cosign.pub</a> · try the loop: <a href="/ask-and-act">/ask-and-act</a></p>
76
  </div>
77
 
78
- <div class="note">This tab probes a11oy's own canonical, same-origin <code>/mcp/</code> endpoint no cross-origin CORS, no dead Space. The tool list and protocol version below are read live from <code>GET /mcp/</code>.</div>
79
- <p class="sub">Source: szl_agentic_loop.py (canonical MCP + governed loop) · live probe of <code>/mcp/</code></p>
80
 
81
  <script>
82
- const BASE=""; // same-origin: a11oy serves the canonical /mcp/ directly
83
- // Static authoritative tool list (mirrors server-card.json); refreshed live below when CORS permits.
84
- const TOOLS=[
85
- ["szl_a11oy_code_chat","a11oy.code","read"],
86
- ["szl_killinchu_detect","vessels / killinchu","read"],
87
- ["szl_killinchu_cue","vessels / killinchu","2-person"],
88
- ["szl_sentra_scan","sentra","read"],
89
- ["szl_rosie_reason","rosie","read"],
90
- ["szl_khipu_verify","governance","read"],
91
- ["szl_lean_verify","lean / formal-verification","read"],
92
- ["szl_puriq_evaluate","puriq","read"],
93
- ["szl_yachay_dome_predict","yachay-dome","read"],
94
- ["szl_wayra_recent","wayra","read"],
95
- ["szl_anatomy_3d_render","anatomy","read"],
96
- ["szl_doctrine_lookup","governance / doctrine","read"],
97
- ["szl_yuyay_score","yuyay-v3","read"],
98
- ["szl_thesis_query","thesis","read"],
99
- ["szl_drone_lookup","uds / counter-uas","read"],
100
- ["szl_formula_evaluate","puriq / formal math","read"]];
101
- function paintTools(list){const tb=document.getElementById('tools');tb.innerHTML='';
102
- list.forEach(([n,b,c])=>{const tr=document.createElement('tr');
103
- const cls=c==='2-person'?'a':'n';
104
- tr.innerHTML=`<td><code>${n}</code></td><td>${b}</td><td><span class="pill ${cls}">${c}</span></td>`;tb.appendChild(tr);});
105
  document.getElementById('toolcount').textContent=list.length;}
106
- paintTools(TOOLS);
107
 
108
- // Live probe of the canonical, same-origin /mcp/ discovery card
109
  const t0=performance.now();
110
- fetch(BASE+"/mcp/").then(r=>r.json().then(j=>({r,j}))).then(({r,j})=>{
111
- const ms=Math.round(performance.now()-t0);
112
- document.getElementById('lat').textContent=ms+' ms';
113
- document.getElementById('health').innerHTML=(r.ok&&j.canonical)?'<span class="pill g">LIVE</span>':'<span class="pill a">'+r.status+'</span>';
114
- document.getElementById('signer').textContent='in-image ECDSA-P256 (see /cosign.pub)';
115
- document.getElementById('chain').textContent='hash-chained receipts ✓';
116
- document.getElementById('proto').textContent=j.protocolVersion||'';
117
- // refresh the tool table from the live card's real governed tools
118
- if(j&&Array.isArray(j.tools)&&j.tools.length){
119
- paintTools(j.tools.map(t=>[t.name, (t.title||'governed tool'), 'governed']));
120
  }
121
- }).catch(()=>{document.getElementById('health').innerHTML='<span class="pill r">unreachable</span>';});
 
 
 
 
 
 
122
 
123
- // Recent invocations feed (optional endpoint; honest fallback if absent/blocked)
124
- fetch(BASE+"/api/hatun/invocations",{mode:'cors'}).then(r=>r.ok?r.json():Promise.reject()).then(rows=>{
125
- const tb=document.getElementById('inv');tb.innerHTML='';
126
- if(!rows||!rows.length){tb.innerHTML='<tr><td colspan="4" class="sub">No recent invocations recorded.</td></tr>';return;}
127
- rows.slice(0,12).forEach(x=>{const tr=document.createElement('tr');
128
- const ok=x.outcome==='success';
129
- tr.innerHTML=`<td>${x.ts||''}</td><td><code>${x.tool||''}</code></td><td><span class="pill ${ok?'g':'a'}">${x.outcome||'—'}</span></td><td><code>${(x.continuum_hash||'').slice(0,16)}</code></td>`;tb.appendChild(tr);});
130
- }).catch(()=>{document.getElementById('inv').innerHTML='<tr><td colspan="4" class="sub">Run a governed agent at <a href="/ask-and-act">/ask-and-act</a> — each run produces a signed, hash-chained receipt you can re-verify in the browser (chain + signature). The MCP <code>tools/call</code> path drives the same governed pipeline.</td></tr>';});
 
 
131
  </script>
132
  </main>
133
- <footer>Doctrine v12 (PURIQ) additive · v11/v12 LOCKED: 749 declarations · 14 axioms · 163 sorries · 13-axis yuyay_v3 · lutar-v18.0.0 @ c7c0ba17 · SLSA L1 (honest) · Khipu signature = DSSE/cosign PLACEHOLDER<br>HfApi direct push only · IP-HOLD a11oy#57 untouched · ADDITIVE / zero-regression · Khipu receipt on every action · Signed <b>Yachay</b> · Co-author Perplexity Computer Agent</footer>
134
  </body></html>
 
9
  header{border-bottom:1px solid var(--line);padding:14px 22px;display:flex;align-items:center;gap:14px;background:linear-gradient(180deg,#10121b,#0a0b10)}
10
  header .brand{font-weight:700;letter-spacing:.5px;color:var(--gold);font-size:18px}
11
  header .tag{color:var(--mut);font-size:12px}
12
+ nav{display:flex;flex-wrap:nowrap;gap:8px;padding:10px 22px;border-bottom:1px solid var(--line);background:var(--panel);overflow-x:auto;overscroll-behavior-inline:contain;scrollbar-width:thin}
13
  nav a{font-size:12.5px;padding:4px 10px;border:1px solid var(--line);border-radius:999px;color:var(--mut)}
14
  nav a:hover{border-color:var(--gold);color:var(--gold);text-decoration:none}
15
  nav a.active{background:var(--gold);color:#0a0b10;border-color:var(--gold);font-weight:600}
 
22
  .grid a.tile:hover{border-color:var(--gold);text-decoration:none}
23
  .grid a.tile .t{color:var(--gold);font-weight:600;margin-bottom:4px}.grid a.tile .d{color:var(--mut);font-size:12.5px}
24
  table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13.5px}
25
+ .table-wrap{width:100%;overflow-x:auto;overscroll-behavior-inline:contain;-webkit-overflow-scrolling:touch}
26
+ .table-wrap table{min-width:620px}
27
  th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top}
28
  th{color:var(--mut);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.4px}
29
  code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px}
 
37
  .statline b{color:var(--ink)}
38
  .tag2{display:inline-block;background:#1c2030;color:var(--mut);border:1px solid var(--line);border-radius:6px;padding:1px 7px;font-size:11px;margin-left:6px}
39
  .st{color:var(--amb);font-weight:600}
40
+ @media(max-width:700px){
41
+ header{padding:12px 14px;align-items:flex-start;flex-direction:column;gap:2px}
42
+ nav{padding:9px 14px}
43
+ main{padding:20px 14px 44px}
44
+ h1{font-size:23px;line-height:1.25}
45
+ .card{padding:15px}
46
+ .statline{display:grid;grid-template-columns:1fr;gap:8px}
47
+ pre{white-space:pre-wrap;overflow-wrap:anywhere}
48
+ footer{padding:16px 14px}
49
+ }
50
  </style></head><body>
51
  <header><span class="brand">a11oy</span><span class="tag">Brand Orchestration Layer · the one place to see everything</span></header>
52
  <nav><a href="/hub">Hub</a><a href="/a11oy.code">a11oy.code</a><a href="/docs">Docs</a><a href="/pricing">Pricing</a><a href="/api-keys">API Keys</a><a href="/sdk">SDK</a><a href="/status">Status</a><a href="/hatun-mcp" class="active">Hatun-MCP</a><a href="/observability">Observability</a><a href="/security">Security</a><a href="/compliance">Compliance</a><a href="/cued-engagement">Cued Engagement</a><a href="/uds">UDS</a><a href="/counter-uas">Counter-UAS</a><a href="/evidence">Evidence</a><a href="/upgrades">Upgrades</a><a href="/audit">Audit</a><a href="/gap-report">Gap Report</a></nav>
53
  <main>
54
  <h1>Hatun-MCP — agentic MCP server</h1>
55
+ <p class="sub"><b>Hatun</b> (Quechua: <i>great / sovereign</i>) is the Model Context Protocol gateway for SZL capabilities. This page separates a reachable runtime declaration from execution and receipt evidence. A listed tool is not proof that it has run. Receipt and signer fields remain <b>UNKNOWN</b> or <b>UNAVAILABLE</b> until the read-only evidence endpoint observes an actual governed-run receipt.</p>
56
 
57
  <div class="card">
58
  <div class="statline">
 
62
  <span>Protocol: <b id="proto">—</b></span>
63
  <span>Probe latency: <b id="lat">—</b></span>
64
  </div>
65
+ <p class="sub" style="margin:12px 0 0">Runtime path: <code>/mcp/</code> (same origin) · Transport: JSON-RPC over HTTP (<code>POST /mcp/</code>). Availability, protocol revision, signer evidence, and receipt-chain evidence are populated only from successful reads below.</p>
66
  </div>
67
 
68
+ <h2>Tools declared (<span id="toolcount"></span>) <span id="toolsource" class="pill a">STATIC FALLBACK</span></h2>
69
+ <p class="sub">The initial rows are an explicitly labelled static fallback. A successful <code>GET /mcp/</code> replaces them with the runtime-declared catalog. Neither state is execution proof.</p>
70
+ <div class="table-wrap"><table><thead><tr><th>Tool</th><th>Description</th><th>Evidence class</th></tr></thead><tbody id="tools"></tbody></table></div>
71
 
72
  <h2>Recent invocations</h2>
73
+ <p class="sub">This bounded, ephemeral feed contains only summaries of governed agent runs for which this process actually created a receipt. It does not claim that every MCP discovery or tool call is signed, and reading it never mints a receipt.</p>
74
+ <div class="table-wrap"><table><thead><tr><th>Time (UTC)</th><th>Observed tool</th><th>Outcome</th><th>Receipt evidence</th></tr></thead><tbody id="inv"><tr><td colspan="4" class="sub">loading…</td></tr></tbody></table></div>
75
 
76
  <h2>Connect a client</h2>
77
  <div class="card">
78
+ <p style="margin:0 0 8px">Claude Desktop — deployment example using the <code>mcp-remote</code> bridge. Verify the target URL and authentication requirements in your environment before use:</p>
79
  <pre>{
80
  "mcpServers": {
81
  "szl-a11oy": {
 
84
  }
85
  }
86
  }</pre>
87
+ <p class="sub" style="margin:8px 0 0">Same-origin checks: <code>GET /mcp/</code> for runtime discovery, <code>GET /api/hatun/evidence</code> for the no-mint evidence contract, and <code>GET /api/hatun/invocations</code> for observed receipt summaries. A public-key path alone is not proof that any receipt was signed.</p>
88
  </div>
89
 
90
+ <div class="note">The page performs read-only same-origin probes. <code>GET /mcp/</code> supplies runtime declarations; <code>GET /api/hatun/evidence</code> supplies observed receipt state. Neither GET signs, appends, or upgrades evidence.</div>
91
+ <p class="sub">Source: <code>szl_agentic_loop.py</code> · read-only probes of <code>/mcp/</code> and <code>/api/hatun/evidence</code></p>
92
 
93
  <script>
94
+ const BASE="";
95
+ const FALLBACK_TOOLS=[
96
+ {name:"retrieve_context",description:"Governance-corpus retrieval declaration"},
97
+ {name:"policy_check",description:"Deny-by-default policy declaration"},
98
+ {name:"trust_score",description:"Advisory trust-score declaration"},
99
+ {name:"sign_receipt",description:"Receipt-signing declaration"},
100
+ {name:"verify_receipt",description:"Receipt-verification declaration"}
101
+ ];
102
+
103
+ function cell(text,tag){const el=document.createElement(tag||'td');el.textContent=text==null?'—':String(text);return el;}
104
+ function pill(text,cls){const el=document.createElement('span');el.className='pill '+cls;el.textContent=text;return el;}
105
+ function setStatus(id,text,cls){const host=document.getElementById(id);host.textContent='';host.appendChild(pill(text,cls));}
106
+ function paintTools(list,evidenceClass){const tb=document.getElementById('tools');tb.textContent='';
107
+ list.forEach(tool=>{const tr=document.createElement('tr');const name=cell(tool.name,'td');
108
+ const code=document.createElement('code');code.textContent=tool.name||'—';name.textContent='';name.appendChild(code);
109
+ tr.appendChild(name);tr.appendChild(cell(tool.description||tool.title||'No description reported'));
110
+ const status=cell('','td');status.appendChild(pill(evidenceClass,evidenceClass==='RUNTIME DECLARED'?'n':'a'));tr.appendChild(status);tb.appendChild(tr);});
 
 
 
 
 
 
111
  document.getElementById('toolcount').textContent=list.length;}
112
+ paintTools(FALLBACK_TOOLS,'STATIC FALLBACK');
113
 
 
114
  const t0=performance.now();
115
+ fetch(BASE+"/mcp/",{cache:'no-store'}).then(r=>r.json().then(j=>({r,j}))).then(({r,j})=>{
116
+ document.getElementById('lat').textContent=Math.round(performance.now()-t0)+' ms';
117
+ setStatus('health',r.ok?'REACHABLE':'HTTP '+r.status,r.ok?'g':'a');
118
+ document.getElementById('proto').textContent=j.protocolVersion||'UNKNOWN';
119
+ if(r.ok&&j&&Array.isArray(j.tools)){
120
+ paintTools(j.tools,'RUNTIME DECLARED');
121
+ const source=document.getElementById('toolsource');source.className='pill n';source.textContent='RUNTIME DECLARED';
 
 
 
122
  }
123
+ }).catch(()=>{setStatus('health','UNAVAILABLE','r');document.getElementById('lat').textContent='UNAVAILABLE';});
124
+
125
+ fetch(BASE+"/api/hatun/evidence",{cache:'no-store'}).then(r=>r.ok?r.json():Promise.reject()).then(e=>{
126
+ const signer=e&&e.signer||{};const chain=e&&e.receipt_chain||{};
127
+ document.getElementById('signer').textContent=signer.status==='OBSERVED_VERIFIED'?(signer.label||'OBSERVED_VERIFIED'):(signer.status||'UNKNOWN');
128
+ document.getElementById('chain').textContent=chain.status||'UNKNOWN';
129
+ }).catch(()=>{document.getElementById('signer').textContent='UNAVAILABLE';document.getElementById('chain').textContent='UNAVAILABLE';});
130
 
131
+ function paintEmptyInvocation(message){const tb=document.getElementById('inv');tb.textContent='';const tr=document.createElement('tr');
132
+ const td=cell(message,'td');td.colSpan=4;td.className='sub';tr.appendChild(td);tb.appendChild(tr);}
133
+ fetch(BASE+"/api/hatun/invocations",{cache:'no-store'}).then(r=>r.ok?r.json():Promise.reject()).then(feed=>{
134
+ const items=feed&&Array.isArray(feed.items)?feed.items:[];const tb=document.getElementById('inv');tb.textContent='';
135
+ if(!items.length){paintEmptyInvocation('UNKNOWN — no governed-run receipt has been observed in this process.');return;}
136
+ items.slice(0,12).forEach(x=>{const tr=document.createElement('tr');tr.appendChild(cell(x.ts));
137
+ const tool=cell('','td'),code=document.createElement('code');code.textContent=x.tool||'—';tool.appendChild(code);tr.appendChild(tool);
138
+ const outcome=cell('','td');outcome.appendChild(pill(x.outcome||'UNKNOWN',x.outcome==='DENY'?'r':'a'));tr.appendChild(outcome);
139
+ const ev=cell('','td'),evCode=document.createElement('code');evCode.textContent=(x.signature_status||'UNKNOWN')+' · '+(x.receipt_hash||'').slice(0,16);ev.appendChild(evCode);tr.appendChild(ev);tb.appendChild(tr);});
140
+ }).catch(()=>{paintEmptyInvocation('UNAVAILABLE — the read-only invocation feed could not be read.');});
141
  </script>
142
  </main>
143
+ <footer>Doctrine v11 honesty boundary · tool catalog = runtime declaration, not execution proof · signer and receipt-chain state remain UNKNOWN or UNAVAILABLE until observed evidence exists · GET reads never mint receipts</footer>
144
  </body></html>
web/immune.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="UTF-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
  <title>Immune (Hukulla) — fail-closed egress gate · SZL Holdings</title>
7
- <meta name="description" content="Live, honest view of the Immune organ (Quechua role 'Hukulla') — a fail-closed, deny-by-default egress gate. Real threat-signature scan + 1 MB size guard + Lambda-gate floor, every verdict signed into the shared Khipu chain. Lean backing: ImmuneNeymanPearsonOpt + FrontierWelfordVariance. Lambda = Conjecture 1; Khipu = Conjecture 2; trust never 100%."/>
8
  <!-- 0 runtime CDN (doctrine v11): system fonts only — no Google Fonts, no external assets. -->
9
  <style>
10
  :root{
@@ -26,7 +26,9 @@ a{color:inherit;text-decoration:none;}
26
  .topbar{position:sticky;top:0;z-index:60;display:flex;align-items:center;gap:1rem;flex-wrap:wrap;padding:.5rem 1.1rem;background:rgba(10,10,10,.92);backdrop-filter:blur(10px);border-bottom:1px solid var(--gold-line);font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--gold);}
27
  .topbar .sep{color:var(--dim);}
28
  .topbar .live{display:inline-flex;align-items:center;gap:.4rem;color:var(--cream);}
29
- .live-dot{width:6px;height:6px;border-radius:50%;background:var(--live);box-shadow:0 0 6px var(--live);animation:pulse 2.2s ease-in-out infinite;}
 
 
30
  @keyframes pulse{0%,100%{opacity:1;}50%{opacity:.35;}}
31
  .switcher{margin-left:auto;display:flex;align-items:center;gap:.3rem;}
32
  .flag{padding:.22rem .55rem;border-radius:6px;border:1px solid transparent;color:var(--muted);transition:.15s;}
@@ -37,12 +39,13 @@ h1{font-size:1.7rem;font-weight:600;margin:.2rem 0 .3rem;letter-spacing:-.01em;}
37
  .badge{display:inline-block;font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--teal);border:1px solid var(--teal-line);background:var(--teal-soft);padding:.25rem .6rem;border-radius:5px;margin-left:.4rem;vertical-align:middle;}
38
  .lede{color:var(--paragraph);font-size:.95rem;line-height:1.65;max-width:1180px;margin:.6rem 0 1rem;}
39
  .lede b{color:var(--cream);font-weight:600;}
40
- .cards{display:grid;grid-template-columns:repeat(4,1fr);gap:.8rem;margin:0 0 1.2rem;}
41
  .card{border:1px solid var(--gold-line);background:var(--panel);border-radius:8px;padding:.85rem 1rem;}
42
  .card .lbl{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);}
43
  .card .big{font-size:1.5rem;font-weight:600;margin:.35rem 0 .15rem;color:var(--teal);}
44
  .card .sub{font-family:var(--mono);font-size:10.5px;color:var(--muted);}
45
- .grid2{display:grid;grid-template-columns:1fr 1fr;gap:1rem;}
 
46
  .panel{border:1px solid var(--gold-line);background:var(--panel);border-radius:10px;padding:1rem 1.1rem;margin-bottom:1rem;}
47
  .panel h2{font-size:1.05rem;font-weight:600;margin:0 0 .2rem;display:flex;align-items:center;justify-content:space-between;}
48
  .panel h2 .meta{font-family:var(--mono);font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.06em;}
@@ -56,6 +59,8 @@ h1{font-size:1.7rem;font-weight:600;margin:.2rem 0 .3rem;letter-spacing:-.01em;}
56
  .pill.dim{color:var(--muted);}
57
  .simnote{border:1px solid var(--warn);background:rgba(201,160,95,.06);border-radius:8px;padding:.7rem .9rem;margin-top:.8rem;font-family:var(--mono);font-size:11px;color:#d8c39a;line-height:1.65;}
58
  table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:11.5px;margin-top:.5rem;}
 
 
59
  th,td{text-align:left;padding:.34rem .5rem;border-bottom:1px solid var(--gold-soft);color:var(--paragraph);vertical-align:top;}
60
  th{color:var(--dim);text-transform:uppercase;letter-spacing:.06em;font-size:9.5px;}
61
  td.deny{color:var(--red);}td.allow{color:var(--green);}
@@ -63,6 +68,7 @@ textarea,input{width:100%;background:var(--panel2);border:1px solid var(--gold-l
63
  textarea:focus,input:focus{outline:none;border-color:var(--teal-line);}
64
  button.run{margin-top:.6rem;padding:.55rem 1.1rem;border-radius:7px;border:1px solid var(--teal-line);background:var(--teal-soft);color:var(--teal);font-family:var(--mono);font-size:12px;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;transition:.15s;}
65
  button.run:hover{background:rgba(95,179,163,.18);color:var(--cream);}
 
66
  .preset{display:inline-block;margin:.3rem .3rem 0 0;padding:.2rem .5rem;border-radius:5px;border:1px solid var(--gold-line);color:var(--muted);font-family:var(--mono);font-size:10.5px;cursor:pointer;}
67
  .preset:hover{color:var(--cream);border-color:var(--teal-line);}
68
  .verdict-box{margin-top:.8rem;border:1px solid var(--gold-line);border-radius:8px;padding:.85rem 1rem;background:var(--panel2);font-family:var(--mono);font-size:12px;line-height:1.8;}
@@ -72,12 +78,33 @@ button.run:hover{background:rgba(95,179,163,.18);color:var(--cream);}
72
  .digest{color:var(--gold);word-break:break-all;}
73
  code{font-family:var(--mono);color:var(--teal);}
74
  .footnote{font-family:var(--mono);font-size:10px;color:var(--dim);margin-top:1.6rem;line-height:1.7;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </style>
76
  </head>
77
  <body>
78
  <div class="topbar">
79
  <span>SZL HOLDINGS</span><span class="sep">/</span><span>a11oy</span>
80
- <span class="live"><span class="live-dot"></span><span id="liveTag">IMMUNE · LIVE</span></span>
81
  <span class="switcher">
82
  <a class="flag active" href="/immune">Immune</a>
83
  <a class="flag" href="/energy">Energy</a>
@@ -88,12 +115,13 @@ code{font-family:var(--mono);color:var(--teal);}
88
  </div>
89
 
90
  <div class="wrap">
91
- <h1>Immune (Hukulla) — fail-closed egress gate<span class="badge" id="organBadge">deny-by-default · real inspection · Khipu-signed</span></h1>
92
  <p class="lede">
93
  The <b>Immune</b> organ (Quechua role <b>Hukulla</b>) is a <b>fail-closed, deny-by-default</b> egress gate.
94
- Every action is inspected against a real threat-signature corpus, a 1&nbsp;MB size guard and a
95
- <b>&Lambda;-gate floor</b> (MIN of supplied trust axes &lt; 0.5 &rarr; deny). Each verdict is signed into the
96
- <b>shared Khipu chain</b> (<code>SZL.Immune.Verdict.v1</code>). Proven Lean backing:
 
97
  <code>ImmuneNeymanPearsonOpt.lean</code> (Neyman&ndash;Pearson-optimal egress) and
98
  <code>FrontierWelfordVariance.lean</code> (Welford online variance). These back the gate but are
99
  <b>not</b> folded into the locked-8 proven set. &Lambda; = <b>Conjecture&nbsp;1</b> (not a theorem);
@@ -111,7 +139,7 @@ code{font-family:var(--mono);color:var(--teal);}
111
  <div class="panel">
112
  <h2>Inspect an action <span class="meta" id="verdictEp">POST /api/a11oy/v1/immune/verdict</span></h2>
113
  <p class="lede" style="margin:.2rem 0 .6rem;font-size:.86rem;">
114
- Submit a real action payload. The gate runs the live inspection and signs a Khipu receipt.
115
  Optionally include <code>axes</code> (a JSON array of trust scores) to exercise the &Lambda;-gate floor.
116
  </p>
117
  <div>
@@ -129,7 +157,7 @@ code{font-family:var(--mono);color:var(--teal);}
129
 
130
  <div class="panel">
131
  <h2>Live status <span class="meta" id="statusEp">GET /api/a11oy/v1/immune/status</span></h2>
132
- <div class="kv" id="statusKv"><span class="k">loading…</span></div>
133
  <div class="simnote" id="honestyNote">
134
  Λ = Conjecture 1 (NOT a theorem). Khipu = Conjecture 2. Trust never 100%. Effectors simulated.
135
  Decision feed is in-memory (resets on restart) — empty means IDLE, never faked.
@@ -139,12 +167,12 @@ code{font-family:var(--mono);color:var(--teal);}
139
 
140
  <div class="panel">
141
  <h2>Gates <span class="meta" id="gatesEp">GET /api/a11oy/v1/immune/gates</span></h2>
142
- <table id="gatesTable"><thead><tr><th>id</th><th>name</th><th>label</th><th>category</th><th>sample → expected</th></tr></thead><tbody><tr><td class="mono" colspan="5">loading…</td></tr></tbody></table>
143
  </div>
144
 
145
  <div class="panel">
146
  <h2>Decision feed <span class="meta" id="feedEp">GET /api/a11oy/v1/immune/feed</span></h2>
147
- <table id="feedTable"><thead><tr><th>time</th><th>decision</th><th>signals</th><th>Λ</th><th>receipt</th></tr></thead><tbody><tr><td class="mono" colspan="5">loading…</td></tr></tbody></table>
148
  </div>
149
 
150
  <div class="footnote" id="leanFoot">
@@ -156,45 +184,96 @@ code{font-family:var(--mono);color:var(--teal);}
156
 
157
  <script>
158
  const BASE = "/api/a11oy/v1/immune";
159
- async function getJSON(u,opt){try{const r=await fetch(u,opt);if(!r.ok)return null;return await r.json();}catch(e){return null;}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  function esc(s){return String(s).replace(/[&<>"']/g,function(c){return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]||c);});}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  async function loadStatus(){
163
- const s = await getJSON(BASE + "/status");
164
  const kv = document.getElementById("statusKv");
165
- if(!s){kv.innerHTML='<span class="k">status</span><span class="v">unreachable — retrying</span>';return;}
166
- document.getElementById("cVerdicts").textContent = (s.verdicts_this_process ?? 0);
 
 
 
 
 
 
167
  document.getElementById("cDenyRate").textContent = (s.deny_rate==null?"—":(Math.round(s.deny_rate*1000)/10)+"%");
168
- document.getElementById("cDenySub").textContent = (s.deny ?? 0)+" deny / "+(s.allow ?? 0)+" allow";
169
  document.getElementById("cCorpus").textContent = (s.signature_corpus_size ?? "—");
170
- document.getElementById("cCorpusSub").textContent = (s.threats_corpus_total ?? 0)+" STIX/MITRE in corpus";
171
  const k = s.khipu||{};
172
  document.getElementById("cChain").textContent = (k.chain_depth ?? "—");
173
- document.getElementById("cChainSub").textContent = (k.chain_verified? "chain verified":"unverified");
 
174
  kv.innerHTML =
175
- '<div><span class="k">organ</span><span class="v">'+esc(s.organ||"Immune (Hukulla)")+'</span></div>'+
176
- '<div><span class="k">role</span><span class="v">'+esc(s.role||"")+'</span></div>'+
177
- '<div><span class="k">Λ-gate floor</span><span class="v">'+esc(s.lambda_gate_floor)+'</span></div>'+
178
- '<div><span class="k">size guard</span><span class="v">'+esc(s.size_guard_bytes)+' bytes</span></div>'+
179
- '<div><span class="k">signatures</span><span class="v">'+esc((s.signatures||[]).join(", "))+'</span></div>'+
180
- '<div><span class="k">last receipt</span><span class="v digest">'+esc(s.last_receipt_digest||"(none yet)")+'</span></div>'+
181
- '<div><span class="k">khipu head</span><span class="v digest">'+esc((k.head_digest||"").slice(0,32))+'…</span></div>'+
182
- '<div><span class="k">status</span><span class="pill green">'+esc(s.status||"REAL")+'</span></div>';
 
 
183
  }
184
 
185
  async function loadGates(){
186
- const g = await getJSON(BASE + "/gates");
187
  const tb = document.querySelector("#gatesTable tbody");
188
- if(!g || !g.gates){tb.innerHTML='<tr><td class="mono" colspan="5">unreachable</td></tr>';return;}
 
189
  tb.innerHTML = g.gates.map(x=>
190
  '<tr><td class="mono">'+esc(x.id)+'</td><td>'+esc(x.name)+'</td><td>'+esc(x.label)+'</td><td>'+esc(x.category)+'</td>'+
191
  '<td><code>'+esc(String(x.sampleInput).slice(0,46))+'</code> → <b>'+esc(x.expectedDecision)+'</b></td></tr>').join("");
192
  }
193
 
194
  async function loadFeed(){
195
- const f = await getJSON(BASE + "/feed?limit=20");
196
  const tb = document.querySelector("#feedTable tbody");
197
- if(!f || !f.verdicts || !f.verdicts.length){tb.innerHTML='<tr><td class="mono" colspan="5">IDLE — no verdicts buffered (resets on restart)</td></tr>';return;}
 
 
198
  tb.innerHTML = f.verdicts.map(v=>
199
  '<tr><td>'+esc((v.timestamp||"").slice(11,19))+'</td>'+
200
  '<td class="'+esc(v.decision)+'">'+esc(v.decision)+'</td>'+
@@ -209,21 +288,33 @@ async function runVerdict(){
209
  try{ body = JSON.parse(document.getElementById("actionInput").value); }
210
  catch(e){ out.innerHTML='<div class="verdict-box deny">Invalid JSON: '+esc(e.message)+'</div>'; return; }
211
  out.innerHTML='<div class="verdict-box">inspecting…</div>';
212
- const v = await getJSON(BASE + "/verdict", {method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)});
213
- if(!v){ out.innerHTML='<div class="verdict-box deny">endpoint unreachable — retry</div>'; return; }
214
- const cls = v.decision==="deny"?"deny":"allow";
215
- const pill = v.decision==="deny"?'<span class="pill red">DENY</span>':'<span class="pill green">ALLOW</span>';
216
- const sigs = (v.signals||[]).map(s=>'<span class="sig">'+esc(s)+'</span>').join("") || '<span class="pill dim">no signal</span>';
 
 
217
  const rec = v.khipu_receipt||{};
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  out.innerHTML =
219
  '<div class="verdict-box '+cls+'">'+
220
- '<div>decision '+pill+' &nbsp; <span style="color:var(--dim)">Λ='+esc(v.lambda_value)+' (floor '+esc(v.lambda_floor)+')</span></div>'+
221
- '<div style="margin:.4rem 0;color:var(--paragraph)">'+esc(v.reason)+'</div>'+
222
  '<div style="margin:.3rem 0;">signals: '+sigs+'</div>'+
223
- '<div><span style="color:var(--dim)">verdict hash</span> <span class="digest">'+esc(v.receipt_hash)+'</span></div>'+
224
- '<div><span style="color:var(--dim)">Khipu receipt</span> <span class="digest">'+esc(rec.digest||"")+'</span> '+
225
- '<span style="color:var(--dim)">seq '+esc(rec.seq)+' · '+esc(rec.receipt_type||"SZL.Immune.Verdict.v1")+'</span></div>'+
226
- '<div style="color:var(--dim);margin-top:.3rem">fail-closed='+esc(v.fail_closed)+' · doctrine '+esc(v.doctrine)+'</div>'+
227
  '</div>';
228
  loadStatus(); loadFeed();
229
  }
 
4
  <meta charset="UTF-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
  <title>Immune (Hukulla) — fail-closed egress gate · SZL Holdings</title>
7
+ <meta name="description" content="Honest operational view of the Immune organ (Quechua role 'Hukulla') — a fail-closed, deny-by-default egress gate. Status, trace, signature, and Khipu-chain evidence are shown only when returned by the live backend. Lambda = Conjecture 1; Khipu = Conjecture 2; trust never 100%."/>
8
  <!-- 0 runtime CDN (doctrine v11): system fonts only — no Google Fonts, no external assets. -->
9
  <style>
10
  :root{
 
26
  .topbar{position:sticky;top:0;z-index:60;display:flex;align-items:center;gap:1rem;flex-wrap:wrap;padding:.5rem 1.1rem;background:rgba(10,10,10,.92);backdrop-filter:blur(10px);border-bottom:1px solid var(--gold-line);font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--gold);}
27
  .topbar .sep{color:var(--dim);}
28
  .topbar .live{display:inline-flex;align-items:center;gap:.4rem;color:var(--cream);}
29
+ .live-dot{width:6px;height:6px;border-radius:50%;background:var(--warn);box-shadow:0 0 6px var(--warn);animation:pulse 2.2s ease-in-out infinite;}
30
+ .live-dot.ready{background:var(--live);box-shadow:0 0 6px var(--live);}
31
+ .live-dot.error{background:var(--err);box-shadow:0 0 6px var(--err);animation:none;}
32
  @keyframes pulse{0%,100%{opacity:1;}50%{opacity:.35;}}
33
  .switcher{margin-left:auto;display:flex;align-items:center;gap:.3rem;}
34
  .flag{padding:.22rem .55rem;border-radius:6px;border:1px solid transparent;color:var(--muted);transition:.15s;}
 
39
  .badge{display:inline-block;font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--teal);border:1px solid var(--teal-line);background:var(--teal-soft);padding:.25rem .6rem;border-radius:5px;margin-left:.4rem;vertical-align:middle;}
40
  .lede{color:var(--paragraph);font-size:.95rem;line-height:1.65;max-width:1180px;margin:.6rem 0 1rem;}
41
  .lede b{color:var(--cream);font-weight:600;}
42
+ .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:.8rem;margin:0 0 1.2rem;}
43
  .card{border:1px solid var(--gold-line);background:var(--panel);border-radius:8px;padding:.85rem 1rem;}
44
  .card .lbl{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);}
45
  .card .big{font-size:1.5rem;font-weight:600;margin:.35rem 0 .15rem;color:var(--teal);}
46
  .card .sub{font-family:var(--mono);font-size:10.5px;color:var(--muted);}
47
+ .grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;}
48
+ .grid2>*{min-width:0;}
49
  .panel{border:1px solid var(--gold-line);background:var(--panel);border-radius:10px;padding:1rem 1.1rem;margin-bottom:1rem;}
50
  .panel h2{font-size:1.05rem;font-weight:600;margin:0 0 .2rem;display:flex;align-items:center;justify-content:space-between;}
51
  .panel h2 .meta{font-family:var(--mono);font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.06em;}
 
59
  .pill.dim{color:var(--muted);}
60
  .simnote{border:1px solid var(--warn);background:rgba(201,160,95,.06);border-radius:8px;padding:.7rem .9rem;margin-top:.8rem;font-family:var(--mono);font-size:11px;color:#d8c39a;line-height:1.65;}
61
  table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:11.5px;margin-top:.5rem;}
62
+ .table-scroll{max-width:100%;overflow-x:auto;overscroll-behavior-inline:contain;-webkit-overflow-scrolling:touch;}
63
+ .table-scroll table{min-width:680px;}
64
  th,td{text-align:left;padding:.34rem .5rem;border-bottom:1px solid var(--gold-soft);color:var(--paragraph);vertical-align:top;}
65
  th{color:var(--dim);text-transform:uppercase;letter-spacing:.06em;font-size:9.5px;}
66
  td.deny{color:var(--red);}td.allow{color:var(--green);}
 
68
  textarea:focus,input:focus{outline:none;border-color:var(--teal-line);}
69
  button.run{margin-top:.6rem;padding:.55rem 1.1rem;border-radius:7px;border:1px solid var(--teal-line);background:var(--teal-soft);color:var(--teal);font-family:var(--mono);font-size:12px;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;transition:.15s;}
70
  button.run:hover{background:rgba(95,179,163,.18);color:var(--cream);}
71
+ button.run:focus-visible,.preset:focus-visible,.flag:focus-visible{outline:2px solid var(--teal);outline-offset:2px;}
72
  .preset{display:inline-block;margin:.3rem .3rem 0 0;padding:.2rem .5rem;border-radius:5px;border:1px solid var(--gold-line);color:var(--muted);font-family:var(--mono);font-size:10.5px;cursor:pointer;}
73
  .preset:hover{color:var(--cream);border-color:var(--teal-line);}
74
  .verdict-box{margin-top:.8rem;border:1px solid var(--gold-line);border-radius:8px;padding:.85rem 1rem;background:var(--panel2);font-family:var(--mono);font-size:12px;line-height:1.8;}
 
78
  .digest{color:var(--gold);word-break:break-all;}
79
  code{font-family:var(--mono);color:var(--teal);}
80
  .footnote{font-family:var(--mono);font-size:10px;color:var(--dim);margin-top:1.6rem;line-height:1.7;}
81
+ @media(max-width:760px){
82
+ .topbar{position:static;padding:.55rem .8rem;gap:.55rem;}
83
+ .switcher{order:3;width:100%;margin-left:0;flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-inline:contain;padding-bottom:.15rem;}
84
+ .flag{flex:0 0 auto;min-height:44px;display:inline-flex;align-items:center;}
85
+ .wrap{padding:1.15rem .85rem 3rem;}
86
+ h1{font-size:1.4rem;line-height:1.25;}
87
+ .badge{display:block;width:max-content;max-width:100%;margin:.55rem 0 0;white-space:normal;}
88
+ .grid2{grid-template-columns:minmax(0,1fr);}
89
+ .panel{padding:.85rem;}
90
+ .panel h2{align-items:flex-start;gap:.5rem;flex-direction:column;}
91
+ .kv .k{min-width:130px;}
92
+ button.run{min-height:44px;width:100%;}
93
+ .preset{min-height:44px;display:inline-flex;align-items:center;}
94
+ }
95
+ @media(max-width:420px){
96
+ .cards{grid-template-columns:minmax(0,1fr);}
97
+ .kv>div{display:grid;grid-template-columns:minmax(0,.7fr) minmax(0,1.3fr);gap:.5rem;}
98
+ .kv .k{min-width:0;}
99
+ .kv .v,.digest{overflow-wrap:anywhere;word-break:break-word;}
100
+ }
101
+ @media(prefers-reduced-motion:reduce){.live-dot{animation:none;}}
102
  </style>
103
  </head>
104
  <body>
105
  <div class="topbar">
106
  <span>SZL HOLDINGS</span><span class="sep">/</span><span>a11oy</span>
107
+ <span class="live"><span class="live-dot" id="liveDot"></span><span id="liveTag" aria-live="polite">IMMUNE · PROBING</span></span>
108
  <span class="switcher">
109
  <a class="flag active" href="/immune">Immune</a>
110
  <a class="flag" href="/energy">Energy</a>
 
115
  </div>
116
 
117
  <div class="wrap">
118
+ <h1>Immune (Hukulla) — fail-closed egress gate<span class="badge" id="organBadge">deny-by-default · evidence pending</span></h1>
119
  <p class="lede">
120
  The <b>Immune</b> organ (Quechua role <b>Hukulla</b>) is a <b>fail-closed, deny-by-default</b> egress gate.
121
+ Submitted actions are sent to the live threat-signature, size, and
122
+ <b>&Lambda;-gate</b> inspection path (MIN of supplied trust axes &lt; 0.5 &rarr; deny). When the backend
123
+ returns a Khipu receipt, its digest, sequence, signature, chain result, and trace context are shown below.
124
+ No receipt or signature state is inferred by this page. Proven Lean backing:
125
  <code>ImmuneNeymanPearsonOpt.lean</code> (Neyman&ndash;Pearson-optimal egress) and
126
  <code>FrontierWelfordVariance.lean</code> (Welford online variance). These back the gate but are
127
  <b>not</b> folded into the locked-8 proven set. &Lambda; = <b>Conjecture&nbsp;1</b> (not a theorem);
 
139
  <div class="panel">
140
  <h2>Inspect an action <span class="meta" id="verdictEp">POST /api/a11oy/v1/immune/verdict</span></h2>
141
  <p class="lede" style="margin:.2rem 0 .6rem;font-size:.86rem;">
142
+ Submit an action payload to the live inspection endpoint. Returned receipt and signing evidence is shown without inference.
143
  Optionally include <code>axes</code> (a JSON array of trust scores) to exercise the &Lambda;-gate floor.
144
  </p>
145
  <div>
 
157
 
158
  <div class="panel">
159
  <h2>Live status <span class="meta" id="statusEp">GET /api/a11oy/v1/immune/status</span></h2>
160
+ <div class="kv" id="statusKv" aria-live="polite"><span class="k">status</span><span class="v">PROBING</span></div>
161
  <div class="simnote" id="honestyNote">
162
  Λ = Conjecture 1 (NOT a theorem). Khipu = Conjecture 2. Trust never 100%. Effectors simulated.
163
  Decision feed is in-memory (resets on restart) — empty means IDLE, never faked.
 
167
 
168
  <div class="panel">
169
  <h2>Gates <span class="meta" id="gatesEp">GET /api/a11oy/v1/immune/gates</span></h2>
170
+ <div class="table-scroll" role="region" aria-label="Immune gates" tabindex="0"><table id="gatesTable"><thead><tr><th>id</th><th>name</th><th>label</th><th>category</th><th>sample → expected</th></tr></thead><tbody><tr><td class="mono" colspan="5">loading…</td></tr></tbody></table></div>
171
  </div>
172
 
173
  <div class="panel">
174
  <h2>Decision feed <span class="meta" id="feedEp">GET /api/a11oy/v1/immune/feed</span></h2>
175
+ <div class="table-scroll" role="region" aria-label="Immune decision feed" tabindex="0"><table id="feedTable"><thead><tr><th>time</th><th>decision</th><th>signals</th><th>Λ</th><th>receipt</th></tr></thead><tbody><tr><td class="mono" colspan="5">loading…</td></tr></tbody></table></div>
176
  </div>
177
 
178
  <div class="footnote" id="leanFoot">
 
184
 
185
  <script>
186
  const BASE = "/api/a11oy/v1/immune";
187
+ async function requestJSON(u,opt){
188
+ try{
189
+ const r=await fetch(u,opt);
190
+ const text=await r.text();
191
+ let data=null;
192
+ if(text){
193
+ try{data=JSON.parse(text);}
194
+ catch(e){return {ok:false,status:r.status,error:"invalid JSON response",data:null};}
195
+ }
196
+ if(!r.ok){
197
+ const detail=data&&(data.detail||data.error||data.message);
198
+ return {ok:false,status:r.status,error:detail||("HTTP "+r.status),data:data};
199
+ }
200
+ return {ok:true,status:r.status,error:null,data:data};
201
+ }catch(e){
202
+ return {ok:false,status:null,error:(e&&e.message)||String(e),data:null};
203
+ }
204
+ }
205
  function esc(s){return String(s).replace(/[&<>"']/g,function(c){return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]||c);});}
206
+ function evidenceValue(v){
207
+ if(typeof v==="string")return v;
208
+ try{return JSON.stringify(v);}catch(e){return String(v);}
209
+ }
210
+ function present(v){return v!==undefined&&v!==null&&v!=="";}
211
+ function row(label,value,cls){
212
+ if(!present(value))return "";
213
+ return '<div><span class="k">'+esc(label)+'</span><span class="v '+(cls||'')+'">'+esc(evidenceValue(value))+'</span></div>';
214
+ }
215
+ function connectionState(state,reachable){
216
+ const normalized=present(state)?String(state).trim().toUpperCase():"UNKNOWN";
217
+ const ready=reachable&&["LIVE","REAL","READY","OPERATIONAL","MEASURED"].includes(normalized);
218
+ const dot=document.getElementById("liveDot");
219
+ dot.className="live-dot"+(ready?" ready":reachable?"":" error");
220
+ document.getElementById("liveTag").textContent="IMMUNE · "+(reachable?normalized:"UNAVAILABLE");
221
+ document.getElementById("organBadge").textContent=ready?"deny-by-default · backend reported "+normalized.toLowerCase():"deny-by-default · evidence "+(reachable?normalized.toLowerCase():"unavailable");
222
+ return {normalized,ready};
223
+ }
224
+ function requestFailure(result){
225
+ const status=result.status==null?"network error":"HTTP "+result.status;
226
+ return status+(present(result.error)?" · "+result.error:result.ok?" · required evidence unavailable":"");
227
+ }
228
 
229
  async function loadStatus(){
230
+ const result = await requestJSON(BASE + "/status");
231
  const kv = document.getElementById("statusKv");
232
+ if(!result.ok||!result.data){
233
+ connectionState(null,false);
234
+ kv.innerHTML=row("status","UNAVAILABLE")+row("evidence",requestFailure(result));
235
+ return;
236
+ }
237
+ const s=result.data;
238
+ const state=connectionState(s.status,true);
239
+ document.getElementById("cVerdicts").textContent = (s.verdicts_this_process ?? "—");
240
  document.getElementById("cDenyRate").textContent = (s.deny_rate==null?"—":(Math.round(s.deny_rate*1000)/10)+"%");
241
+ document.getElementById("cDenySub").textContent = (s.deny ?? "—")+" deny / "+(s.allow ?? "—")+" allow";
242
  document.getElementById("cCorpus").textContent = (s.signature_corpus_size ?? "—");
243
+ document.getElementById("cCorpusSub").textContent = (s.threats_corpus_total ?? "—")+" STIX/MITRE in corpus";
244
  const k = s.khipu||{};
245
  document.getElementById("cChain").textContent = (k.chain_depth ?? "—");
246
+ document.getElementById("cChainSub").textContent = (typeof k.chain_verified==="boolean"?(k.chain_verified?"chain verified":"chain not verified"):"verification unavailable");
247
+ const observed=s.observed_at||s.observedAt||s.timestamp;
248
  kv.innerHTML =
249
+ row("organ",s.organ)+
250
+ row("role",s.role)+
251
+ row("Λ-gate floor",s.lambda_gate_floor)+
252
+ row("size guard",present(s.size_guard_bytes)?s.size_guard_bytes+" bytes":null)+
253
+ row("signatures",Array.isArray(s.signatures)?s.signatures.join(", "):s.signatures)+
254
+ row("last receipt",s.last_receipt_digest,"digest")+
255
+ row("khipu head",k.head_digest,"digest")+
256
+ row("chain verified",typeof k.chain_verified==="boolean"?String(k.chain_verified):null)+
257
+ row("observed at",observed)+
258
+ '<div><span class="k">status</span><span class="pill '+(state.ready?'green':'yellow')+'">'+esc(state.normalized)+'</span></div>';
259
  }
260
 
261
  async function loadGates(){
262
+ const result = await requestJSON(BASE + "/gates");
263
  const tb = document.querySelector("#gatesTable tbody");
264
+ if(!result.ok||!result.data||!Array.isArray(result.data.gates)){tb.innerHTML='<tr><td class="mono" colspan="5">'+esc(requestFailure(result))+'</td></tr>';return;}
265
+ const g=result.data;
266
  tb.innerHTML = g.gates.map(x=>
267
  '<tr><td class="mono">'+esc(x.id)+'</td><td>'+esc(x.name)+'</td><td>'+esc(x.label)+'</td><td>'+esc(x.category)+'</td>'+
268
  '<td><code>'+esc(String(x.sampleInput).slice(0,46))+'</code> → <b>'+esc(x.expectedDecision)+'</b></td></tr>').join("");
269
  }
270
 
271
  async function loadFeed(){
272
+ const result = await requestJSON(BASE + "/feed?limit=20");
273
  const tb = document.querySelector("#feedTable tbody");
274
+ if(!result.ok||!result.data||!Array.isArray(result.data.verdicts)){tb.innerHTML='<tr><td class="mono" colspan="5">'+esc(requestFailure(result))+'</td></tr>';return;}
275
+ const f=result.data;
276
+ if(!f.verdicts.length){tb.innerHTML='<tr><td class="mono" colspan="5">IDLE — no verdicts buffered (resets on restart)</td></tr>';return;}
277
  tb.innerHTML = f.verdicts.map(v=>
278
  '<tr><td>'+esc((v.timestamp||"").slice(11,19))+'</td>'+
279
  '<td class="'+esc(v.decision)+'">'+esc(v.decision)+'</td>'+
 
288
  try{ body = JSON.parse(document.getElementById("actionInput").value); }
289
  catch(e){ out.innerHTML='<div class="verdict-box deny">Invalid JSON: '+esc(e.message)+'</div>'; return; }
290
  out.innerHTML='<div class="verdict-box">inspecting…</div>';
291
+ const result = await requestJSON(BASE + "/verdict", {method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)});
292
+ if(!result.ok||!result.data){ out.innerHTML='<div class="verdict-box deny">'+esc(requestFailure(result))+'</div>'; return; }
293
+ const v=result.data;
294
+ const decision=String(v.decision||"UNKNOWN").toLowerCase();
295
+ const cls = decision==="deny"?"deny":decision==="allow"?"allow":"";
296
+ const pill = decision==="deny"?'<span class="pill red">DENY</span>':decision==="allow"?'<span class="pill green">ALLOW</span>':'<span class="pill yellow">UNKNOWN</span>';
297
+ const sigs = Array.isArray(v.signals)?(v.signals.map(s=>'<span class="sig">'+esc(s)+'</span>').join("")||'<span class="pill dim">no signals returned</span>'):'<span class="pill dim">signals unavailable</span>';
298
  const rec = v.khipu_receipt||{};
299
+ const observed=v.observed_at||v.observedAt||v.timestamp||rec.observed_at||rec.timestamp;
300
+ const lambdaEvidence=present(v.lambda_value)?' &nbsp; <span style="color:var(--dim)">Λ='+esc(v.lambda_value)+(present(v.lambda_floor)?' (floor '+esc(v.lambda_floor)+')':'')+'</span>':'';
301
+ const reasonEvidence=present(v.reason)?'<div style="margin:.4rem 0;color:var(--paragraph)">'+esc(v.reason)+'</div>':'';
302
+ const policyEvidence=row("fail closed",v.fail_closed)+row("doctrine",v.doctrine);
303
+ const receiptEvidence=
304
+ row("verdict hash",v.receipt_hash,"digest")+
305
+ row("receipt digest",rec.digest,"digest")+
306
+ row("receipt sequence",rec.seq)+
307
+ row("receipt type",rec.receipt_type)+
308
+ row("signature",rec.signature,"digest")+
309
+ row("chain verified",typeof rec.chain_verified==="boolean"?String(rec.chain_verified):null)+
310
+ row("traceparent",v.traceparent,"digest")+
311
+ row("observed at",observed);
312
  out.innerHTML =
313
  '<div class="verdict-box '+cls+'">'+
314
+ '<div>decision '+pill+lambdaEvidence+'</div>'+
315
+ reasonEvidence+
316
  '<div style="margin:.3rem 0;">signals: '+sigs+'</div>'+
317
+ '<div class="kv">'+receiptEvidence+policyEvidence+'</div>'+
 
 
 
318
  '</div>';
319
  loadStatus(); loadFeed();
320
  }