From 5048450b7968e2b9a20aed029c66ac90140ba366 Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 20 Jul 2026 08:22:21 +0200 Subject: [PATCH] =?UTF-8?q?feat(atlas):=20UX=20sprint=20=E2=80=94=20progre?= =?UTF-8?q?ssive=20disclosure=20(Plain=20default=20+=20transitions)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes the atlas self-explanatory. Every stage/node gains a plain-language layer (plain_title + plain "what happens" + jargon-free node text); the previous technical copy demotes to a subtitle + on-demand detail. A Plain⇄Technical toggle (default Plain, persisted) flips the whole atlas. Biggest win: every transition arrow is now LABELLED with "what must be true to advance" (the gated- flow story that was invisible), gate hops (human @04, CI @06) styled distinctly. Spine repositioned into a uniform header band so labels never collide with copy. Copy grounded in a fresh-eyes UX review (docs/UX-REVIEW.md, reviewer≠implementer). Data model: plain_title/plain/trans_label/trans on Stage, plain on Node — guarded by a test (every stage has plain_title + a transition). Live overlays unchanged. Verified: build/vet/lint(0)/test green; Plain render screenshot-checked. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/UX-REVIEW.md | 215 +++++++++++++++++++++++++++++ internal/atlas/atlas.json | 63 +++++---- internal/atlas/build_test.go | 23 +++ internal/atlas/model.go | 27 ++-- internal/web/static/cad-atlas.html | 65 +++++++-- 5 files changed, 349 insertions(+), 44 deletions(-) create mode 100644 docs/UX-REVIEW.md diff --git a/docs/UX-REVIEW.md b/docs/UX-REVIEW.md new file mode 100644 index 0000000..f1b5509 --- /dev/null +++ b/docs/UX-REVIEW.md @@ -0,0 +1,215 @@ +# CAD Atlas — Fresh-Eyes UX Review + +Reviewer role: fresh-eyes UX (not implementer). This document critiques the copy and +information architecture of the "From Signal to Pod" atlas and specifies the +progressive-disclosure layer for the coming sprint. It does not change code. + +Sources reviewed: +- `internal/atlas/atlas.json` (authored stages + nodes) +- `internal/web/handler.go` (which stages get live data overlaid) +- `.context/PROJECT.md` (ground-truth meaning of each stage/gate) + +--- + +## 1. Diagnosis — why the current atlas is hard for a naive viewer + +The atlas is written by the person who built the pipeline, for the person who built the +pipeline. Almost every node names a **mechanism** (`Ed25519 admission controller`, +`var-go Oath`, `dma-cli`, `assessor-loop ledger`, `agentsquad`, `ISC`, `TELOS`) rather +than the **thing that happens to a piece of work**. A compliance officer or a new +engineer cannot answer the two questions they actually have: *"what is happening to the +work at this step?"* and *"why does it move to the next step?"* + +The second question is completely unanswered. The atlas renders nine stages connected by +arrows, but the arrows carry **zero copy**. There is no statement of what has to be true +for work to advance — which is exactly where the interesting governance lives (a human +sign-off at 04, a green CI gate at 06, an integrity check at 03). The pipeline's whole +selling point is "auditable, gated flow," yet the gates between stages are invisible. +A viewer sees a row of jargon boxes and an implied left-to-right drift, with no sense of +what earns each hop. + +Two smaller aggravators: (a) the reel is dense — 20+ nodes, colour-coded pills whose +meaning is never keyed, and Swedish-homelab proper nouns (koala, iguana, flamingo) that a +stakeholder can't decode; (b) Stage 06 is authored-empty (it's generated live from CI), +so on a cold/offline load that column can read as "nothing happens here," which is the +opposite of the truth — CI is a governance gate. + +The fix is not to dumb it down. It is to make **plain-language the default layer** — a +one-line "what happens + why" per stage, and a labelled "what must be true to advance" +per arrow — and demote today's precise, correct technical copy to an **on-demand layer**. + +--- + +## 2. Per-stage plain-language map (00–08) + +| stage | plain_title | plain_what (one jargon-free sentence) | keep_technical (on-demand) | +|---|---|---|---| +| **00 Signals** | Notice what's happening | New ideas and developments worth reacting to are collected — mostly an automated daily/weekly scan of AI news, plus things saved by hand. | "Signals → mathias/signals"; Applied AI Radar (Tier-1 daily / Tier-2 weekly), verified-primary bar; brain capture; aspirational inbox surfaces (not built). | +| **01 TELOS** | Why we're here | The mission, goals, and problems we're actually trying to solve live here — every piece of work downstream has to trace back to one of these goals. | "TELOS — intention substrate", `wiki/telos/`, `brain_query wing=telos`. | +| **02 Strategic session** | Think it through | A human and AI models work out *what* to do and *why*, debating hard calls and writing down the decision and what "done" will mean. | "Strategic session" — claude.ai frontier + brain MCP; ADRs/specs; ISC acceptance criteria; LLM Council (fan-out → anonymous cross-review → chairman synth); Autoresearch Council. | +| **03 Spec → Gitea issue** | Write the work order | The decision is turned into a precise, self-contained work order that an AI agent can execute unsupervised — with a pass/fail definition of done, a risk rating, and a tamper-proof seal. | "Spec → Gitea issue" — binary ISC, risk tier LOW/MED/HIGH, reg-risk assessment, no open human deps; Ed25519 admission controller (#36); var-go Oath (single fenced block, fail-closed). | +| **04 Human dispatch gate** | Human says go | A person reviews the work order and its risk and decides whether to release it — this is the one and only checkpoint where work does not move on its own. | "Human dispatch gate — the only checkpoint"; ratify plan + risk tier; Session-Dispatch bridge (claude.ai MCP → `workflow_run_trigger` → `cad-dispatch.yml` → agentsquad); dispatch-allow eligibility. | +| **05 Execute · agentsquad** | Agents do the work | AI agents actually build the thing — one writes, a second independent one reviews it to avoid marking its own homework — and every step is logged for the audit trail. | "Execute · agentsquad" on koala; Task API (`POST /tasks`); executor+reviewer loop (ADK Go + LiteLLM, reviewer on distinct tier); dma-cli routing + 3-layer scope guardrail; assessor-loop attestation ledger + brain session_log. | +| **06 PR → CI** | Automatic quality checks | The proposed change is run through automated tests and safety checks — including a check that it actually satisfies the work order's definition of done — and only a clean pass lets it continue. | "PR → CI" — Gitea Actions `cd.yml`; go test/vet/lint/govulncheck; **var-go/oath gate** (correctness floor over the reviewer, anti-rubber-stamp #55). *Nodes generated live from the latest CI run.* | +| **07 CD → pod** | Ship it | Once everything is green, the change is deployed automatically to the live server — with the rule that merging code alone doesn't ship it; the release has to be pointed at the new version. | "CD → pod" — Flux GitOps → k3s on koala; "push ≠ deploy: bump tag in mathias/infra"; ntfy on deploy. *Deploy + Flux state overlaid live.* | +| **08 Loop back** | Did it work? | The result is scored against the goal that started it and fed back into the mission board, so the next round of planning learns from what shipped. | "Loop back → TELOS (feedback bus)"; session_log + attestation → brain; outcome scored vs originating goal; arc partly manual (improvement target). | + +--- + +## 3. Per-node plain restatements + +The existing `d` text stays as the **technical detail layer**. Each `plain` line below is +the jargon-free default. Kept accurate to PROJECT.md. + +**Stage 00 — Signals** +- *Applied AI Radar* → **"An automated scan reads AI news every day (and deeper every week) and keeps only claims backed by a real paper, benchmark, code, or named lab."** +- *Manual capture* → **"Anything interesting spotted by hand gets saved into the same inbox."** +- *Aspirational surfaces* → **"Planned-but-not-built: sending ideas in by Telegram, voice, or a URL."** (mark clearly as a gap / not yet real.) + +**Stage 01 — TELOS** +- *Intention substrate* → **"The master list of mission, goals, problems, and current status — the yardstick everything downstream is measured against."** + +**Stage 02 — Strategic session** +- *Design · ADRs · specs* → **"A human and a top-tier AI model figure out the approach and write down the decision plus what a finished result must prove."** +- *LLM Council* → **"For hard calls, several AI models answer independently, anonymously critique each other, and a 'chair' model synthesises one verdict — reduces any single model's bias."** +- *Autoresearch Council* → **"A parallel version of the same review that vets research findings before they're allowed through."** + +**Stage 03 — Spec → Gitea issue** +- *Contract enforced* → **"The work order must have a clear pass/fail test, a risk rating, a regulatory-risk note, and no unfinished human dependencies before it counts as agent-ready."** +- *Admission controller* → **"The work order is cryptographically signed when created, so any later tampering is detectable and the eventual change can be checked against it."** +- *var-go Oath* → **"A machine-checkable 'definition of done' is embedded in the work order — exactly one, or the order is rejected — later used to prove the result actually meets the spec."** + +**Stage 04 — Human dispatch gate** +- *Human triggers execution* → **"A person confirms the plan and its risk level, then releases the work — nothing runs until they do."** +- *Session-Dispatch bridge* → **"The approval flips a switch that hands the signed work order over to the agents to start execution."** + +**Stage 05 — Execute · agentsquad** +- *Task API* → **"A request kicks off a job and hands back an id you can poll for progress."** +- *Executor + reviewer loop* → **"One agent does the work; a second, independent agent on a different model reviews it — so nothing marks its own homework."** +- *dma-cli · routing + scope* → **"A router sends each agent to the right AI backend and enforces what it is and isn't allowed to touch, with a confirmation gate as a guardrail."** +- *assessor-loop ledger* → **"Every step is recorded in a tamper-evident log so the whole run can be audited afterwards."** + +**Stage 06 — PR → CI** *(nodes generated live from the latest CI run — no authored nodes)* +- Live jobs render here; the plain framing for the column is: **"Automated tests and safety checks run on the proposed change, including a check that it truly satisfies the work order — only a clean pass moves on."** + +**Stage 07 — CD → pod** +- *Deploy on green* → **"When all checks pass, the release system rolls the new version onto the live server automatically — but only once the release is pointed at that version (merging code alone doesn't ship it)."** *(live deploy + Flux status also shown.)* + +**Stage 08 — Loop back** +- *Close the loop* → **"The outcome is scored against the goal that started it and written back to the mission board, so future planning learns from what actually shipped."** + +--- + +## 4. Transitions — the key deliverable + +For each arrow: *what moves the work forward, and what must be true for it to advance.* +These should be rendered **on the arrows themselves** (see §5). Today they are blank. + +- **00 → 01 — "Does it matter to us?"** + A raw signal only advances if it connects to something we actually care about. Most + captured signals stop here; the few that touch the mission get pulled up against a goal. + +- **01 → 02 — "Worth a session?"** + A goal or problem on the board becomes the seed for a design session when it's decided + it's worth working on now. The goal is the input the session must trace back to. + +- **02 → 03 — "Decision reached."** + Once the debate converges on a decision (and what "done" will mean), it advances only + when that thinking is written down as a concrete, testable specification — not while + the answer is still open. + +- **03 → 04 — "Order written, sealed, agent-ready."** + Work advances to the gate only when the spec is a complete contract: a pass/fail test, a + risk tier, a regulatory note, no open human dependencies, one embedded Oath, and a valid + cryptographic signature. A malformed or unsigned order fails closed and does not reach + the gate. + +- **04 → 05 — "A human said go."** + This is the hard stop. Nothing crosses automatically. A person must review the plan and + risk and explicitly release it, and the repo must be on the allow-list, before any agent + starts. This is the single human checkpoint in the whole pipeline. + +- **05 → 06 — "Agents produced a change."** + Work advances when the agents finish and open a proposed change (a PR) with its audit + log attached. Until there's a concrete change to test, nothing moves. + +- **06 → 07 — "All checks green."** + The change advances only if every automated check passes — tests, linters, security + scan, **and** the Oath check proving it meets the original work order. Any red gate stops + it here; a passing reviewer is not enough to override a failed Oath. + +- **07 → 08 — "It's live."** + Once the new version is actually running on the server, the deployed outcome becomes the + input to scoring. Advancing means "shipped and observable," not just "merged." + +- **08 → TELOS (feedback bus, dashed) — "What did we learn?"** + The scored outcome flows back into the mission board so goals, problems, and priorities + update. This is the loop that makes the pipeline a cycle rather than a line. Note per + PROJECT.md this arc is **partly manual today** and is an explicit improvement target — + the dashed styling should read as "aspirational / not fully automated," not just decorative. + +--- + +## 5. Progressive-disclosure recommendations + +**Default (Plain) layer — what everyone sees on load:** +- Each stage column shows: the **plain_title** as the headline, the technical title as a + smaller subtitle, and the one-line **plain_what** directly under it. +- Each node shows its **plain** one-liner as the primary text. The current `d` string is + hidden by default. +- Each arrow shows a short **transition label** (the bolded phrase from §4, e.g. "A human + said go", "All checks green") — this is the single biggest comprehension win and must + ship in the default layer, not behind a toggle. + +**On hover / expand (per node):** +- Reveal the technical `d` text, the `tags`, and the pill's meaning. +- On the arrow, hovering expands the short label into the full "what must be true to + advance" sentence from §4. + +**Plain ⇄ Technical toggle (global):** +- A single top-level switch, defaulting to **Plain**. Persist the choice (localStorage). +- Plain: plain_title headline, plain_what, plain node lines, short arrow labels. Proper + nouns (koala/iguana/agentsquad/TELOS) suppressed or shown only as a footnote. +- Technical: today's exact copy — titles, `d` strings, tags, substrate host specs — i.e. + the atlas as it exists now. Nothing is lost; the current view becomes "Technical." +- The toggle should crossfade in place, not reflow the whole layout, so a viewer can flip + back and forth and map plain↔technical on the same node. + +**Visual cues for the currently-bare transitions:** +- Give every arrow a **label chip** sitting on the spine. Gate arrows (04→05 human, 06→07 + CI) get a distinct treatment — a lock/shield glyph and a stronger colour — because those + are the governance moments the whole atlas exists to show. +- Make the **08 → TELOS feedback bus** visibly different (dashed + "partly manual" tag) so + its aspirational status is honest, matching PROJECT.md's dogfooding-honesty discipline. +- Add a small, always-visible **legend** keying the pill colours and the three gate types + (integrity / eligibility / correctness), since colour is currently unexplained. +- For **Stage 06** (authored-empty, generated live): when no live CI data is present, show + the plain_what placeholder ("Automated tests and safety checks run…") rather than an + empty column, so it never reads as "nothing happens here." + +**Three-gate overlay (stretch, high value for the compliance audience):** +- A "show governance gates" toggle that highlights the three orthogonal gates on top of + the pipeline: integrity (03, Ed25519), eligibility (04/05, dispatch-allow), correctness + (06, Oath). This directly serves the "audit chain is the viz data" thesis for a + compliance/exec viewer. + +--- + +## 6. Prioritized punch list (top 8 by comprehension impact) + +1. **Label every arrow with a plain "what must be true to advance" phrase** (§4). Biggest + miss, biggest win — turns a row of boxes into a story of gated flow. Default layer. +2. **Add a plain_what one-liner per stage** as the default column copy (§2), with the + technical title demoted to subtitle. +3. **Ship the Plain ⇄ Technical global toggle, defaulting to Plain**, persisting choice; + current copy becomes the Technical view (nothing thrown away). +4. **Rewrite node primary text to the plain lines** (§3); move existing `d` to hover/expand. +5. **Visually distinguish the two real gates (04 human, 06 CI)** with lock/shield glyphs and + stronger colour so the checkpoints read as checkpoints. +6. **Add a legend** keying pill colours and the three gate types — colour currently carries + meaning nobody can decode. +7. **Fix the Stage-06 empty-column problem**: show a plain placeholder when live CI data is + absent, so the CI gate never looks like a no-op. +8. **Make the 08→TELOS feedback bus honestly aspirational** (dashed + "partly manual" tag), + and suppress homelab proper nouns (koala/iguana/flamingo/agentsquad/TELOS) in Plain mode, + surfacing them only in Technical or a footnote. diff --git a/internal/atlas/atlas.json b/internal/atlas/atlas.json index 541376a..273ed58 100644 --- a/internal/atlas/atlas.json +++ b/internal/atlas/atlas.json @@ -7,40 +7,49 @@ ], "ns": "Tailscale mesh · ns: ai-stack · supervisor(→brain) · gitea-mcp · infra-mcp · council", "stages": [ - {"no":"STAGE 00","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 00","title":"Signals","plain_title":"Notice what's happening","plain":"New ideas and developments worth reacting to are collected — mostly an automated daily/weekly scan of AI news, plus things saved by hand.","path":"→ mathias/signals", + "trans_label":"Does it matter to us?","trans":"A raw signal only advances if it connects to something we actually care about. Most captured signals stop here; the few that touch the mission get pulled up against a goal.","nodes":[ + {"t":"Applied AI Radar","plain":"An automated scan reads AI news every day (and deeper every week) and keeps only claims backed by a real paper, benchmark, code, or named lab.","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","plain":"Anything interesting spotted by hand gets saved into the same inbox.","d":"claude.ai strategic drop · brain capture tool.","tags":["ad-hoc"]}, + {"t":"Aspirational surfaces","plain":"Planned but not built yet: sending ideas in by Telegram, voice, or a URL.","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 01","cls":"telos","title":"TELOS","plain_title":"Why we're here","plain":"The mission, goals, and problems we're actually trying to solve live here — every piece of work downstream has to trace back to one of these goals.","path":"wiki/telos/", + "trans_label":"Worth a session?","trans":"A goal or problem on the board becomes the seed for a design session when it's decided worth working on now. The goal is the input the session must trace back to.","nodes":[ + {"t":"Intention substrate","plain":"The master list of mission, goals, problems, and current status — the yardstick everything downstream is measured against.","pill":"var(--violet)","d":"Mission · goals · problems · strategies · status. Every downstream item traces to a goal.","tags":["brain_query wing=telos"]} ]}, - {"no":"STAGE 02","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 02","title":"Strategic session","plain_title":"Think it through","plain":"A human and AI models work out what to do and why, debating hard calls and writing down the decision and what \"done\" will mean.","path":"claude.ai frontier + brain MCP", + "trans_label":"Decision reached","trans":"It advances only when the thinking converges on a decision and is written down as a concrete, testable specification — not while the answer is still open.","nodes":[ + {"t":"Design · ADRs · specs","plain":"A human and a top-tier AI model figure out the approach and write down the decision plus what a finished result must prove.","d":"Human + frontier model. ISC acceptance criteria written here.","tags":["Define / converge"]}, + {"t":"🏛️ LLM Council","plain":"For hard calls, several AI models answer independently, anonymously critique each other, and a \"chair\" model synthesises one verdict — reducing any single model's bias.","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","plain":"A parallel version of the same review that vets research findings before they're allowed through.","cls":"council","pill":"var(--violet)","d":"Sibling pipe — ratifies research before the gate.","tags":["proposed: → standalone svc"]} ]}, - {"no":"STAGE 03","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 03","title":"Spec → Gitea issue","plain_title":"Write the work order","plain":"The decision is turned into a precise, self-contained work order an AI agent can execute unsupervised — with a pass/fail definition of done, a risk rating, and a tamper-proof seal.","path":"agent-ready contract", + "trans_label":"Order written, sealed, agent-ready","trans":"Advances to the gate only when the spec is a complete contract: a pass/fail test, a risk tier, a regulatory note, no open human dependencies, one embedded Oath, and a valid cryptographic signature. A malformed or unsigned order fails closed and never reaches the gate.","nodes":[ + {"t":"Contract enforced","plain":"The work order must have a clear pass/fail test, a risk rating, a regulatory-risk note, and no unfinished human dependencies before it counts as agent-ready.","d":"Binary ISC · declared risk tier · reg-risk assessment · no open human deps.","tags":["LOW / MED / HIGH"]}, + {"t":"Admission controller","plain":"The work order is cryptographically signed when created, so any later tampering is detectable and the eventual change can be checked against it.","d":"Ed25519-sign issue body at creation (#36). Verify sig + PR alignment at infra boundary.","tags":["chain of custody"]}, + {"t":"⚖️ var-go Oath","plain":"A machine-checkable \"definition of done\" is embedded in the work order — exactly one, or the order is rejected — later used to prove the result actually meets the spec.","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 04","cls":"gate","title":"Human dispatch gate","plain_title":"Human says go","plain":"A person reviews the work order and its risk and decides whether to release it — the one and only checkpoint where work does not move on its own.","path":"the only checkpoint", + "trans_label":"A human said go","trans":"The hard stop. Nothing crosses automatically — a person must review the plan and risk and explicitly release it, and the repo must be on the allow-list, before any agent starts. This is the single human checkpoint in the whole pipeline.","nodes":[ + {"t":"Human triggers execution","plain":"A person confirms the plan and its risk level, then releases the work — nothing runs until they do.","cls":"gateway","pill":"var(--amber)","d":"Ratify proposed-plan + risk tier, then dispatch.","gate":true}, + {"t":"Session-Dispatch bridge","plain":"The approval flips a switch that hands the signed work order over to the agents to start execution.","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 05","cls":"exec","title":"Execute · agentsquad","plain_title":"Agents do the work","plain":"AI agents actually build the thing — one writes, a second independent one reviews it to avoid marking its own homework — and every step is logged for the audit trail.","path":"koala · cmd/agentsquad-serve", + "trans_label":"Agents produced a change","trans":"Advances when the agents finish and open a proposed change (a PR) with its audit log attached. Until there's a concrete change to test, nothing moves.","nodes":[ + {"t":"Task API","plain":"A request kicks off a job and hands back an id you can poll for progress.","pill":"var(--coral)","d":"POST /tasks → job id · GET /tasks/{id}. taskqueue + serve (v0.12+).","tags":["single agentsquad.yaml"]}, + {"t":"Executor + reviewer loop","plain":"One agent does the work; a second, independent agent on a different model reviews it — so nothing marks its own homework.","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","plain":"A router sends each agent to the right AI backend and enforces what it is and isn't allowed to touch, with a confirmation gate as a guardrail.","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","plain":"Every step is recorded in a tamper-evident log so the whole run can be audited afterwards.","d":"Attestation ledger (audit trail) + brain session_log on completion.","tags":["audit package"]} ]}, - {"no":"STAGE 06","title":"PR → CI","path":"Gitea Actions · cd.yml (live)","generate":"ci-jobs","nodes":[]}, - {"no":"STAGE 07","cls":"cd","title":"CD → pod","path":"Flux GitOps → k3s","generate":"deploy-state","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 06","title":"PR → CI","plain_title":"Automatic quality checks","plain":"The proposed change is run through automated tests and safety checks — including a check that it actually satisfies the work order's definition of done — and only a clean pass lets it continue.","path":"Gitea Actions · cd.yml (live)","generate":"ci-jobs", + "trans_label":"All checks green","trans":"Advances only if every automated check passes — tests, linters, security scan, and the Oath check proving it meets the original work order. Any red gate stops it here; a passing reviewer is not enough to override a failed Oath.","nodes":[]}, + {"no":"STAGE 07","cls":"cd","title":"CD → pod","plain_title":"Ship it","plain":"Once everything is green, the change is deployed automatically to the live server — with the rule that merging code alone doesn't ship it; the release has to be pointed at the new version.","path":"Flux GitOps → k3s","generate":"deploy-state", + "trans_label":"It's live","trans":"Once the new version is actually running on the server, the deployed outcome becomes the input to scoring. Advancing means shipped and observable, not just merged.","nodes":[ + {"t":"Deploy on green","plain":"When all checks pass, the release system rolls the new version onto the live server automatically — but only once the release is pointed at that version (merging code alone doesn't ship it).","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"]} + {"no":"STAGE 08","cls":"telos","title":"Loop back","plain_title":"Did it work?","plain":"The result is scored against the goal that started it and fed back into the mission board, so the next round of planning learns from what shipped.","path":"→ TELOS (feedback bus)", + "trans_label":"What did we learn?","trans":"The scored outcome flows back into the mission board so goals, problems, and priorities update — the loop that makes the pipeline a cycle rather than a line. Partly manual today; an explicit improvement target.","nodes":[ + {"t":"Close the loop","plain":"The outcome is scored against the goal that started it and written back to the mission board, so future planning learns from what actually shipped.","pill":"var(--violet)","d":"session_log + attestation → brain. Score deploy outcome vs originating goal. (arc partly manual — improvement target.)","tags":["continuous"]} ]} ] } diff --git a/internal/atlas/build_test.go b/internal/atlas/build_test.go index e4a1d17..138e7c6 100644 --- a/internal/atlas/build_test.go +++ b/internal/atlas/build_test.go @@ -41,6 +41,29 @@ func TestBuild_OverlaysCIStageNodesFromWorkflow(t *testing.T) { } } +func TestDefault_HasPlainLayerAndTransitions(t *testing.T) { + a, err := atlas.Build(atlas.DataJSON, []byte("jobs:\n guard:\n a: 1\n")) + if err != nil { + t.Fatalf("Build embedded atlas: %v", err) + } + for _, s := range a.Stages { + if s.PlainTitle == "" { + t.Fatalf("stage %s missing plain_title", s.No) + } + if s.TransLabel == "" { + t.Fatalf("stage %s missing trans_label", s.No) + } + if s.Generate == "ci-jobs" { + continue // nodes are generated live, no authored plain + } + for _, n := range s.Nodes { + if n.Plain == "" { + t.Fatalf("stage %s node %q missing plain", s.No, n.Title) + } + } + } +} + func TestBuild_ErrorsOnBadAtlasJSON(t *testing.T) { if _, err := atlas.Build([]byte("{not json"), []byte("jobs:\n x:\n a: 1\n")); err == nil { t.Fatal("expected error on bad atlas JSON, got nil") diff --git a/internal/atlas/model.go b/internal/atlas/model.go index aae8790..2c69f9c 100644 --- a/internal/atlas/model.go +++ b/internal/atlas/model.go @@ -11,9 +11,11 @@ type Host struct { Spec string `json:"k"` } -// Node is a card within a stage. +// Node is a card within a stage. Plain is the jargon-free default text; Desc is +// the technical detail shown on demand. type Node struct { Title string `json:"t"` + Plain string `json:"plain,omitempty"` Desc string `json:"d,omitempty"` Pill string `json:"pill,omitempty"` Cls string `json:"cls,omitempty"` @@ -22,15 +24,22 @@ type Node struct { Gate bool `json:"gate,omitempty"` } -// Stage is one column of the pipeline. When Generate is set, its Nodes are -// derived from a source at Build time rather than taken from the authored data. +// Stage is one column of the pipeline. PlainTitle/Plain are the plain-language +// default layer; Title/Path/Nodes[].Desc are the technical layer. TransLabel/ +// Trans annotate the outgoing transition (the arrow to the next stage): what +// moves work forward and what must be true to advance. When Generate is set, +// Nodes are derived from a live source at Build time. type Stage struct { - No string `json:"no"` - Title string `json:"title"` - Path string `json:"path,omitempty"` - Cls string `json:"cls,omitempty"` - Generate string `json:"generate,omitempty"` - Nodes []Node `json:"nodes"` + No string `json:"no"` + Title string `json:"title"` + PlainTitle string `json:"plain_title,omitempty"` + Plain string `json:"plain,omitempty"` + Path string `json:"path,omitempty"` + Cls string `json:"cls,omitempty"` + Generate string `json:"generate,omitempty"` + TransLabel string `json:"trans_label,omitempty"` + Trans string `json:"trans,omitempty"` + Nodes []Node `json:"nodes"` } // Atlas is the full data model the frontend renders. diff --git a/internal/web/static/cad-atlas.html b/internal/web/static/cad-atlas.html index 896b88d..e812d97 100644 --- a/internal/web/static/cad-atlas.html +++ b/internal/web/static/cad-atlas.html @@ -59,6 +59,15 @@ display:inline-flex;align-items:center;justify-content:center; font-size:9px;color:#08121f;font-weight:600} + /* progressive disclosure */ + .tech-sub{color:var(--dim);font-size:11px;font-family:ui-monospace,SFMono-Regular,monospace;margin:1px 0 5px} + .plain-what{color:var(--ink);font-size:12.5px;line-height:1.45;margin-bottom:2px;opacity:.92} + .translabel{fill:var(--mono);font-size:10px;font-family:ui-monospace,SFMono-Regular,monospace} + .translabel-gate{fill:var(--gold);font-weight:700} + .stagehead{min-height:64px} + body.plain .stagehead{min-height:188px} + .stage .node:first-of-type{margin-top:24px} + .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} @@ -123,6 +132,7 @@

CAD Atlas · From Signal to Pod

one human gate · everything up- and downstream is agents · dev
+
@@ -145,6 +155,7 @@ stroke-dasharray="5 5" opacity=".7"> +
@@ -163,6 +174,7 @@ // empty and are filled by init()'s fetch; on failure the page shows an error // banner rather than stale inline data. let SUBSTRATE=[], NS="", STAGES=[], TIMELINE=[]; +let MODE = localStorage.getItem('atlas-mode') || 'plain'; // 'plain' | 'technical' const track=document.getElementById('track'); let stageEls=[]; @@ -174,14 +186,22 @@ function renderAtlas(){ const mesh=document.createElement('div');mesh.className='host mesh mono';mesh.textContent=NS;sub.appendChild(mesh); stageEls=[]; track.querySelectorAll('.stage').forEach(el=>el.remove()); + const plain = MODE==='plain'; STAGES.forEach(s=>{ - const st=document.createElement('div');st.className='stage '+s.cls; - let h=`
${s.no}

${s.title}

${s.path||''}
`; - s.nodes.forEach(n=>{ + const st=document.createElement('div');st.className='stage '+(s.cls||''); + const head = plain + ? `
${s.no}

${s.plain_title||s.title}

`+ + `
${s.title}
`+(s.plain?`
${s.plain}
`:'') + : `
${s.no}

${s.title}

${s.path||''}
`; + let h = `
${head}
`; + (s.nodes||[]).forEach(n=>{ const pill=n.pill?``:''; - let inner=`
${pill}${n.t}
${n.d}
`; - if(n.tags&&n.tags.length)inner+=n.tags.map(t=>`${t}`).join(''); - if(n.risk)inner+=`
LOW · autoMED · ntfy gateHIGH · blocked
`; + const body = plain ? (n.plain||n.d||'') : (n.d||''); + let inner=`
${pill}${n.t}
`+(body?`
${body}
`:''); + if(!plain){ + if(n.tags&&n.tags.length)inner+=n.tags.map(t=>`${t}`).join(''); + if(n.risk)inner+=`
LOW · autoMED · ntfy gateHIGH · blocked
`; + } if(n.gate)inner+=`
✓ approve
✕ reject
`; h+=`
${inner}
`; }); @@ -207,13 +227,16 @@ function renderTimeline(){ 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 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); + // spine sits in the band between the (uniform) stage headers and the first node + const firstTops=stageEls.map(s=>{const n=s.querySelector('.node');return n?s.offsetTop+n.offsetTop:120;}); + RAILY=Math.max(66, Math.min(...firstTops)-16); const maxBottom=Math.max(...stageEls.map(s=>s.offsetTop+s.offsetHeight)); loopY=maxBottom+40; spine.setAttribute('width',track.scrollWidth); @@ -223,7 +246,22 @@ function build(){ 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'; + loopLbl.textContent=(STAGES[8]&&STAGES[8].trans_label?STAGES[8].trans_label+' · ':'')+'feedback bus'; + // transition labels on the spine — the "what must be true to advance" story. + // Always visible (default layer). Gate hops (04→05 human, 06→07 CI) in gold. + const tg=document.getElementById('translabels'); tg.innerHTML=''; + for(let i=0;i{slow=!slow;e.currentTarget.classList.toggle('on',slow); document.getElementById('slowState').textContent=slow?'on':'off';replay();}; +function applyMode(){ + document.getElementById('modeState').textContent = MODE==='plain'?'Plain':'Technical'; + document.getElementById('mode').classList.toggle('on', MODE==='plain'); + document.body.classList.toggle('plain', MODE==='plain'); +} +document.getElementById('mode').onclick=()=>{ + MODE = MODE==='plain' ? 'technical' : 'plain'; + localStorage.setItem('atlas-mode',MODE); + applyMode(); renderAtlas(); replay(); +}; window.addEventListener('resize',()=>{clearTimeout(window._r);window._r=setTimeout(replay,150);}); async function init(){ try{ @@ -265,6 +313,7 @@ async function init(){ '
/api/atlas.json failed to load
'); return; } + applyMode(); renderAtlas(); renderTimeline(); replay();