Skip to content

Commit b762f26

Browse files
authored
Merge pull request #260 from commandlayer/codex/add-live-proof-widget-to-homepage
Add live protocol proof widget to homepage
2 parents ba272fb + 993d0b3 commit b762f26

1 file changed

Lines changed: 126 additions & 90 deletions

File tree

public/index.html

Lines changed: 126 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,32 @@ <h1 class="hero-h1">
11581158
<span id="rp-bar-text">Verifying…</span>
11591159
</div>
11601160
</div>
1161+
<p class="hero-note">The animation above explains the model. The live proof below runs it.</p>
1162+
</div>
1163+
</section>
1164+
1165+
<section class="section live-proof" id="live-proof">
1166+
<div class="container">
1167+
<p class="section-eyebrow">Live Protocol Proof</p>
1168+
<h2 class="section-h2">Resolve. Sign. Verify. Tamper. Reject.</h2>
1169+
<p class="section-p">This is not a mock animation. The homepage calls the production runtime, verifies the returned receipt, then tampers with the payload and verifies again.</p>
1170+
<div class="live-proof-card">
1171+
<div class="lp-rows">
1172+
<div class="lp-row pending" data-live-step="resolve"><div><div class="lp-row-main"><span class="lp-dot"></span><span class="lp-label">Resolve / identify signer</span></div><div class="lp-meta" data-live-meta="resolve">pending</div></div><div class="lp-state" data-live-state="resolve">pending</div></div>
1173+
<div class="lp-row pending" data-live-step="sign"><div><div class="lp-row-main"><span class="lp-dot"></span><span class="lp-label">Sign</span></div><div class="lp-meta" data-live-meta="sign">pending</div></div><div class="lp-state" data-live-state="sign">pending</div></div>
1174+
<div class="lp-row pending" data-live-step="verify"><div><div class="lp-row-main"><span class="lp-dot"></span><span class="lp-label">Verify</span></div><div class="lp-meta" data-live-meta="verify">pending</div></div><div class="lp-state" data-live-state="verify">pending</div></div>
1175+
<div class="lp-row pending" data-live-step="tamper"><div><div class="lp-row-main"><span class="lp-dot"></span><span class="lp-label">Tamper</span></div><div class="lp-meta" data-live-meta="tamper">pending</div></div><div class="lp-state" data-live-state="tamper">pending</div></div>
1176+
<div class="lp-row pending" data-live-step="reject"><div><div class="lp-row-main"><span class="lp-dot"></span><span class="lp-label">Reject</span></div><div class="lp-meta" data-live-meta="reject">pending</div></div><div class="lp-state" data-live-state="reject">pending</div></div>
1177+
</div>
1178+
<div class="lp-grid">
1179+
<div class="lp-panel"><h4>Original receipt</h4><div class="lp-kv" id="lp-valid-summary">waiting...</div></div>
1180+
<div class="lp-panel"><h4>Tampered receipt</h4><div class="lp-kv" id="lp-invalid-summary">waiting...</div></div>
1181+
</div>
1182+
<div id="lp-error" class="lp-error" style="display:none;">Runtime or verifier unavailable. No success was faked.</div>
1183+
<details class="lp-raw"><summary>Show raw JSON</summary><pre id="lp-raw-json">No data yet.</pre></details>
1184+
<div class="lp-note">Runtime signs. Verifier validates. MCP bridges. SDK wraps. Schemas describe. Schema-valid alone is not verified. Tampering changes the canonical payload and breaks verification.</div>
1185+
<div class="lp-ctas"><a class="btn btn-primary" href="/webhook-auto-verify.html">Run Auto-Verify Demo</a><a class="btn btn-secondary" href="/verify.html">Open Verifier</a><a class="btn btn-secondary" href="/stack-proof-demo.html">View Production Proof</a></div>
1186+
</div>
11611187
</div>
11621188
</section>
11631189

@@ -1390,98 +1416,108 @@ <h2>Trust the proof.<br><span class="grad">Not the agent.</span></h2>
13901416

13911417
<script>
13921418
(function () {
1393-
const steps = document.querySelectorAll('.rp-step[data-step]');
1394-
const statusRow = document.getElementById('rp-status');
1395-
const statusText = document.getElementById('rp-status-text');
1396-
const statusSub = document.getElementById('rp-status-sub');
1397-
const statusDot = document.getElementById('rp-dot');
1398-
const bar = document.getElementById('rp-bar');
1399-
const fill = document.getElementById('rp-fill');
1400-
const barText = document.getElementById('rp-bar-text');
1401-
const preview = document.querySelector('.receipt-preview');
1402-
1403-
if (!preview || !steps.length) return;
1404-
1405-
let tids = [];
1406-
let active = false;
1407-
1408-
function wait(ms) {
1409-
return new Promise(r => { const t = setTimeout(r, ms); tids.push(t); });
1410-
}
1411-
1412-
function clearAll() {
1413-
tids.forEach(clearTimeout);
1414-
tids = [];
1415-
}
1416-
1417-
function reset() {
1418-
steps.forEach(s => { s.classList.remove('is-active', 'is-done'); });
1419-
statusRow.className = 'status-row is-pending';
1420-
statusDot.style.background = '#94A3B8';
1421-
statusText.textContent = 'Waiting…';
1422-
statusSub.textContent = 'Ready to verify';
1423-
bar.className = 'rp-verified-bar';
1424-
barText.textContent = 'Verifying…';
1425-
fill.style.width = '0%';
1426-
}
1427-
1428-
async function run() {
1429-
if (active) return;
1430-
active = true;
1431-
reset();
1432-
1433-
await wait(500);
1434-
1435-
/* mark "running" in status */
1436-
statusText.textContent = 'STEP 1 SIGNED';
1437-
statusSub.textContent = 'Running canonical proof checks';
1438-
1439-
const total = steps.length;
1440-
for (let i = 0; i < total; i++) {
1441-
steps[i].classList.add('is-active');
1442-
fill.style.width = ((i / total) * 100) + '%';
1443-
await wait(700);
1444-
steps[i].classList.remove('is-active');
1445-
steps[i].classList.add('is-done');
1446-
fill.style.width = (((i + 1) / total) * 100) + '%';
1447-
await wait(120);
1419+
const preview = document.querySelector('.receipt-preview');
1420+
const liveWidget = document.getElementById('live-proof');
1421+
1422+
(function animateHeroPreview() {
1423+
const steps = document.querySelectorAll('.rp-step[data-step]');
1424+
const statusRow = document.getElementById('rp-status');
1425+
const statusText = document.getElementById('rp-status-text');
1426+
const statusSub = document.getElementById('rp-status-sub');
1427+
const statusDot = document.getElementById('rp-dot');
1428+
const bar = document.getElementById('rp-bar');
1429+
const fill = document.getElementById('rp-fill');
1430+
const barText = document.getElementById('rp-bar-text');
1431+
if (!preview || !steps.length) return;
1432+
let tids = []; let active = false;
1433+
const wait = ms => new Promise(r => { const t = setTimeout(r, ms); tids.push(t); });
1434+
const clearAll = () => { tids.forEach(clearTimeout); tids = []; };
1435+
const reset = () => { steps.forEach(s => s.classList.remove('is-active', 'is-done')); statusRow.className='status-row is-pending'; statusDot.style.background='#94A3B8'; statusText.textContent='Animation preview'; statusSub.textContent='Model walkthrough'; bar.className='rp-verified-bar'; barText.textContent='Previewing…'; fill.style.width='0%'; };
1436+
async function run() { if (active) return; active=true; reset(); await wait(350); for (let i=0;i<steps.length;i++){ steps[i].classList.add('is-active'); fill.style.width=((i/steps.length)*100)+'%'; await wait(500); steps[i].classList.remove('is-active'); steps[i].classList.add('is-done'); fill.style.width=(((i+1)/steps.length)*100)+'%'; await wait(90);} statusDot.style.background='#10B981'; statusRow.className='status-row is-verified'; statusText.textContent='Preview complete'; statusSub.textContent='Live proof runs below'; bar.className='rp-verified-bar is-verified'; barText.textContent='Model preview'; await wait(2600); active=false; run(); }
1437+
const io = new IntersectionObserver(e => { if (e[0].isIntersecting) run(); else { clearAll(); active=false; reset(); } }, { threshold: 0.2 });
1438+
io.observe(preview);
1439+
})();
1440+
1441+
(function liveProtocolProof() {
1442+
if (!liveWidget) return;
1443+
const runtimeBase = 'https://runtime.commandlayer.org';
1444+
const signerId = 'runtime.commandlayer.eth';
1445+
const knownKid = 'vC4WbcNoq2znSCiQ';
1446+
const stateEls = Object.fromEntries(['resolve','sign','verify','tamper','reject'].map(k => [k, document.querySelector(`[data-live-state="${k}"]`)]));
1447+
const metaEls = Object.fromEntries(['resolve','sign','verify','tamper','reject'].map(k => [k, document.querySelector(`[data-live-meta="${k}"]`)]));
1448+
const rowEls = Object.fromEntries(['resolve','sign','verify','tamper','reject'].map(k => [k, document.querySelector(`[data-live-step="${k}"]`)]));
1449+
const validSummary = document.getElementById('lp-valid-summary');
1450+
const invalidSummary = document.getElementById('lp-invalid-summary');
1451+
const raw = document.getElementById('lp-raw-json');
1452+
const err = document.getElementById('lp-error');
1453+
const compact = v => typeof v === 'string' && v.length > 18 ? `${v.slice(0,10)}${v.slice(-8)}` : String(v ?? 'n/a');
1454+
const setStep = (name, status, meta) => { rowEls[name].className = `lp-row ${status}`; stateEls[name].textContent = status; metaEls[name].textContent = meta; };
1455+
const normalize = r => ({ status: r?.status || (r?.ok === true ? 'VALID' : 'INVALID'), hash_matches: r?.hash_matches, signature_valid: r?.signature_valid });
1456+
const isValid = r => r?.ok === true || r?.status === 'VALID' || r?.status === 'VERIFIED';
1457+
1458+
async function runLive() {
1459+
err.style.display = 'none';
1460+
['resolve','sign','verify','tamper','reject'].forEach(k => setStep(k,'pending','pending'));
1461+
setStep('resolve','running','Signer identity loaded');
1462+
await Promise.resolve();
1463+
setStep('resolve','passed',`signer_id=${signerId} · kid=${knownKid}`);
1464+
1465+
let receipt;
1466+
try {
1467+
setStep('sign','running','POST /trust-verification/sign/v1.0.0');
1468+
const signResp = await fetch(`${runtimeBase}/trust-verification/sign/v1.0.0`, { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ payload:{ message:'homepage live proof', source:'commandlayer.org', ts:new Date().toISOString() } }) });
1469+
const signJson = await signResp.json();
1470+
receipt = signJson?.receipt || signJson?.final_receipt;
1471+
if (!signResp.ok || !receipt) throw new Error('TRANSPORT_ERROR');
1472+
const proof = receipt?.metadata?.proof || {};
1473+
setStep('sign','passed',`verb=${receipt?.verb||'n/a'} class=${receipt?.class||'n/a'} hash=${compact(proof?.hash?.value)} kid=${proof?.signature?.kid||'n/a'} signer_id=${proof?.signer_id||'n/a'}`);
1474+
1475+
setStep('verify','running','POST /verify');
1476+
const verifyResp = await fetch(`${runtimeBase}/verify`, { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ receipt }) });
1477+
const verifyJson = await verifyResp.json();
1478+
const v = normalize(verifyJson);
1479+
if (!verifyResp.ok || !isValid(verifyJson)) throw new Error('VERIFY_INVALID');
1480+
setStep('verify','passed',`verifier_status=${v.status} hash_matches=${String(v.hash_matches)} signature_valid=${String(v.signature_valid)}`);
1481+
validSummary.textContent = `VALID
1482+
verifier_status: ${v.status}
1483+
hash_matches: ${String(v.hash_matches)}
1484+
signature_valid: ${String(v.signature_valid)}`;
1485+
1486+
setStep('tamper','running','Mutating receipt.result.payload.message');
1487+
const tampered = JSON.parse(JSON.stringify(receipt));
1488+
if (!tampered.result) tampered.result = {};
1489+
if (!tampered.result.payload) tampered.result.payload = {};
1490+
tampered.result.payload.message = 'tampered homepage proof';
1491+
setStep('tamper','passed','message="tampered homepage proof"');
1492+
1493+
setStep('reject','running','POST /verify with tampered receipt');
1494+
const rejectResp = await fetch(`${runtimeBase}/verify`, { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ receipt: tampered }) });
1495+
const rejectJson = await rejectResp.json();
1496+
const t = normalize(rejectJson);
1497+
const tamperRejected = !isValid(rejectJson);
1498+
if (!rejectResp.ok || !tamperRejected) throw new Error('REJECT_EXPECTED');
1499+
setStep('reject','failed',`tampered_status=${t.status} hash_matches=${String(t.hash_matches)} signature_valid=${String(t.signature_valid)}`);
1500+
invalidSummary.textContent = `INVALID
1501+
tampered_status: ${t.status}
1502+
hash_matches: ${String(t.hash_matches)}
1503+
signature_valid: ${String(t.signature_valid)}`;
1504+
raw.textContent = JSON.stringify({ sign_response: signJson, verify_response: verifyJson, tampered_verify_response: rejectJson }, null, 2);
1505+
} catch (e) {
1506+
err.style.display = 'block';
1507+
const msg = String(e && e.message || 'TRANSPORT_ERROR');
1508+
if (msg === 'TRANSPORT_ERROR') setStep('sign','failed','TRANSPORT_ERROR');
1509+
else if (msg === 'VERIFY_INVALID') setStep('verify','failed','TRANSPORT_ERROR');
1510+
else setStep('reject','failed','TRANSPORT_ERROR');
1511+
validSummary.textContent = 'No VERIFIED result returned.';
1512+
invalidSummary.textContent = 'Tampered rejection not completed.';
1513+
}
14481514
}
14491515

1450-
/* all done — light everything green */
1451-
await wait(180);
1452-
statusDot.style.background = '#10B981';
1453-
statusRow.className = 'status-row is-verified';
1454-
statusText.textContent = 'STEP 2 VERIFIED';
1455-
statusSub.textContent = 'STEP 3 TAMPERED INVALID';
1456-
bar.className = 'rp-verified-bar is-verified';
1457-
barText.textContent = 'Proof complete';
1458-
1459-
await wait(3200);
1460-
1461-
active = false;
1462-
run();
1463-
}
1464-
1465-
/* trigger on scroll, plus immediately on load if already in view */
1466-
const io = new IntersectionObserver(entries => {
1467-
if (entries[0].isIntersecting) {
1468-
run();
1469-
} else {
1470-
clearAll();
1471-
active = false;
1472-
reset();
1473-
}
1474-
}, { threshold: 0.2 });
1475-
1476-
io.observe(preview);
1477-
1478-
/* belt-and-suspenders: if card is in view on page load, start right away */
1479-
document.addEventListener('DOMContentLoaded', () => {
1480-
requestAnimationFrame(() => {
1481-
const r = preview.getBoundingClientRect();
1482-
if (r.top < window.innerHeight && r.bottom > 0) run();
1483-
});
1484-
});
1516+
const io = new IntersectionObserver(entries => {
1517+
if (entries[0].isIntersecting) { io.disconnect(); runLive(); }
1518+
}, { threshold: 0.25 });
1519+
io.observe(liveWidget);
1520+
})();
14851521
})();
14861522
</script>
14871523
</body>

0 commit comments

Comments
 (0)