Spaces:
Running
Running
deploy(hf): sync szl-holdings/a11oy@main derived COPY set
Browse filesReusable 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>
- pages/hatun-mcp.html +67 -57
- 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:
|
| 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
|
| 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">
|
| 54 |
</div>
|
| 55 |
|
| 56 |
-
<h2>Tools
|
| 57 |
-
<p class="sub">
|
| 58 |
-
<table><thead><tr><th>Tool</th><th>
|
| 59 |
|
| 60 |
<h2>Recent invocations</h2>
|
| 61 |
-
<p class="sub">
|
| 62 |
-
<table><thead><tr><th>Time (UTC)</th><th>
|
| 63 |
|
| 64 |
<h2>Connect a client</h2>
|
| 65 |
<div class="card">
|
| 66 |
-
<p style="margin:0 0 8px">Claude Desktop —
|
| 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">
|
| 76 |
</div>
|
| 77 |
|
| 78 |
-
<div class="note">
|
| 79 |
-
<p class="sub">Source: szl_agentic_loop.py
|
| 80 |
|
| 81 |
<script>
|
| 82 |
-
const BASE="";
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 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(
|
| 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 |
-
|
| 112 |
-
|
| 113 |
-
document.getElementById('
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
document.getElementById('
|
| 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('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
| 131 |
</script>
|
| 132 |
</main>
|
| 133 |
-
<footer>Doctrine
|
| 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="
|
| 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(--
|
|
|
|
|
|
|
| 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(
|
| 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
|
|
|
|
| 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 ·
|
| 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 ·
|
| 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 |
-
|
| 95 |
-
<b>Λ-gate
|
| 96 |
-
|
|
|
|
| 97 |
<code>ImmuneNeymanPearsonOpt.lean</code> (Neyman–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. Λ = <b>Conjecture 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
|
| 115 |
Optionally include <code>axes</code> (a JSON array of trust scores) to exercise the Λ-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">
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
function esc(s){return String(s).replace(/[&<>"']/g,function(c){return({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]||c);});}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
async function loadStatus(){
|
| 163 |
-
const
|
| 164 |
const kv = document.getElementById("statusKv");
|
| 165 |
-
if(!
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
document.getElementById("cDenyRate").textContent = (s.deny_rate==null?"—":(Math.round(s.deny_rate*1000)/10)+"%");
|
| 168 |
-
document.getElementById("cDenySub").textContent = (s.deny ??
|
| 169 |
document.getElementById("cCorpus").textContent = (s.signature_corpus_size ?? "—");
|
| 170 |
-
document.getElementById("cCorpusSub").textContent = (s.threats_corpus_total ??
|
| 171 |
const k = s.khipu||{};
|
| 172 |
document.getElementById("cChain").textContent = (k.chain_depth ?? "—");
|
| 173 |
-
document.getElementById("cChainSub").textContent = (k.chain_verified? "chain verified":"
|
|
|
|
| 174 |
kv.innerHTML =
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
| 183 |
}
|
| 184 |
|
| 185 |
async function loadGates(){
|
| 186 |
-
const
|
| 187 |
const tb = document.querySelector("#gatesTable tbody");
|
| 188 |
-
if(!
|
|
|
|
| 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
|
| 196 |
const tb = document.querySelector("#feedTable tbody");
|
| 197 |
-
if(!
|
|
|
|
|
|
|
| 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
|
| 213 |
-
if(!
|
| 214 |
-
const
|
| 215 |
-
const
|
| 216 |
-
const
|
|
|
|
|
|
|
| 217 |
const rec = v.khipu_receipt||{};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
out.innerHTML =
|
| 219 |
'<div class="verdict-box '+cls+'">'+
|
| 220 |
-
'<div>decision '+pill+
|
| 221 |
-
|
| 222 |
'<div style="margin:.3rem 0;">signals: '+sigs+'</div>'+
|
| 223 |
-
'<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>Λ-gate</b> inspection path (MIN of supplied trust axes < 0.5 → 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–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. Λ = <b>Conjecture 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 Λ-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({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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)?' <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 |
}
|