generated from mathias/template-go-web
The atlas no longer hand-maintains its content. Authored data lives in one place (internal/atlas/atlas.json); the Go layer overlays sourced facts and serves the result at /api/atlas.json; the frontend fetches + renders (inline arrays kept only as an offline fallback). First generated source: the CI/CD stage's nodes are parsed from the repo's own .gitea/workflows/cd.yml — so the viz shows the pipeline that actually runs (guard/check/build/deploy), dropping the aspirational var-go/oath-gate node that isn't wired yet. That's the point: it can't drift from the real pipeline. New internal/atlas package (JobsFromWorkflow, Build) built test-first. Adds gopkg.in/yaml.v3 (justified: parsing the workflow YAML; stdlib has no YAML). Verified: go build/vet/lint(0)/test green; /api/atlas.json → 9 stages, CI = real jobs; frontend renders from the fetch (screenshot). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
290 lines
16 KiB
HTML
290 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>CAD Atlas · From Signal to Pod</title>
|
||
<style>
|
||
:root{
|
||
--bg:#080b12; --panel:#0e1420; --panel2:#121a2b; --line:#1e2b44;
|
||
--ink:#cdd9ef; --dim:#7f92b5; --mono:#8fb4ff;
|
||
--blue:#4aa8ff; --amber:#f5b942; --coral:#ff7a5c; --green:#4ad07a; --violet:#9b8cff; --gold:#e6c15a;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html,body{margin:0;height:100%}
|
||
body{
|
||
background:
|
||
radial-gradient(circle at 1px 1px,#16213a 1px,transparent 0) 0 0/26px 26px,
|
||
var(--bg);
|
||
color:var(--ink);
|
||
font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif;
|
||
-webkit-font-smoothing:antialiased;
|
||
}
|
||
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
||
|
||
header{
|
||
position:sticky;top:0;z-index:30;backdrop-filter:blur(8px);
|
||
background:linear-gradient(180deg,rgba(8,11,18,.94),rgba(8,11,18,.72));
|
||
border-bottom:1px solid var(--line);
|
||
display:flex;align-items:baseline;gap:16px;flex-wrap:wrap;padding:14px 22px;
|
||
}
|
||
header h1{font-size:19px;margin:0}
|
||
header h1 b{color:var(--blue)}
|
||
header .sub{color:var(--dim);font-size:12px}
|
||
header .sub em{color:var(--amber);font-style:normal}
|
||
.controls{margin-left:auto;display:flex;gap:8px}
|
||
button{
|
||
font:inherit;font-size:12px;color:var(--ink);cursor:pointer;
|
||
background:var(--panel2);border:1px solid var(--line);border-radius:8px;
|
||
padding:7px 13px;display:inline-flex;align-items:center;gap:7px;
|
||
}
|
||
button:hover{border-color:var(--blue)}
|
||
button.on{border-color:var(--amber);color:var(--amber)}
|
||
button .dot{width:7px;height:7px;border-radius:50%;background:var(--blue)}
|
||
button.on .dot{background:var(--amber)}
|
||
|
||
.substrate{display:flex;gap:10px;flex-wrap:wrap;align-items:center;
|
||
padding:10px 22px;border-bottom:1px solid var(--line);background:var(--panel)}
|
||
.substrate .lbl{color:var(--dim);font-size:11px;letter-spacing:1.5px;margin-right:4px}
|
||
.host{border:1px solid var(--line);border-radius:8px;padding:6px 11px;
|
||
background:var(--panel2);font-size:12px;display:flex;gap:8px;align-items:center}
|
||
.host b{color:var(--blue)}
|
||
.host .k{color:var(--dim);font-size:11px}
|
||
.host.mesh{border-style:dashed;color:var(--dim)}
|
||
|
||
.scroll{overflow-x:auto;padding:24px 22px 20px}
|
||
.track{position:relative;display:flex;align-items:flex-start;min-width:max-content}
|
||
svg.spine{position:absolute;left:0;top:0;z-index:0;pointer-events:none;overflow:visible}
|
||
.pulse{position:absolute;top:0;left:0;width:13px;height:13px;border-radius:50%;
|
||
background:var(--amber);box-shadow:0 0 15px 4px rgba(245,185,66,.75);
|
||
transform:translate(-6.5px,-6.5px);z-index:6}
|
||
|
||
.stage{width:300px;flex:0 0 300px;padding:0 14px;position:relative;z-index:2}
|
||
.stage .no{color:var(--dim);font-size:11px;letter-spacing:2px}
|
||
.stage h2{font-size:17px;margin:6px 0 2px}
|
||
.stage .path{color:var(--mono);font-size:11.5px;margin-bottom:6px;min-height:16px}
|
||
.node{border:1px solid var(--line);border-radius:11px;background:var(--panel);
|
||
padding:12px 13px;margin-top:12px;position:relative;
|
||
transition:border-color .25s,box-shadow .25s,transform .25s}
|
||
.node .t{font-weight:600;margin-bottom:3px;display:flex;align-items:center;gap:7px}
|
||
.node .d{color:var(--dim);font-size:12px}
|
||
.node .tag{display:inline-block;font-size:10.5px;padding:2px 7px;border-radius:20px;
|
||
border:1px solid var(--line);color:var(--dim);margin:6px 6px 0 0}
|
||
.pill{width:8px;height:8px;border-radius:50%;flex:0 0 8px}
|
||
|
||
.stage.telos h2{color:var(--violet)}
|
||
.stage.gate h2{color:var(--amber)}
|
||
.stage.exec h2{color:var(--coral)}
|
||
.stage.cd h2{color:var(--green)}
|
||
.node.win{border-color:var(--coral);box-shadow:0 0 0 1px rgba(255,122,92,.25)}
|
||
.node.gateway{border-color:var(--amber);background:linear-gradient(180deg,rgba(245,185,66,.06),transparent)}
|
||
.node.council{border-color:var(--violet)}
|
||
.node.bridge{border-color:var(--blue);border-style:dashed}
|
||
.node.oath{border-color:var(--gold);border-style:dashed;background:linear-gradient(180deg,rgba(230,193,90,.05),transparent)}
|
||
|
||
.stage.active h2{text-shadow:0 0 18px currentColor}
|
||
.stage.active .node{border-color:var(--amber);
|
||
box-shadow:0 0 0 1px rgba(245,185,66,.35),0 10px 30px -12px rgba(245,185,66,.4);transform:translateY(-2px)}
|
||
|
||
.risk{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}
|
||
.risk span{font-size:10.5px;padding:2px 7px;border-radius:6px;border:1px solid var(--line)}
|
||
.risk .lo{color:var(--green);border-color:rgba(74,208,122,.4)}
|
||
.risk .md{color:var(--amber);border-color:rgba(245,185,66,.4)}
|
||
.risk .hi{color:var(--coral);border-color:rgba(255,122,92,.4)}
|
||
.gatebtns{display:flex;gap:8px;margin-top:10px}
|
||
.gatebtns .g{flex:1;text-align:center;padding:8px;border-radius:8px;font-size:12px;border:1px solid}
|
||
.gatebtns .ok{border-color:rgba(74,208,122,.5);color:var(--green)}
|
||
.gatebtns .no{border-color:rgba(255,122,92,.5);color:var(--coral)}
|
||
|
||
footer{padding:14px 22px 30px;color:var(--dim);font-size:11px;border-top:1px solid var(--line)}
|
||
footer code{color:var(--mono)}
|
||
|
||
/* ---- responsive: stack vertically, drop the SVG spine ---- */
|
||
@media(max-width:820px){
|
||
.scroll{overflow-x:visible;padding:16px}
|
||
.track{flex-direction:column;min-width:0}
|
||
svg.spine,.pulse{display:none}
|
||
.stage{width:100%;flex:auto;padding:0;border-left:2px solid var(--line);margin-left:6px;
|
||
padding-left:16px;padding-bottom:8px}
|
||
.stage.telos{border-color:var(--violet)} .stage.gate{border-color:var(--amber)}
|
||
.stage.exec{border-color:var(--coral)} .stage.cd{border-color:var(--green)}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1><b>CAD</b> Atlas · From Signal to Pod</h1>
|
||
<span class="sub mono">one human gate · everything up- and downstream is agents · <em>v0.4 · data-driven (CI stage generated from cd.yml)</em></span>
|
||
<div class="controls">
|
||
<button id="replay"><span class="dot"></span> Replay</button>
|
||
<button id="slowmo">Slow-mo · <span id="slowState">off</span></button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="substrate" id="substrate"><span class="lbl mono">SUBSTRATE</span></div>
|
||
|
||
<div class="scroll">
|
||
<div class="track" id="track">
|
||
<svg class="spine" id="spine">
|
||
<defs>
|
||
<marker id="arw" markerWidth="9" markerHeight="9" refX="6" refY="3" orient="auto">
|
||
<path d="M0,0 L6,3 L0,6 Z" fill="#33507f"></path>
|
||
</marker>
|
||
</defs>
|
||
<path id="spinePath" fill="none" stroke="#33507f" stroke-width="2"
|
||
marker-mid="url(#arw)" marker-end="url(#arw)"></path>
|
||
<path id="loopPath" fill="none" stroke="#9b8cff" stroke-width="1.6"
|
||
stroke-dasharray="5 5" opacity=".7"></path>
|
||
<text id="loopLbl" fill="#9b8cff" font-size="11"
|
||
font-family="ui-monospace,monospace" opacity=".85"></text>
|
||
</svg>
|
||
<div class="pulse" id="pulse"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="mono">
|
||
CAD → CI → CD · intent→specify→dispatch · build→test→validate · deploy→ship.
|
||
Dashed violet = feedback bus (stage 08 → TELOS: deploy outcome scored vs originating goal).
|
||
Data served from <code>/api/atlas.json</code> (authored <code>atlas.json</code> + CI stage generated from the live <code>cd.yml</code>).
|
||
Phase C plugs in live reads of <code>assessor-loop</code> ledger · <code>session_log</code> · Gitea run API · Flux events.
|
||
</footer>
|
||
|
||
<script>
|
||
// Inline data is an offline FALLBACK. Authoritative data is fetched from
|
||
// /api/atlas.json (authored atlas.json + CI stage generated from the real cd.yml).
|
||
let SUBSTRATE=[
|
||
{n:"koala", k:"RTX 5070 · k3s control-plane · Gitea · LiteLLM :30401 · llama-swap :31234 · searxng"},
|
||
{n:"iguana", k:"M2 Ultra · Ollama / mlx"},
|
||
{n:"flamingo",k:"daily driver · ~/dev"},
|
||
{n:"piguard",k:"NGINX reverse-proxy · ntfy"},
|
||
];
|
||
let NS="Tailscale mesh · ns: ai-stack · supervisor(→brain) · gitea-mcp · infra-mcp · council";
|
||
|
||
let STAGES=[
|
||
{no:"STAGE 00",cls:"",title:"Signals",path:"→ mathias/signals",nodes:[
|
||
{t:"Applied AI Radar",d:"Daily Tier-1 + weekly Tier-2 deep pass. Verified-primary bar (paper/benchmark/code/named-lab).",tags:["cron · daily/weekly","→ signals #1–26+"]},
|
||
{t:"Manual capture",d:"claude.ai strategic drop · brain capture tool.",tags:["ad-hoc"]},
|
||
{t:"Aspirational surfaces",pill:"var(--dim)",d:"Telegram / voice / URL → inbox. NOT built.",tags:["gap"]},
|
||
]},
|
||
{no:"STAGE 01",cls:"telos",title:"TELOS",path:"wiki/telos/",nodes:[
|
||
{t:"Intention substrate",pill:"var(--violet)",d:"Mission · goals · problems · strategies · status. Every downstream item traces to a goal.",tags:["brain_query wing=telos"]},
|
||
]},
|
||
{no:"STAGE 02",cls:"",title:"Strategic session",path:"claude.ai frontier + brain MCP",nodes:[
|
||
{t:"Design · ADRs · specs",d:"Human + frontier model. ISC acceptance criteria written here.",tags:["Define / converge"]},
|
||
{t:"🏛️ LLM Council",cls:"council",pill:"var(--violet)",d:"fan-out → anonymous cross-review → chairman synth. glm-4.7-flash · qwen36-35b · gemma4-31b (chair).",tags:["hard strategic Q","chat.d-ma.be"]},
|
||
{t:"Autoresearch Council",cls:"council",pill:"var(--violet)",d:"Sibling pipe — ratifies research before the gate.",tags:["proposed: → standalone svc"]},
|
||
]},
|
||
{no:"STAGE 03",cls:"",title:"Spec → Gitea issue",path:"agent-ready contract",nodes:[
|
||
{t:"Contract enforced",d:"Binary ISC · declared risk tier · reg-risk assessment · no open human deps.",tags:["LOW / MED / HIGH"]},
|
||
{t:"Admission controller",d:"Ed25519-sign issue body at creation (#36). Verify sig + PR alignment at infra boundary.",tags:["chain of custody"]},
|
||
{t:"⚖️ var-go Oath",cls:"oath",pill:"var(--gold)",d:"Acceptance contract embedded in the issue as a ```var fenced block. Exactly one — zero/multiple fail closed. Prose → typed steps; failures anchored to byte spans.",tags:["swedsl · var-go","defined here → enforced @06"]},
|
||
]},
|
||
{no:"STAGE 04",cls:"gate",title:"Human dispatch gate",path:"the only checkpoint",nodes:[
|
||
{t:"Human triggers execution",cls:"gateway",pill:"var(--amber)",d:"Ratify proposed-plan + risk tier, then dispatch.",gate:true},
|
||
{t:"Session-Dispatch bridge",cls:"bridge",pill:"var(--blue)",d:"claude.ai MCP → gitea:workflow_run_trigger → cad-dispatch.yml → agentsquad. The final design→execution bridge.",tags:["workflow_dispatch"]},
|
||
]},
|
||
{no:"STAGE 05",cls:"exec",title:"Execute · agentsquad",path:"koala · cmd/agentsquad-serve",nodes:[
|
||
{t:"Task API",pill:"var(--coral)",d:"POST /tasks → job id · GET /tasks/{id}. taskqueue + serve (v0.12+).",tags:["single agentsquad.yaml"]},
|
||
{t:"Executor + reviewer loop",cls:"win",pill:"var(--coral)",d:"ADK Go + LiteLLM. Frontier models (local qwen spirals). Reviewer on distinct tier — echo-chamber prevention.",risk:true},
|
||
{t:"dma-cli · routing + scope",cls:"bridge",pill:"var(--blue)",d:"Harness-config arm: routes agents to the right LLM backend. Three-layer scope policy + confirmation gate = CAD guardrail.",tags:["backend routing","scope guardrail"]},
|
||
{t:"assessor-loop ledger",d:"Attestation ledger (audit trail) + brain session_log on completion.",tags:["audit package"]},
|
||
]},
|
||
{no:"STAGE 06",cls:"",title:"PR → CI",path:"Gitea Actions",nodes:[
|
||
{t:"PR + label",d:"Gitea PR · agent-done / agent-blocked label.",tags:[]},
|
||
{t:"Mechanical gate",d:"go test · vet · lint · govulncheck. ISC verified mechanically.",tags:["green = proceed"]},
|
||
{t:"⚖️ var-go/oath gate",cls:"oath",pill:"var(--gold)",d:"cmd/vargo-gate runs in CI → posts commit status context=var-go/oath. Authoritative FLOOR: failed Oath blocks regardless of reviewer approval (#55 anti-rubber-stamp).",tags:["branch-protection req","enforces @03 Oath"]},
|
||
]},
|
||
{no:"STAGE 07",cls:"cd",title:"CD → pod",path:"Flux GitOps → k3s",nodes:[
|
||
{t:"Deploy on green",pill:"var(--green)",d:"Flux reconciles image → k3s pod on koala. Push ≠ deploy: bump tag in mathias/infra.",tags:["ntfy on deploy"]},
|
||
]},
|
||
{no:"STAGE 08",cls:"telos",title:"Loop back",path:"→ TELOS (feedback bus)",nodes:[
|
||
{t:"Close the loop",pill:"var(--violet)",d:"session_log + attestation → brain. Score deploy outcome vs originating goal. (arc partly manual — improvement target.)",tags:["continuous"]},
|
||
]},
|
||
];
|
||
|
||
const track=document.getElementById('track');
|
||
let stageEls=[];
|
||
function renderAtlas(){
|
||
const sub=document.getElementById('substrate');
|
||
sub.querySelectorAll('.host').forEach(el=>el.remove());
|
||
SUBSTRATE.forEach(h=>{const el=document.createElement('div');el.className='host';
|
||
el.innerHTML=`<b>${h.n}</b><span class="k mono">${h.k}</span>`;sub.appendChild(el);});
|
||
const mesh=document.createElement('div');mesh.className='host mesh mono';mesh.textContent=NS;sub.appendChild(mesh);
|
||
stageEls=[];
|
||
track.querySelectorAll('.stage').forEach(el=>el.remove());
|
||
STAGES.forEach(s=>{
|
||
const st=document.createElement('div');st.className='stage '+s.cls;
|
||
let h=`<div class="no mono">${s.no}</div><h2>${s.title}</h2><div class="path mono">${s.path||''}</div>`;
|
||
s.nodes.forEach(n=>{
|
||
const pill=n.pill?`<span class="pill" style="background:${n.pill}"></span>`:'';
|
||
let inner=`<div class="t">${pill}${n.t}</div><div class="d">${n.d}</div>`;
|
||
if(n.tags&&n.tags.length)inner+=n.tags.map(t=>`<span class="tag mono">${t}</span>`).join('');
|
||
if(n.risk)inner+=`<div class="risk mono"><span class="lo">LOW · auto</span><span class="md">MED · ntfy gate</span><span class="hi">HIGH · blocked</span></div>`;
|
||
if(n.gate)inner+=`<div class="gatebtns mono"><div class="g ok">✓ approve</div><div class="g no">✕ reject</div></div>`;
|
||
h+=`<div class="node ${n.cls||''}">${inner}</div>`;
|
||
});
|
||
st.innerHTML=h;track.appendChild(st);stageEls.push(st);
|
||
});
|
||
}
|
||
|
||
/* ---- geometry ---- */
|
||
const spine=document.getElementById('spine'), spinePath=document.getElementById('spinePath'),
|
||
loopPath=document.getElementById('loopPath'), loopLbl=document.getElementById('loopLbl'),
|
||
pulse=document.getElementById('pulse');
|
||
const RAILY=70;
|
||
let cs=[],loopY=0,spineLen=0,loopLen=0,slow=false,raf=null,t0=null,mobile=false;
|
||
|
||
function build(){
|
||
mobile=window.matchMedia('(max-width:820px)').matches;
|
||
if(mobile)return;
|
||
cs=stageEls.map(s=>s.offsetLeft+s.offsetWidth/2);
|
||
const maxBottom=Math.max(...stageEls.map(s=>s.offsetTop+s.offsetHeight));
|
||
loopY=maxBottom+40;
|
||
spine.setAttribute('width',track.scrollWidth);
|
||
spine.setAttribute('height',loopY+70);
|
||
track.style.minHeight=(loopY+70)+'px';
|
||
spinePath.setAttribute('d','M '+cs[0]+' '+RAILY+cs.slice(1).map(x=>' L '+x+' '+RAILY).join(''));
|
||
const lastX=cs[cs.length-1], telosX=cs[1];
|
||
loopPath.setAttribute('d',`M ${lastX} ${RAILY} L ${lastX} ${loopY} L ${telosX} ${loopY} L ${telosX} ${RAILY}`);
|
||
loopLbl.setAttribute('x',(telosX+lastX)/2-90);loopLbl.setAttribute('y',loopY-8);
|
||
loopLbl.textContent='feedback bus · outcome → goal';
|
||
spineLen=spinePath.getTotalLength();loopLen=loopPath.getTotalLength();
|
||
}
|
||
function run(ts){
|
||
if(mobile)return;
|
||
if(!t0)t0=ts;
|
||
const dur=slow?16000:7000;
|
||
const p=Math.min((ts-t0)/dur,1);
|
||
const total=spineLen+loopLen, dist=total*p;
|
||
let pt,onLoop=dist>spineLen;
|
||
pt=onLoop?loopPath.getPointAtLength(dist-spineLen):spinePath.getPointAtLength(dist);
|
||
pulse.style.left=pt.x+'px';pulse.style.top=pt.y+'px';
|
||
pulse.style.background=onLoop?'var(--violet)':'var(--amber)';
|
||
pulse.style.boxShadow=onLoop?'0 0 15px 4px rgba(155,140,255,.75)':'0 0 15px 4px rgba(245,185,66,.75)';
|
||
let act=onLoop?1:0; // during loop, highlight TELOS
|
||
if(!onLoop)cs.forEach((c,i)=>{if(pt.x>=c-150)act=i;});
|
||
stageEls.forEach((s,i)=>s.classList.toggle('active',i===act));
|
||
if(p<1)raf=requestAnimationFrame(run);
|
||
}
|
||
function replay(){cancelAnimationFrame(raf);t0=null;build();if(!mobile)raf=requestAnimationFrame(run);}
|
||
document.getElementById('replay').onclick=replay;
|
||
document.getElementById('slowmo').onclick=e=>{slow=!slow;e.currentTarget.classList.toggle('on',slow);
|
||
document.getElementById('slowState').textContent=slow?'on':'off';replay();};
|
||
window.addEventListener('resize',()=>{clearTimeout(window._r);window._r=setTimeout(replay,150);});
|
||
async function init(){
|
||
try{
|
||
const r=await fetch('/api/atlas.json');
|
||
if(!r.ok) throw new Error('atlas.json '+r.status);
|
||
const data=await r.json();
|
||
if(Array.isArray(data.substrate)) SUBSTRATE=data.substrate;
|
||
if(typeof data.ns==='string') NS=data.ns;
|
||
if(Array.isArray(data.stages)) STAGES=data.stages;
|
||
}catch(e){ console.warn('atlas: using inline fallback —',e); }
|
||
renderAtlas();
|
||
replay();
|
||
}
|
||
window.addEventListener('load',init);
|
||
</script>
|
||
</body>
|
||
</html>
|