Download docs/twins.html from Snapkitty/holy-agents: direct link, hf CLI and curl.
- Browser
- Download file 22.2 kB
-
https://huggingface.co/Snapkitty/holy-agents/resolve/main/docs/twins.html
- Command line
-
hf download hf://Snapkitty/holy-agents/docs/twins.html
-
curl -L -o twins.html https://huggingface.co/Snapkitty/holy-agents/resolve/main/docs/twins.html
22.2 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title>twins · holy-agents</title> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{background:#0a0a0a;color:#b0b0b0;font-family:'Courier New',monospace;min-height:100vh} | |
| .nav{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;border-bottom:1px solid #1a1a1a} | |
| .nav-brand{color:#e0e0e0;font-size:14px;letter-spacing:2px} | |
| .nav-links a{color:#555;text-decoration:none;margin-left:16px;font-size:11px} | |
| .nav-links a:hover,.nav-links a.active{color:#e0e0e0} | |
| .container{max-width:900px;margin:0 auto;padding:32px 20px} | |
| h1{color:#e0e0e0;font-size:18px;margin-bottom:8px} | |
| p{color:#555;font-size:12px;margin-bottom:24px} | |
| .twin-card{border:1px solid #1a1a1a;margin-bottom:16px;padding:20px;position:relative;overflow:hidden} | |
| .twin-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px} | |
| .twin-name{color:#e0e0e0;font-size:14px;display:flex;align-items:center;gap:8px} | |
| .twin-emoji{font-size:20px} | |
| .twin-zone{color:#444;font-size:10px} | |
| .twin-status{padding:3px 10px;font-size:10px;border:1px solid;text-transform:uppercase;letter-spacing:1px} | |
| .twin-status.auditing{border-color:#a84;color:#a84;animation:pulse 1s infinite} | |
| .twin-status.complete{border-color:#4a9;color:#4a9} | |
| .twin-status.fail{border-color:#a44;color:#a44} | |
| @keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}} | |
| .twin-body{display:grid;grid-template-columns:1fr 1fr;gap:16px} | |
| .twin-metric{border:1px solid #111;padding:12px} | |
| .twin-metric-label{color:#555;font-size:9px;text-transform:uppercase;letter-spacing:1px;margin-bottom:4px} | |
| .twin-metric-value{color:#e0e0e0;font-size:16px} | |
| .twin-metric-value.pass{color:#4a9} | |
| .twin-metric-value.fail{color:#a44} | |
| .twin-metric-value.warn{color:#a84} | |
| .findings{margin-top:12px;border-top:1px solid #111;padding-top:12px} | |
| .finding{padding:6px 0;border-bottom:1px solid #0a0a0a;font-size:11px} | |
| .finding:last-child{border-bottom:none} | |
| .finding-sev{display:inline-block;width:60px;font-size:9px;text-transform:uppercase} | |
| .finding-sev.critical{color:#a44} | |
| .finding-sev.high{color:#a44} | |
| .finding-sev.medium{color:#a84} | |
| .finding-sev.low{color:#555} | |
| .finding-sev.info{color:#444} | |
| .disagreements{margin-top:16px;border:1px solid #a443;padding:12px} | |
| .disagreements h3{color:#a44;font-size:11px;margin-bottom:8px} | |
| .disagreement{font-size:10px;color:#888;margin-bottom:4px} | |
| .remediation{margin-top:24px} | |
| .remediation h2{color:#e0e0e0;font-size:14px;margin-bottom:4px} | |
| .remediation-sub{color:#555;font-size:11px;margin-bottom:16px} | |
| .repair-card{border:1px solid #1a1a1a;margin-bottom:12px;overflow:hidden} | |
| .repair-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid #111} | |
| .repair-id{color:#e0e0e0;font-size:11px;font-weight:bold} | |
| .repair-verdict{padding:2px 8px;font-size:9px;border:1px solid;text-transform:uppercase;letter-spacing:1px} | |
| .repair-verdict.before{border-color:#a44;color:#a44} | |
| .repair-verdict.after{border-color:#4a9;color:#4a9} | |
| .repair-body{padding:12px 16px} | |
| .repair-row{display:flex;gap:12px;margin-bottom:8px} | |
| .repair-col{flex:1;padding:10px;border:1px solid #111} | |
| .repair-col-label{font-size:9px;text-transform:uppercase;letter-spacing:1px;margin-bottom:4px} | |
| .repair-col.before .repair-col-label{color:#a44} | |
| .repair-col.after .repair-col-label{color:#4a9} | |
| .repair-col.before{background:#0a0000} | |
| .repair-col.after{background:#000a00} | |
| .repair-col-text{font-size:10px;color:#888;line-height:1.5} | |
| .repair-file{font-size:9px;color:#444;margin-top:4px} | |
| .animation-zone{border:1px solid #1a1a1a;padding:16px;margin-bottom:16px;text-align:center} | |
| #twin-canvas{background:#050505;display:block;margin:0 auto} | |
| .vote-bar{display:flex;gap:4px;margin-top:12px;justify-content:center} | |
| .vote{padding:4px 12px;font-size:10px;border:1px solid #1a1a1a} | |
| .vote.pass{border-color:#4a9;color:#4a9} | |
| .vote.fail{border-color:#a44;color:#a44} | |
| </style> | |
| </head> | |
| <body> | |
| <nav class="nav"> | |
| <div class="nav-brand">holy-agents</div> | |
| <div class="nav-links"> | |
| <a href="index.html">home</a> | |
| <a href="query.html">query</a> | |
| <a href="twins.html" class="active">twins</a> | |
| <a href="agents.html">agents</a> | |
| <a href="geometry.html">geometry</a> | |
| <a href="debate.html">debate</a> | |
| <a href="repl.html">repl</a> | |
| </div> | |
| </nav> | |
| <div class="container"> | |
| <h1>Digital Twin Audit</h1> | |
| <p>Three independent auditors run in parallel. Disagreements are logged, not hidden.</p> | |
| <div class="animation-zone"> | |
| <canvas id="twin-canvas" width="860" height="200"></canvas> | |
| <div class="vote-bar"> | |
| <div class="vote" id="vote-a">🛡️ TWIN-A</div> | |
| <div class="vote" id="vote-b">⚖️ TWIN-B</div> | |
| <div class="vote" id="vote-c">⚡ TWIN-C</div> | |
| </div> | |
| </div> | |
| <div class="twin-card" id="twin-a"> | |
| <div class="twin-header"> | |
| <div class="twin-name"><span class="twin-emoji">🛡️</span> TWIN-A — Security Auditor</div> | |
| <span class="twin-status auditing" id="status-a">auditing</span> | |
| </div> | |
| <div class="twin-zone">zone: sentinel_gate</div> | |
| <div class="twin-body"> | |
| <div class="twin-metric"><div class="twin-metric-label">Confidence</div><div class="twin-metric-value" id="conf-a">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Verdict</div><div class="twin-metric-value" id="verd-a">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Critical</div><div class="twin-metric-value" id="crit-a">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Findings</div><div class="twin-metric-value" id="find-a">—</div></div> | |
| </div> | |
| <div class="findings" id="findings-a"></div> | |
| </div> | |
| <div class="twin-card" id="twin-b"> | |
| <div class="twin-header"> | |
| <div class="twin-name"><span class="twin-emoji">⚖️</span> TWIN-B — Logic Auditor</div> | |
| <span class="twin-status auditing" id="status-b">auditing</span> | |
| </div> | |
| <div class="twin-zone">zone: court</div> | |
| <div class="twin-body"> | |
| <div class="twin-metric"><div class="twin-metric-label">Confidence</div><div class="twin-metric-value" id="conf-b">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Verdict</div><div class="twin-metric-value" id="verd-b">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">High</div><div class="twin-metric-value" id="high-b">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Findings</div><div class="twin-metric-value" id="find-b">—</div></div> | |
| </div> | |
| <div class="findings" id="findings-b"></div> | |
| </div> | |
| <div class="twin-card" id="twin-c"> | |
| <div class="twin-header"> | |
| <div class="twin-name"><span class="twin-emoji">⚡</span> TWIN-C — Performance Auditor</div> | |
| <span class="twin-status auditing" id="status-c">auditing</span> | |
| </div> | |
| <div class="twin-zone">zone: forge</div> | |
| <div class="twin-body"> | |
| <div class="twin-metric"><div class="twin-metric-label">Confidence</div><div class="twin-metric-value" id="conf-c">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Verdict</div><div class="twin-metric-value" id="verd-c">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Critical</div><div class="twin-metric-value" id="crit-c">—</div></div> | |
| <div class="twin-metric"><div class="twin-metric-label">Findings</div><div class="twin-metric-value" id="find-c">—</div></div> | |
| </div> | |
| <div class="findings" id="findings-c"></div> | |
| </div> | |
| <div class="disagreements" id="disagreements" style="display:none"> | |
| <h3>⚡ Disagreements</h3> | |
| <div id="disagreement-list"></div> | |
| </div> | |
| <div class="remediation"> | |
| <h2>Previous Failure → Current Pass</h2> | |
| <div class="remediation-sub">9 consensus failures repaired via repentance cycle. Each finding was diagnosed, repaired, and re-audited.</div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">1. WORM Race Condition</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">readEntries() → modify → writeEntries() rewrote entire file. Concurrent appends overwrite each other.</div><div class="repair-file">src/worm.ts — writeEntries()</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">appendLine() using fs.appendFileSync(). Single-line atomic append, no full-file rewrite.</div><div class="repair-file">src/worm.ts — appendLine()</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">2. Path Traversal Vulnerability</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">serveStatic() joined paths with no containment check. /docs/../../src/server.ts served arbitrary files.</div><div class="repair-file">src/server.ts — path.join()</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">isPathSafe() resolves full path, checks it starts with DOCS_DIR. Returns 403 on escape.</div><div class="repair-file">src/server.ts — isPathSafe()</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">3. RTRUST Enforcement Incomplete</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">checkActionAgainstRules() checked 3 of 7 principles: truthfulness, love, justice. Autonomy, accountability, attribution unenforced.</div><div class="repair-file">src/rtrust.ts — switch (principle)</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">Added autonomy (consent), accountability (witnessed), attribution (cited). All 7 principles enforced.</div><div class="repair-file">src/rtrust.ts — 7 cases</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">4. Reverse Proof Not Integrated</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">reverseProof hardcoded to { allVerified: true, orphanArtifacts: [] }. buildReverseProof() never called.</div><div class="repair-file">src/server.ts — /query endpoint</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">buildReverseProof() called with actual artifact maps. Article III of Second Trust Deed enforced.</div><div class="repair-file">src/server.ts — /query, /audit</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">5. Unsafe JSON.parse</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">JSON.parse without try/catch in worm.ts, rtrust.ts, server.ts. Corrupted data crashes the system.</div><div class="repair-file">src/worm.ts, rtrust.ts, server.ts</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">All JSON.parse calls wrapped in try/catch. Corrupt lines skipped gracefully. POST handlers return 400.</div><div class="repair-file">src/worm.ts, rtrust.ts, server.ts</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">6. Missing Request Validation</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">POST endpoints accept arbitrary JSON. body.query as string — no type checks.</div><div class="repair-file">src/server.ts — POST /query, /action</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">validateQueryBody() and validateActionBody() type guards. Returns 400 for missing/invalid fields.</div><div class="repair-file">src/server.ts — validators</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">7. Sync I/O Hot Paths</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">loadRTRUST() reads constitution from disk on every request. No caching.</div><div class="repair-file">src/rtrust.ts — loadRTRUST()</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">Module-level cachedRules with clearRTRUSTCache(). Loaded once, cached for subsequent requests.</div><div class="repair-file">src/rtrust.ts — cachedRules</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">8. FORBIDDEN_ACTIONS Incomplete</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">unmerciful_judgment, cruelty, vengeance from RTRUST-006 missing. 21 of 24.</div><div class="repair-file">src/validation.ts — FORBIDDEN_ACTIONS</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">Added 3 missing actions. Total now 24. All RTRUST-006 violations covered.</div><div class="repair-file">src/validation.ts — 24 items</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card"> | |
| <div class="repair-header"> | |
| <span class="repair-id">9. Sentinel Skips RTRUST</span> | |
| <div><span class="repair-verdict before">FAIL</span> <span class="repair-verdict after">PASS</span></div> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before</div><div class="repair-col-text">sentinelCheck() only calls validateAction and checkForbidden. RTRUST rule violations ignored.</div><div class="repair-file">src/agents/sentinel.ts — sentinelCheck()</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After</div><div class="repair-col-text">Now imports and calls checkActionAgainstRules() from rtrust.ts. RTRUST violations added to violations array.</div><div class="repair-file">src/agents/sentinel.ts — RTRUST check</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="repair-card" style="border-color:#4a9"> | |
| <div class="repair-header" style="background:#000a00"> | |
| <span class="repair-id" style="color:#4a9">Re-Audit Summary</span> | |
| <span class="repair-verdict after" style="font-size:10px">0/3 FAIL</span> | |
| </div> | |
| <div class="repair-body"> | |
| <div class="repair-row"> | |
| <div class="repair-col before"><div class="repair-col-label">Before — All Twins</div><div class="repair-col-text">TWIN-A (Security): FAIL 0.88<br>TWIN-B (Logic): FAIL 0.42<br>TWIN-C (Performance): FAIL 0.45</div></div> | |
| <div class="repair-col after"><div class="repair-col-label">After — All Twins</div><div class="repair-col-text">TWIN-A (Security): PASS 0.92<br>TWIN-B (Logic): PASS 0.85<br>TWIN-C (Performance): PASS 0.88</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const REPLAY = { | |
| twins: { | |
| security: { | |
| verdict: 'pass', confidence: 0.92, findings: [ | |
| { severity: 'low', category: 'weak_randomness', description: 'Math.random() in visualizer.js — not security-relevant' }, | |
| { severity: 'info', category: 'self_audit', description: 'Security twin does not audit itself — by design' } | |
| ] | |
| }, | |
| logic: { | |
| verdict: 'pass', confidence: 0.85, findings: [ | |
| { severity: 'medium', category: 'principle_list', description: 'Required principles hardcoded — add 8th would not be detected' }, | |
| { severity: 'low', category: 'incomplete_test', description: 'Only one test action in logic twin check' } | |
| ] | |
| }, | |
| performance: { | |
| verdict: 'pass', confidence: 0.88, findings: [ | |
| { severity: 'low', category: 'false_async', description: 'Promise.resolve() wrapping sync functions — negligible overhead' }, | |
| { severity: 'info', category: 'acceptable', description: 'Sync I/O acceptable for startup config reads' } | |
| ] | |
| } | |
| }, | |
| disagreements: [] | |
| }; | |
| const canvas = document.getElementById('twin-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const zones = { sentinel_gate: { x: 210, y: 100 }, court: { x: 430, y: 100 }, forge: { x: 650, y: 100 } }; | |
| const twins = [ | |
| { name: 'TWIN-A', emoji: '🛡️', zone: 'sentinel_gate', color: '#44a' }, | |
| { name: 'TWIN-B', emoji: '⚖️', zone: 'court', color: '#a84' }, | |
| { name: 'TWIN-C', emoji: '⚡', zone: 'forge', color: '#a44' } | |
| ]; | |
| let particles = []; | |
| function drawZones() { | |
| Object.entries(zones).forEach(([k, v]) => { | |
| ctx.strokeStyle = '#1a1a1a'; | |
| ctx.strokeRect(v.x - 60, v.y - 40, 120, 80); | |
| ctx.fillStyle = '#1a1a1a'; | |
| ctx.font = '9px Courier New'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(k.replace('_', ' '), v.x, v.y + 55); | |
| }); | |
| } | |
| function drawTwins() { | |
| twins.forEach((t, i) => { | |
| const z = zones[t.zone]; | |
| ctx.font = '24px serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(t.emoji, z.x, z.y + 8); | |
| ctx.font = '10px Courier New'; | |
| ctx.fillStyle = t.color; | |
| ctx.fillText(t.name, z.x, z.y + 28); | |
| }); | |
| } | |
| function spawnParticle(from, to, color) { | |
| const fx = zones[from].x, fy = zones[from].y; | |
| const tx = zones[to].x, ty = zones[to].y; | |
| for (let i = 0; i < 3; i++) { | |
| particles.push({ | |
| x: fx, y: fy, | |
| vx: (tx - fx) / 40 + (Math.random() - 0.5) * 2, | |
| vy: (ty - fy) / 40 + (Math.random() - 0.5) * 2, | |
| life: 40, maxLife: 40, color | |
| }); | |
| } | |
| } | |
| function render() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| drawZones(); | |
| drawTwins(); | |
| particles = particles.filter(p => p.life > 0); | |
| particles.forEach(p => { | |
| p.x += p.vx; p.y += p.vy; p.life--; | |
| ctx.globalAlpha = p.life / p.maxLife; | |
| ctx.fillStyle = p.color; | |
| ctx.fillRect(p.x, p.y, 2, 2); | |
| }); | |
| ctx.globalAlpha = 1; | |
| requestAnimationFrame(render); | |
| } | |
| render(); | |
| async function runAudit() { | |
| const twins_data = REPLAY.twins; | |
| const labels = ['a', 'b', 'c']; | |
| const keys = ['security', 'logic', 'performance']; | |
| const confs = ['0.92', '0.85', '0.88']; | |
| for (let i = 0; i < 3; i++) { | |
| await new Promise(r => setTimeout(r, 800)); | |
| const t = twins_data[keys[i]]; | |
| document.getElementById(`status-${labels[i]}`).textContent = 'complete'; | |
| document.getElementById(`status-${labels[i]}`).className = 'twin-status complete'; | |
| document.getElementById(`conf-${labels[i]}`).textContent = confs[i]; | |
| document.getElementById(`conf-${labels[i]}`).className = 'twin-metric-value ' + (t.verdict === 'pass' ? 'pass' : 'fail'); | |
| document.getElementById(`verd-${labels[i]}`).textContent = t.verdict; | |
| document.getElementById(`verd-${labels[i]}`).className = 'twin-metric-value ' + (t.verdict === 'pass' ? 'pass' : 'fail'); | |
| const critCount = t.findings.filter(f => f.severity === 'critical').length; | |
| const highCount = t.findings.filter(f => f.severity === 'high').length; | |
| document.getElementById(labels[i] === 'b' ? `high-b` : `crit-${labels[i]}`).textContent = labels[i] === 'b' ? highCount : critCount; | |
| document.getElementById(`find-${labels[i]}`).textContent = t.findings.length; | |
| const findingsEl = document.getElementById(`findings-${labels[i]}`); | |
| findingsEl.innerHTML = t.findings.map(f => | |
| `<div class="finding"><span class="finding-sev ${f.severity}">${f.severity}</span> ${f.description}</div>` | |
| ).join(''); | |
| document.getElementById(`vote-${labels[i]}`).className = `vote ${t.verdict}`; | |
| spawnParticle(twins[i].zone, twins[(i + 1) % 3].zone, twins[i].color); | |
| } | |
| } | |
| runAudit(); | |
| </script> | |
| </body> | |
| </html> | |