23 Commits
Author SHA1 Message Date
mathiasandClaude Sonnet 5 97d0f56383 feat(stuck): stuck-state pipeline overlay (cad-atlas#11)
CD / Build & Import (push) Failing after 3s
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / var-go/oath (push) Skipped
CD / Deploy via GitOps (push) Skipped
Consumes mathias/cad#14's cad-stuck query directly — cad's app/domain
packages are public Go modules, so this reuses the exact tested
classification logic (import, not re-implementation) rather than
duplicating the taxonomy across two repos.

ApplyStuckOverlay (internal/atlas/stuck.go, fixture-tested) maps each
finding's category onto the existing 9-stage pipeline: armed_never_fired to
the human dispatch gate, claimed_never_ran/ceiling_hit/refire_loop/
near_attempt_cap to the one Execute stage (there is no separate Claim
stage), done_not_closed to the loop-back stage. Adds a StuckCount/
AwaitingCount badge per stage plus one Node per finding, linking out to the
underlying issue/comment — read-only, no controls, matching cad#14's own
constraint.

Two visual classes, kept deliberately distinct per the issue's own framing:
a coral 'N stuck' badge (needs intervention) vs a green 'N awaiting' badge
(a functioning control waiting on a human) — done_not_closed can never
render as a bottleneck.

Verified against the live estate: STAGE 05 shows 2 stuck (cobalt-dingo#30
ceiling_hit, cad-atlas#10 near_attempt_cap), STAGE 08 shows 5 awaiting
(cobalt-dingo #3/#7/#12/#16/#19 done_not_closed) — screenshotted via a
koala k3s Playwright job against a live-token server, matching cad#14's own
proven query output exactly.

go.mod: added git.d-ma.be/mathias/cad v0.10.0 (app/domain/adapters-gitearead
only — no mcp-chassis pulled in, that's only imported by cad's own cmd/cad).
Dockerfile: added the same GOPRIVATE/insteadOf fetch config mathias/cad's
own Dockerfile uses, now needed for this new private-module dependency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 13:27:02 +02:00
mathiasandClaude Opus 5 99fb969d27 fix(test): stop asserting CI job names that the live overlay legitimately replaces
CD / Detect unsubstituted template (push) Successful in 0s
CD / var-go/oath (push) Skipped
CD / Lint / Test / Vet (push) Successful in 8s
CD / Build & Import (push) Failing after 2s
CD / Deploy via GitOps (push) Skipped
TestAtlasJSON_ServesAtlasWithGeneratedCIStage demanded STAGE 06 contain the
job names parsed from the embedded cd.yml (guard/check/build/deploy). But when
the forge is reachable, the handler deliberately REPLACES those nodes with the
latest real run — live per-job status, which is the whole point of that overlay.

So the test passed on a laptop with no route to Gitea and failed inside CI,
where the overlay works as designed:

  CI stage missing generated job "guard"
  (got map[Detect unsubstituted template:true Lint / Test / Vet:true
           ▶ run #62 · running:true])

It went red on a commit that touched only atlas.json — a test that depends on
whether the network is reachable will eventually fail on someone innocent, and
did.

The endpoint test now asserts the shape it serves — STAGE 06 exists, is
populated by whichever source won, and every node has a title. The
deterministic assertion already exists where it belongs, in
TestBuild_OverlaysCIStageNodesFromWorkflow, which feeds Build a fixed workflow
and never touches the network.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
2026-08-19 13:26:28 +02:00
mathiasandClaude Opus 5 294d9e2fc5 feat(atlas): stage 05 reflects the dispatch execution path
CD / Detect unsubstituted template (push) Successful in 1s
CD / var-go/oath (push) Skipped
CD / Lint / Test / Vet (push) Failing after 6s
CD / Build & Import (push) Skipped
CD / Deploy via GitOps (push) Skipped
The atlas described stage 05 as agentsquad only, while headless single-issue
execution has run through mathias/dispatch for months and gained substantial
machinery in the 2026-08-17→19 session. Four nodes added, and the stage now
names both paths rather than one.

- dispatch · headless issue runs — the 1-minute poll over allow-listed repos,
  and the rule that CI-green on the pushed commit is the only done-signal.
- Two independent executors — claude or kimi (berget/Kimi-K2.6 via LiteLLM,
  EU-metered), routed per issue. Marked as a win, and as risk-bearing: it exists
  because a single provider's empty account stopped all CAD for six weeks.
- Fail-closed tool policy — one policy shared by both executors, writes confined
  to the checkout (symlink-resolved after an escape was found live), per-run
  turn and USD ceilings.
- Read-only PR review — status:review, no write tools, and the grounding rule
  that a review citing no changed file is dropped rather than published.

Deliberately NOT added: the risk-tiered agentic review with ntfy escalation.
That is designed (dispatch#48) and not built, and the atlas is a map of what
runs, not of what is planned.

Every node carries the plain-language layer the build test enforces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011PL5XcG68SvQqz3vGVbWbf
2026-08-19 12:00:17 +02:00
mathias 6967d12d1d feat(atlas): weight replay pacing on CI/CD stages by real job durations (#5)
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 6s
CD / var-go/oath (push) Has been skipped
CD / Build & Import (push) Successful in 18s
CD / Deploy via GitOps (push) Has been skipped
TDD: Job.Seconds() + StageSeconds() derive real CI/CD dwell time from the
latest run's per-job created_at/updated_at (last job in pipeline order =
deploy/stage 07, everything before it = CI/stage 06).

Frontend: weightedSpineDist() redistributes the pixel-time-budget the
06/07 segments already had, splitting it by real CI:CD duration ratio
instead of raw pixel width. Falls back to the exact prior constant-speed
sweep when no run data is available (weights default to segment pixel
length) or when a job is skipped (0 duration) — no behavior change for
stages 00-05/08, which still have no live timing source (same gap as #5's
ledger item).

Verified: real duration values flow through /api/atlas.json (ci_duration_s:
18 observed against a real run), full page screenshot confirms no visual
regression.
2026-07-20 23:22:53 +02:00
mathias b2761a4747 feat(gitea): surface mathias's own open Gitea issues on stage 03 (#4)
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / var-go/oath (push) Has been skipped
CD / Build & Import (push) Successful in 12s
CD / Deploy via GitOps (push) Has been skipped
TDD: IssueNodes parses /repos/issues/search into stage nodes (title, repo
tag, clickable html_url). gitea.MyIssues() reads GITEA_TOKEN and skips
gracefully when unset, mirroring the existing liveOverlay fallback pattern.

Single-operator homelab, not per-visitor OAuth: a static read-only PAT
gates on "cleared Authentik forward-auth", not per-user token exchange —
see #4 discussion. GITEA_TOKEN provisioning in infra (ExternalSecret) is
a separate follow-up; without it the overlay is inert (no crash, just no
live nodes), so this ships safely ahead of that wiring.

Nodes with a url now render as clickable <a class="node"> instead of
<div class="node">.
2026-07-20 23:04:44 +02:00
mathiasandClaude Opus 4.8 60cc8894a2 feat(atlas): UX — overview rail + gate glyphs + pill reconcile (#3,#4,#5)
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 12s
CD / Deploy via GitOps (push) Has been skipped
Closes the UX sprint's authored layer.
#3 Horizontal-scroll blindness → an always-visible "PIPELINE" overview rail:
9 colour-coded chips (00 Notice → 08 Learn) showing the whole shape at a glance,
each click-to-scroll to its stage. First-timers now see the arc + that more exists.
#4 Real gate glyph — 🔒 on the two checkpoint transitions (human @04, CI @06),
desktop spine + mobile rows.
#5 Reconcile legend with Plain: decorative node pills hidden in Plain, so colour
is reserved for live status (green/amber/coral) — exactly what the legend keys.
(#6 attached chips/grammar: the question-vs-statement label mix is meaningful —
decisions vs milestones — kept; SVG chip background skipped as low-value.)

New Stage.short (guarded by JSON validity via existing tests). build/vet/lint(0)/
test green; desktop Plain screenshot-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 10:46:08 +02:00
mathiasandClaude Opus 4.8 863c4c964b feat(atlas): UX — plain node titles + mobile transition rows (re-review #1,#2)
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Has been skipped
Lap-2 review's top two findings:
#1 Node titles were still jargon (the lap-1 disease one level down). Every node
now has a plain_t ("var-go Oath" → "Definition of done", "Admission controller"
→ "Tamper-proof seal", …); Plain view leads with it and demotes the technical
name to a dim in-card subtitle — mirrors the stage-head pattern. plain_t on every
authored node is now guard-tested.
#2 Transition labels lived only in the SVG spine (display:none <820px) with a
hover-only rationale — invisible on phones. Added stacked-layout transition rows
(HTML, always-visible, full sentence, no hover) shown on mobile; desktop header
band scoped to ≥821px so mobile isn't stretched.

build/vet/lint(0)/test green; desktop Plain screenshot-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 10:14:53 +02:00
mathiasandClaude Opus 4.8 ac086b65b9 fix(atlas): UX polish — stage-06 plain nodes + shorter arrow labels (#7)
CD / Detect unsubstituted template (push) Successful in 0s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Has been skipped
Stage 06 no longer renders bare in Plain: Build is now tolerant of a jobless/
absent workflow (keeps an authored plain placeholder instead of erroring), and
generated CI job nodes carry a plain hint. Shortened the two overlong transition
labels ("Order written, sealed, agent-ready" → "Sealed & agent-ready",
"Agents produced a change" → "Change proposed") so they stop clipping.

Build tolerance test-first. build/vet/lint(0)/test green; screenshot-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 09:46:42 +02:00
mathiasandClaude Opus 4.8 d992ca4a01 feat(atlas): UX polish — legend + feedback-bus honesty + Plain declutter (#6, #8)
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Has been skipped
#6: always-visible legend keying the live-status colours (passed/running/failed),
the governance-gate colour, and the dashed feedback loop.
#8: the 08→TELOS loop is labelled "partly manual today" (honest, per PROJECT.md);
Plain view hides the jargon-dense substrate ribbon + technical footer, so a
non-technical viewer isn't shown koala/iguana/ns/assessor-loop proper nouns —
they remain in Technical view.

Frontend/CSS only; verified by screenshot. build/test/lint green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 09:40:46 +02:00
mathiasandClaude Opus 4.8 5048450b79 feat(atlas): UX sprint — progressive disclosure (Plain default + transitions)
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / Build & Import (push) Successful in 18s
CD / Deploy via GitOps (push) Has been skipped
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) <noreply@anthropic.com>
2026-07-20 08:22:21 +02:00
mathiasandClaude Opus 4.8 1f7c9ab1eb feat(atlas): Phase C — live Flux reconcile status on stage 07
CD / Detect unsubstituted template (push) Successful in 0s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Has been skipped
Stage 07 now also shows the Flux `apps` Kustomization state — reconciled/failed
+ last-applied revision (main@shortsha) — read in-cluster (new read-only Role in
flux-system). Sits alongside the live deploy node. FluxStatus/FluxNode test-first,
fallback-safe.

Verified: build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:56:42 +02:00
mathiasandClaude Opus 4.8 3e28dcf69f fix(atlas): treat skipped jobs as OK in run aggregate
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 13s
CD / Deploy via GitOps (push) Has been skipped
Tag-push runs skip the deploy job (deploy is main-only), so runs with a skipped
job were mislabelled "running" in the timeline. Skipped now counts as completed-
OK; only genuinely in-progress states aggregate to running. Test-first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:48:30 +02:00
mathiasandClaude Opus 4.8 995e428eba feat(atlas): Phase C — recent-runs timeline
CD / Detect unsubstituted template (push) Successful in 0s
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Has been skipped
A strip of the last 12 runs (aggregate pass/fail/running per run) below the
substrate ribbon, coloured, newest-first, run # on hover. Parsed from the same
Gitea /actions/tasks fetch (RecentRuns, test-first; State refactored to share
the aggregate). No new RBAC. Hidden when no live data.

Verified: build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:45:59 +02:00
mathiasandClaude Opus 4.8 3d1f76997b feat(atlas): Phase C — live CD/deploy state on stage 07
CD / Lint / Test / Vet (push) Successful in 4s
CD / Build & Import (push) Successful in 13s
CD / Detect unsubstituted template (push) Successful in 0s
CD / Deploy via GitOps (push) Has been skipped
Stage 07 now shows the pod's own live deploy state — deployed image tag +
N/M replicas ready, green only when the rollout is fully ready — read from the
k8s Deployment in-cluster (new namespaced deployments-read Role). DeployState/
DeployNode test-first; prepended to the authored CD narrative, fallback-safe.

Verified: build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:39:01 +02:00
mathiasandClaude Opus 4.8 3ff922a4d5 feat(atlas): per-job CI status + build-injected version in the UI
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / Build & Import (push) Successful in 18s
CD / Deploy via GitOps (push) Has been skipped
Phase C: the CI stage now shows the latest run's per-JOB status (Lint/Test,
Build, Deploy, …), each coloured by outcome, from the Gitea /actions/tasks
per-job entries — replacing the static cd.yml job-id list when live.
LatestRunJobs/RunNodes + RunSummary.State aggregate, all test-first.

Version: injected at build via -ldflags from `git describe --tags` (CI checkout
now fetch-depth:0 + --build-arg), served in /api/atlas.json, shown in the header.
No more hand-maintained version label drifting from the git tag.

Verified: build/vet/lint(0)/test green; version ldflag served correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 07:21:52 +02:00
mathiasandClaude Opus 4.8 02a23a02dd feat(atlas): Phase C increment 1 — live CI runs on the pipeline
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 6s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 0s
The CI stage now shows the pipeline's latest real execution, read live from the
in-cluster Gitea Actions API (public read, no token, gitea-http.gitea.svc):
"▶ run #N · <state>" coloured by outcome, prepended to the generated job list.
LatestRun + RunNode built test-first; cached in the same 30s liveOverlay; falls
back cleanly when Gitea is unreachable.

assessor-loop ledger / session_log deferred: no live CAD data exists for this repo
yet (brain confirms). Gitea run history is the real available trace.

Verified: build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:03:03 +02:00
mathiasandClaude Opus 4.8 633ba153f2 feat(atlas): Phase B tail — live namespaces + trim inline fallback
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 1s
NamespaceSummary (test-first) renders the substrate ns line from live cluster
namespaces (system-filtered, sorted, capped). Handler now overlays both node
specs and the ns line via a single cached liveOverlay(). Removed the inline
SUBSTRATE/STAGES/NS data — /api/atlas.json is the single source; the page shows
an error banner on fetch failure instead of stale data.

GPU model naming intentionally NOT done: koala's node has no GPU product label
(only nvidia.com/gpu count), so "1× GPU" is the API's truth — naming the RTX 5070
would need GPU-feature-discovery, out of scope.

Verified: build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 00:56:40 +02:00
mathiasandClaude Opus 4.8 39fd9b9adc feat(atlas): hybrid substrate — live specs for cluster nodes, authored rest
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 6s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 1s
MergeSubstrate overlays live node specs onto the authored machine list by name:
cluster nodes (koala) get fresh live specs, non-cluster machines (iguana/flamingo/
piguard) stay authored, new live nodes are appended. Test-first. Restores the full
homelab machine list while keeping cluster nodes truthful.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 00:47:53 +02:00
mathiasandClaude Opus 4.8 74dffc1ff3 feat(atlas): Phase B — substrate from the live cluster
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 5s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 0s
The substrate machines are now rendered from the live k3s nodes (arch, cpu,
memory, GPU, k3s version), read in-cluster via the mounted ServiceAccount with a
stdlib HTTP client (no client-go) and a 30s cache; falls back to the authored
substrate whenever the cluster is unreachable. Placement per the homelab decision
"in-cluster only for in-workload ops" — a running web app rendering live data fits.

HostsFromNodes parser built test-first. RBAC (read-only node-reader SA) shipped in
infra k3s/apps/cad-atlas/.

Verified: go build/vet/lint(0)/test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 00:43:20 +02:00
mathiasandClaude Opus 4.8 a9c72d6ca8 feat(atlas): Phase B — data-driven /api/atlas.json, CI stage generated from cd.yml
CD / Detect unsubstituted template (push) Successful in 0s
CD / Lint / Test / Vet (push) Successful in 5s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 1s
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>
2026-07-20 00:34:06 +02:00
mathiasandClaude Opus 4.8 5c6a4ab232 feat: Phase A — serve static CAD Atlas viz + self-contained context
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Has been skipped
CD / Build & Import (push) Has been skipped
CD / Deploy via GitOps (push) Has been skipped
Wire the reel-style CAD workflow atlas (signal→pod) as the served page via
go:embed. Fill PROJECT.md/README with the 9-stage workflow, three governance
gates (Ed25519 admission / dispatch-allow / var-go Oath), phase map A→C,
dogfooding model, brain deep-links and external references.

Fixes two template-go-web latent bugs surfaced by dogfooding:
- templ version skew: go.mod pinned v0.2.778 vs templ@latest generator
- .gitignore pattern *.templ.go did not match generated *_templ.go

Verified: go build · vet · golangci-lint (0 issues) · go test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:34:23 +02:00
mathias 9ea65ef5d2 template: substitute placeholders
CD / Detect unsubstituted template (push) Has been cancelled
CD / Lint / Test / Vet (push) Has been cancelled
CD / Build & Import (push) Has been cancelled
CD / Deploy via GitOps (push) Has been cancelled
2026-07-19 08:29:48 +00:00
mathias 25ad154eac Initial commit 2026-07-19 08:29:23 +00:00