generated from mathias/template-go-web
feat(stuck): stuck-state pipeline overlay (cad-atlas#11)
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>
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +15,10 @@ import (
|
||||
"git.d-ma.be/mathias/cad-atlas/internal/cluster"
|
||||
"git.d-ma.be/mathias/cad-atlas/internal/gitea"
|
||||
"git.d-ma.be/mathias/cad-atlas/internal/version"
|
||||
|
||||
"git.d-ma.be/mathias/cad/adapters/gitearead"
|
||||
cadapp "git.d-ma.be/mathias/cad/app"
|
||||
caddomain "git.d-ma.be/mathias/cad/domain"
|
||||
)
|
||||
|
||||
// atlasHTML is the Phase-A/B static shell. It fetches /api/atlas.json at load
|
||||
@@ -64,6 +71,10 @@ func NewHandler() http.Handler {
|
||||
}
|
||||
}
|
||||
}
|
||||
// cad-atlas#11: overlay mathias/cad#14's cad-stuck findings as
|
||||
// per-stage badges. Read-only — ApplyStuckOverlay only adds Nodes
|
||||
// with a link out; it never writes anything back to Gitea.
|
||||
a.Stages = atlas.ApplyStuckOverlay(a.Stages, ld.stuck)
|
||||
if ld.deploy != nil || ld.flux != nil {
|
||||
var live []atlas.Node
|
||||
if ld.deploy != nil {
|
||||
@@ -93,6 +104,30 @@ type liveOverlayData struct {
|
||||
flux *atlas.Flux
|
||||
timeline []atlas.RunDot
|
||||
issues []atlas.Node
|
||||
stuck []caddomain.StuckFinding
|
||||
}
|
||||
|
||||
// stuckRepos is the estate cad-stuck queries — mirrors mathias/dispatch's
|
||||
// dispatch-repos.txt scope. Override with CAD_ATLAS_STUCK_REPOS (comma
|
||||
// separated "owner/name" entries) so this doesn't need a redeploy every time
|
||||
// the estate's repo list changes; cad-atlas has no filesystem access to the
|
||||
// canonical dispatch-repos.txt at runtime.
|
||||
var defaultStuckRepos = []string{
|
||||
"mathias/dispatch-sandbox", "mathias/cobalt-dingo", "mathias/parallax",
|
||||
"mathias/cad-atlas", "mathias/brain-gardener", "mathias/hyperguild", "mathias/brain",
|
||||
}
|
||||
|
||||
func stuckRepos() []string {
|
||||
if v := os.Getenv("CAD_ATLAS_STUCK_REPOS"); v != "" {
|
||||
var repos []string
|
||||
for _, r := range strings.Split(v, ",") {
|
||||
if r = strings.TrimSpace(r); r != "" {
|
||||
repos = append(repos, r)
|
||||
}
|
||||
}
|
||||
return repos
|
||||
}
|
||||
return defaultStuckRepos
|
||||
}
|
||||
|
||||
// live cache: query the cluster at most once per TTL; fall back to the authored
|
||||
@@ -147,6 +182,40 @@ func liveOverlay() liveOverlayData {
|
||||
d.flux = &f
|
||||
}
|
||||
}
|
||||
d.stuck = fetchStuck()
|
||||
liveCache = d
|
||||
return d
|
||||
}
|
||||
|
||||
// fetchStuck re-derives cad#14's stuck worklist fresh on every liveOverlay
|
||||
// call (no cache beyond the shared liveTTL) — the same "re-derive, never
|
||||
// recall" discipline cad-stuck and cad-babysit both hold to. Read-only:
|
||||
// gitearead.Client has no write method in its package. Degrades to nil
|
||||
// (no badges rendered) on any error or when GITEA_TOKEN is unset, exactly
|
||||
// like every other liveOverlay source — a forge outage costs a missing
|
||||
// overlay, never a broken page.
|
||||
func fetchStuck() []caddomain.StuckFinding {
|
||||
token := os.Getenv("GITEA_TOKEN")
|
||||
if token == "" {
|
||||
return nil
|
||||
}
|
||||
base := os.Getenv("GITEA_BASE")
|
||||
if base == "" || strings.Contains(base, ".svc.cluster.local") {
|
||||
// The in-cluster Gitea service (internal/gitea's default) has no
|
||||
// public HTTPS endpoint for gitearead's client to call with a
|
||||
// bearer token in the same way; cad-stuck's own default (the public
|
||||
// git.d-ma.be) is the correct base here regardless of what
|
||||
// GITEA_BASE is set to for the read-only in-cluster fetchers above.
|
||||
base = "https://git.d-ma.be"
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
client := gitearead.New(base, token)
|
||||
cfg := cadapp.DefaultStuckConfig()
|
||||
cfg.BaseURL = base
|
||||
findings, _, err := cadapp.RunStuckQuery(ctx, client, stuckRepos(), cfg)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return findings
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user